instauto 9.0.0 → 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +5 -2
- package/{db.js → src/db.js} +0 -0
- package/{index.js → src/index.js} +107 -72
- package/.eslintrc +0 -36
- package/.github/FUNDING.yml +0 -2
- package/example.js +0 -101
- package/instabot.yml +0 -5
- package/logo.png +0 -0
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Now there is a GUI application for those who don't want to code: [SimpleInstaBot
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
11
11
|
- First install [Node.js](https://nodejs.org/en/) 8 or newer.
|
|
12
|
+
- On MacOS, it's recommended to use [homebrew](https://brew.sh/): `brew install node`
|
|
12
13
|
|
|
13
14
|
- Create a new directory with a file like [example.js](https://github.com/mifi/instauto/blob/master/example.js)
|
|
14
15
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instauto",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.2",
|
|
4
4
|
"description": "Instagram automation library written in Node.js",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
8
8
|
"test": "exit 0"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
10
13
|
"author": "Mikael Finstad <finstaden@gmail.com>",
|
|
11
14
|
"repository": "mifi/instauto",
|
|
12
15
|
"license": "MIT",
|
package/{db.js → src/db.js}
RENAMED
|
File without changes
|
|
@@ -136,28 +136,30 @@ const Instauto = async (db, browser, options) => {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
async function checkReachedFollowedUserDayLimit() {
|
|
139
|
-
|
|
140
|
-
if (reachedFollowedUserDayLimit) {
|
|
139
|
+
if (getNumFollowedUsersThisTimeUnit(dayMs) >= maxFollowsPerDay) {
|
|
141
140
|
logger.log('Have reached daily follow/unfollow limit, waiting 10 min');
|
|
142
141
|
await sleep(10 * 60 * 1000);
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
async function checkReachedFollowedUserHourLimit() {
|
|
147
|
-
|
|
148
|
-
if (hasReachedFollowedUserHourLimit) {
|
|
146
|
+
if (getNumFollowedUsersThisTimeUnit(hourMs) >= maxFollowsPerHour) {
|
|
149
147
|
logger.log('Have reached hourly follow rate limit, pausing 10 min');
|
|
150
148
|
await sleep(10 * 60 * 1000);
|
|
151
149
|
}
|
|
152
150
|
}
|
|
153
151
|
|
|
152
|
+
async function checkReachedLikedUserDayLimit() {
|
|
153
|
+
if (getNumLikesThisTimeUnit(dayMs) >= maxLikesPerDay) {
|
|
154
|
+
logger.log('Have reached daily like rate limit, pausing 10 min');
|
|
155
|
+
await sleep(10 * 60 * 1000);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
154
159
|
async function throttle() {
|
|
155
160
|
await checkReachedFollowedUserDayLimit();
|
|
156
161
|
await checkReachedFollowedUserHourLimit();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
function hasReachedDailyLikesLimit() {
|
|
160
|
-
return getNumLikesThisTimeUnit(dayMs) >= maxLikesPerDay;
|
|
162
|
+
await checkReachedLikedUserDayLimit();
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
function haveRecentlyFollowedUser(username) {
|
|
@@ -307,6 +309,9 @@ const Instauto = async (db, browser, options) => {
|
|
|
307
309
|
const elementHandles3 = await page.$x("//header//button[*//span[@aria-label='Following']]");
|
|
308
310
|
if (elementHandles3.length > 0) return elementHandles3[0];
|
|
309
311
|
|
|
312
|
+
const elementHandles4 = await page.$x("//header//button[*//*[name()='svg'][@aria-label='Following']]");
|
|
313
|
+
if (elementHandles4.length > 0) return elementHandles4[0];
|
|
314
|
+
|
|
310
315
|
return undefined;
|
|
311
316
|
}
|
|
312
317
|
|
|
@@ -571,10 +576,57 @@ const Instauto = async (db, browser, options) => {
|
|
|
571
576
|
await page.evaluate(likeCurrentUserImagesPageCode, { dryRun, likeImagesMin, likeImagesMax });
|
|
572
577
|
}
|
|
573
578
|
|
|
574
|
-
async function
|
|
579
|
+
async function followUserRespectingRestrictions({ username, skipPrivate = false }) {
|
|
580
|
+
if (getPrevFollowedUser(username)) {
|
|
581
|
+
logger.log('Skipping already followed user', username);
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
const graphqlUser = await navigateToUserAndGetData(username);
|
|
585
|
+
|
|
586
|
+
const followedByCount = graphqlUser.edge_followed_by.count;
|
|
587
|
+
const followsCount = graphqlUser.edge_follow.count;
|
|
588
|
+
const isPrivate = graphqlUser.is_private;
|
|
589
|
+
|
|
590
|
+
// logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
591
|
+
|
|
592
|
+
const ratio = followedByCount / (followsCount || 1);
|
|
593
|
+
|
|
594
|
+
if (isPrivate && skipPrivate) {
|
|
595
|
+
logger.log('User is private, skipping');
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
if (
|
|
599
|
+
(followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
|
|
600
|
+
(followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
|
|
601
|
+
(followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
|
|
602
|
+
(followUserMinFollowing != null && followsCount < followUserMinFollowing)
|
|
603
|
+
) {
|
|
604
|
+
logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (
|
|
608
|
+
(followUserRatioMax != null && ratio > followUserRatioMax) ||
|
|
609
|
+
(followUserRatioMin != null && ratio < followUserRatioMin)
|
|
610
|
+
) {
|
|
611
|
+
logger.log('User has too many followers compared to follows or opposite, skipping');
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
await followUser(username);
|
|
616
|
+
|
|
617
|
+
await sleep(30000);
|
|
618
|
+
await throttle();
|
|
619
|
+
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function processUserFollowers(username, {
|
|
575
624
|
maxFollowsPerUser = 5, skipPrivate = false, enableLikeImages, likeImagesMin, likeImagesMax,
|
|
576
625
|
} = {}) {
|
|
577
|
-
|
|
626
|
+
const enableFollow = maxFollowsPerUser > 0;
|
|
627
|
+
|
|
628
|
+
if (enableFollow) logger.log(`Following up to ${maxFollowsPerUser} followers of ${username}`);
|
|
629
|
+
if (enableLikeImages) logger.log(`Liking images of up to ${likeImagesMax} followers of ${username}`);
|
|
578
630
|
|
|
579
631
|
await throttle();
|
|
580
632
|
|
|
@@ -586,86 +638,52 @@ const Instauto = async (db, browser, options) => {
|
|
|
586
638
|
logger.log('User followers batch', followersBatch);
|
|
587
639
|
|
|
588
640
|
for (const follower of followersBatch) {
|
|
589
|
-
|
|
590
|
-
logger.log('Skipping already followed user', follower);
|
|
591
|
-
} else {
|
|
592
|
-
try {
|
|
593
|
-
if (numFollowedForThisUser >= maxFollowsPerUser) {
|
|
594
|
-
logger.log('Have reached followed limit for this user, stopping');
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
641
|
+
await throttle();
|
|
597
642
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
// logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
605
|
-
|
|
606
|
-
const ratio = followedByCount / (followsCount || 1);
|
|
607
|
-
|
|
608
|
-
if (isPrivate && skipPrivate) {
|
|
609
|
-
logger.log('User is private, skipping');
|
|
610
|
-
} else if (
|
|
611
|
-
(followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
|
|
612
|
-
(followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
|
|
613
|
-
(followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
|
|
614
|
-
(followUserMinFollowing != null && followsCount < followUserMinFollowing)
|
|
615
|
-
) {
|
|
616
|
-
logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
617
|
-
} else if (
|
|
618
|
-
(followUserRatioMax != null && ratio > followUserRatioMax) ||
|
|
619
|
-
(followUserRatioMin != null && ratio < followUserRatioMin)
|
|
620
|
-
) {
|
|
621
|
-
logger.log('User has too many followers compared to follows or opposite, skipping');
|
|
622
|
-
} else {
|
|
623
|
-
await followUser(follower);
|
|
624
|
-
numFollowedForThisUser += 1;
|
|
643
|
+
try {
|
|
644
|
+
if (enableFollow && numFollowedForThisUser >= maxFollowsPerUser) {
|
|
645
|
+
logger.log('Have reached followed limit for this user, stopping');
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
625
648
|
|
|
626
|
-
|
|
649
|
+
let didActuallyFollow = false;
|
|
650
|
+
if (enableFollow) didActuallyFollow = await followUserRespectingRestrictions({ username: follower, skipPrivate });
|
|
651
|
+
if (didActuallyFollow) numFollowedForThisUser += 1;
|
|
627
652
|
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
|
|
631
|
-
} catch (err) {
|
|
632
|
-
logger.error(`Failed to follow user's images ${follower}`, err);
|
|
633
|
-
await takeScreenshot();
|
|
634
|
-
}
|
|
635
|
-
}
|
|
653
|
+
const didFailToFollow = enableFollow && !didActuallyFollow;
|
|
636
654
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
} catch (err) {
|
|
641
|
-
logger.error(`Failed to process follower ${follower}`, err);
|
|
642
|
-
await sleep(20000);
|
|
655
|
+
if (enableLikeImages && !didFailToFollow) {
|
|
656
|
+
// Note: throws error if user isPrivate
|
|
657
|
+
await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
|
|
643
658
|
}
|
|
659
|
+
} catch (err) {
|
|
660
|
+
logger.error(`Failed to process follower ${follower}`, err);
|
|
661
|
+
await takeScreenshot();
|
|
662
|
+
await sleep(20000);
|
|
644
663
|
}
|
|
645
664
|
}
|
|
646
665
|
}
|
|
647
666
|
}
|
|
648
667
|
|
|
649
|
-
async function
|
|
650
|
-
if (!maxFollowsTotal || maxFollowsTotal <= 2) {
|
|
651
|
-
throw new Error(`Invalid parameter maxFollowsTotal ${maxFollowsTotal}`);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
668
|
+
async function processUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableFollow = true, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
|
|
655
669
|
// If maxFollowsTotal turns out to be lower than the user list size, slice off the user list
|
|
656
670
|
const usersToFollowFollowersOfSliced = shuffleArray(usersToFollowFollowersOf).slice(0, maxFollowsTotal);
|
|
657
671
|
|
|
658
|
-
|
|
659
|
-
|
|
672
|
+
const maxFollowsPerUser = enableFollow && usersToFollowFollowersOfSliced.length > 0 ? Math.floor(maxFollowsTotal / usersToFollowFollowersOfSliced.length) : 0;
|
|
673
|
+
|
|
674
|
+
if (maxFollowsPerUser === 0 && (!enableLikeImages || likeImagesMin < 1 || likeImagesMax < 1)) {
|
|
675
|
+
logger.warn('Nothing to follow or like');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
660
678
|
|
|
661
679
|
for (const username of usersToFollowFollowersOfSliced) {
|
|
662
680
|
try {
|
|
663
|
-
await
|
|
681
|
+
await processUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
|
|
664
682
|
|
|
665
683
|
await sleep(10 * 60 * 1000);
|
|
666
684
|
await throttle();
|
|
667
685
|
} catch (err) {
|
|
668
|
-
|
|
686
|
+
logger.error('Failed to process user followers, continuing', username, err);
|
|
669
687
|
await takeScreenshot();
|
|
670
688
|
await sleep(60 * 1000);
|
|
671
689
|
}
|
|
@@ -725,6 +743,22 @@ const Instauto = async (db, browser, options) => {
|
|
|
725
743
|
return j;
|
|
726
744
|
}
|
|
727
745
|
|
|
746
|
+
async function safelyFollowUserList({ users, skipPrivate, limit }) {
|
|
747
|
+
logger.log('Following users, up to limit', limit);
|
|
748
|
+
|
|
749
|
+
for (const username of users) {
|
|
750
|
+
await throttle();
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
await followUserRespectingRestrictions({ username, skipPrivate });
|
|
754
|
+
} catch (err) {
|
|
755
|
+
logger.error(`Failed to follow user ${username}, continuing`, err);
|
|
756
|
+
await takeScreenshot();
|
|
757
|
+
await sleep(20000);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
728
762
|
function getPage() {
|
|
729
763
|
return page;
|
|
730
764
|
}
|
|
@@ -1011,7 +1045,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
1011
1045
|
}
|
|
1012
1046
|
|
|
1013
1047
|
return {
|
|
1014
|
-
followUserFollowers,
|
|
1048
|
+
followUserFollowers: processUserFollowers,
|
|
1015
1049
|
unfollowNonMutualFollowers,
|
|
1016
1050
|
unfollowAllUnknown,
|
|
1017
1051
|
unfollowOldFollowed,
|
|
@@ -1023,8 +1057,9 @@ const Instauto = async (db, browser, options) => {
|
|
|
1023
1057
|
getFollowersOrFollowing,
|
|
1024
1058
|
getUsersWhoLikedContent,
|
|
1025
1059
|
safelyUnfollowUserList,
|
|
1060
|
+
safelyFollowUserList,
|
|
1026
1061
|
getPage,
|
|
1027
|
-
followUsersFollowers,
|
|
1062
|
+
followUsersFollowers: processUsersFollowers,
|
|
1028
1063
|
doesUserFollowMe,
|
|
1029
1064
|
};
|
|
1030
1065
|
};
|
package/.eslintrc
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"node": true
|
|
4
|
-
},
|
|
5
|
-
"extends": "airbnb/base",
|
|
6
|
-
"parserOptions": {
|
|
7
|
-
"sourceType": "script",
|
|
8
|
-
"ecmaVersion": 2022
|
|
9
|
-
},
|
|
10
|
-
"globals": {
|
|
11
|
-
"window": true,
|
|
12
|
-
"document": true
|
|
13
|
-
},
|
|
14
|
-
"rules": {
|
|
15
|
-
"max-len": 0,
|
|
16
|
-
"arrow-parens": 0,
|
|
17
|
-
"no-console": 0,
|
|
18
|
-
"no-await-in-loop": 0,
|
|
19
|
-
"object-curly-newline": 0,
|
|
20
|
-
'no-restricted-syntax': [
|
|
21
|
-
'error',
|
|
22
|
-
{
|
|
23
|
-
selector: 'ForInStatement',
|
|
24
|
-
message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
selector: 'LabeledStatement',
|
|
28
|
-
message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
selector: 'WithStatement',
|
|
32
|
-
message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
}
|
|
36
|
-
}
|
package/.github/FUNDING.yml
DELETED
package/example.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const puppeteer = require('puppeteer'); // eslint-disable-line import/no-extraneous-dependencies
|
|
4
|
-
|
|
5
|
-
const Instauto = require('instauto'); // eslint-disable-line import/no-unresolved
|
|
6
|
-
|
|
7
|
-
const options = {
|
|
8
|
-
cookiesPath: './cookies.json',
|
|
9
|
-
|
|
10
|
-
username: 'your-ig-username',
|
|
11
|
-
password: 'your-ig-password',
|
|
12
|
-
|
|
13
|
-
// Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one hour:
|
|
14
|
-
maxFollowsPerHour: 20,
|
|
15
|
-
// Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one day:
|
|
16
|
-
maxFollowsPerDay: 150,
|
|
17
|
-
// (NOTE setting the above parameters too high will cause temp ban/throttle)
|
|
18
|
-
|
|
19
|
-
maxLikesPerDay: 50,
|
|
20
|
-
|
|
21
|
-
// Don't follow users that have a followers / following ratio less than this:
|
|
22
|
-
followUserRatioMin: 0.2,
|
|
23
|
-
// Don't follow users that have a followers / following ratio higher than this:
|
|
24
|
-
followUserRatioMax: 4.0,
|
|
25
|
-
// Don't follow users who have more followers than this:
|
|
26
|
-
followUserMaxFollowers: null,
|
|
27
|
-
// Don't follow users who have more people following them than this:
|
|
28
|
-
followUserMaxFollowing: null,
|
|
29
|
-
// Don't follow users who have less followers than this:
|
|
30
|
-
followUserMinFollowers: null,
|
|
31
|
-
// Don't follow users who have more people following them than this:
|
|
32
|
-
followUserMinFollowing: null,
|
|
33
|
-
|
|
34
|
-
// NOTE: The dontUnfollowUntilTimeElapsed option is ONLY for the unfollowNonMutualFollowers function
|
|
35
|
-
// This specifies the time during which the bot should not touch users that it has previously followed (in milliseconds)
|
|
36
|
-
// After this time has passed, it will be able to unfollow them again.
|
|
37
|
-
// TODO should remove this option from here
|
|
38
|
-
dontUnfollowUntilTimeElapsed: 3 * 24 * 60 * 60 * 1000,
|
|
39
|
-
|
|
40
|
-
// Usernames that we should not touch, e.g. your friends and actual followings
|
|
41
|
-
excludeUsers: [],
|
|
42
|
-
|
|
43
|
-
// If true, will not do any actions (defaults to true)
|
|
44
|
-
dryRun: false,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
(async () => {
|
|
48
|
-
let browser;
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
browser = await puppeteer.launch({ headless: false });
|
|
52
|
-
|
|
53
|
-
// Create a database where state will be loaded/saved to
|
|
54
|
-
const instautoDb = await Instauto.JSONDB({
|
|
55
|
-
// Will store a list of all users that have been followed before, to prevent future re-following.
|
|
56
|
-
followedDbPath: './followed.json',
|
|
57
|
-
// Will store all unfollowed users here
|
|
58
|
-
unfollowedDbPath: './unfollowed.json',
|
|
59
|
-
// Will store all likes here
|
|
60
|
-
likedPhotosDbPath: './liked-photos.json',
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const instauto = await Instauto(instautoDb, browser, options);
|
|
64
|
-
|
|
65
|
-
// This can be used to unfollow people:
|
|
66
|
-
// Will unfollow auto-followed AND manually followed accounts who are not following us back, after some time has passed
|
|
67
|
-
// The time is specified by config option dontUnfollowUntilTimeElapsed
|
|
68
|
-
// await instauto.unfollowNonMutualFollowers();
|
|
69
|
-
// await instauto.sleep(10 * 60 * 1000);
|
|
70
|
-
|
|
71
|
-
// Unfollow previously auto-followed users (regardless of whether or not they are following us back)
|
|
72
|
-
// after a certain amount of days (2 weeks)
|
|
73
|
-
// Leave room to do following after this too (unfollow 2/3 of maxFollowsPerDay)
|
|
74
|
-
const unfollowedCount = await instauto.unfollowOldFollowed({ ageInDays: 14, limit: options.maxFollowsPerDay * (2 / 3) });
|
|
75
|
-
|
|
76
|
-
if (unfollowedCount > 0) await instauto.sleep(10 * 60 * 1000);
|
|
77
|
-
|
|
78
|
-
// List of usernames that we should follow the followers of, can be celebrities etc.
|
|
79
|
-
const usersToFollowFollowersOf = ['lostleblanc', 'sam_kolder'];
|
|
80
|
-
|
|
81
|
-
// Now go through each of these and follow a certain amount of their followers
|
|
82
|
-
await instauto.followUsersFollowers({
|
|
83
|
-
usersToFollowFollowersOf,
|
|
84
|
-
maxFollowsTotal: options.maxFollowsPerDay - unfollowedCount,
|
|
85
|
-
skipPrivate: true,
|
|
86
|
-
enableLikeImages: true,
|
|
87
|
-
likeImagesMax: 3,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await instauto.sleep(10 * 60 * 1000);
|
|
91
|
-
|
|
92
|
-
console.log('Done running');
|
|
93
|
-
|
|
94
|
-
await instauto.sleep(30000);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.error(err);
|
|
97
|
-
} finally {
|
|
98
|
-
console.log('Closing browser');
|
|
99
|
-
if (browser) await browser.close();
|
|
100
|
-
}
|
|
101
|
-
})();
|
package/instabot.yml
DELETED
package/logo.png
DELETED
|
Binary file
|