├── .gitignore ├── README.md ├── to_replace ├── bot_block.py └── bot.py └── remove_ghosts.py /.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | whitelist.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # May not work 2 | Instagram is constantly making it harder to create any kind of bots, the [instabot](https://github.com/ohld/igbot) API used in this script is not being maintained anymore. 3 | And I keep getting issues about the `4xx` errors, meaning there is no guarantee this script will work. 4 | 5 |
6 | 7 | --- 8 | --- 9 | 10 |
11 | 12 | # Remove all ghost followers from instagram 13 | Finds and removes everyone who hasn't interacted with the last 100 of your posts 14 | 15 |
16 | 17 | ## Download 18 | Download the latest release [here](http://bit.ly/remove-ghosts-releases) 19 | 20 |
21 | 22 | ## Preview 23 | ![preview](https://user-images.githubusercontent.com/25122875/124366716-fcff8d00-dc51-11eb-8183-e4a7c356776e.png) 24 | 25 | 26 |
27 | 28 | ## How to 29 | 1. If you're on windows download the the ready to use `.exe` from [here](http://bit.ly/remove-ghosts-releases) or 30 | 2. Install [instabot](https://github.com/ohld/igbot) using: `pip install instabot` 31 | 3. Locate the package using `pip show instabot` and navigate to the given path 32 | 4. Copy files from `to_replace` folder to the opened instabot location and replace the originals 33 | 5. Launch the script: `python remove_ghosts.py` 34 | 35 |
36 | 37 | ## Whitelist 38 | - Every username or user ID in the `whitelist.txt` file will be ignored 39 | 40 |
41 | 42 | ## Removing from list 43 | - If you happen to have a list of usernames you want to remove you can use the `--list` or `-l` argument and provide the filename 44 | - Like `python remove_ghosts.py --list yourfilename.txt` 45 | 46 |
47 | 48 | ## Sidenotes 49 | - If you receive a `403` error, you have to wait ~1h before continuing 50 | -------------------------------------------------------------------------------- /to_replace/bot_block.py: -------------------------------------------------------------------------------- 1 | import random 2 | from tqdm import tqdm 3 | 4 | def check_if_replaced(self): 5 | pass 6 | 7 | def remove_follower(self, user_id): 8 | user_id = self.convert_to_user_id(user_id) 9 | return self.api.remove_follower(user_id) 10 | 11 | def block(self, user_id): 12 | user_id = self.convert_to_user_id(user_id) 13 | self.delay("block") 14 | if self.api.block(user_id): 15 | self.total["blocks"] += 1 16 | return True 17 | else: 18 | print('ERROR 1') 19 | return False 20 | 21 | 22 | def unblock(self, user_id): 23 | user_id = self.convert_to_user_id(user_id) 24 | self.delay("unblock") 25 | if self.api.unblock(user_id): 26 | self.total["unblocks"] += 1 27 | return True 28 | else: 29 | print('ERROR 2') 30 | return False 31 | 32 | 33 | def block_users(self, user_ids): 34 | broken_items = [] 35 | self.logger.info("Going to block %d users." % len(user_ids)) 36 | for user_id in tqdm(user_ids): 37 | if not self.block(user_id): 38 | self.error_delay() 39 | broken_items = user_ids[user_ids.index(user_id) :] 40 | break 41 | self.logger.info("DONE: Total blocked %d users." % self.total["blocks"]) 42 | return broken_items 43 | 44 | 45 | def unblock_users(self, user_ids): 46 | broken_items = [] 47 | self.logger.info("Going to unblock %d users." % len(user_ids)) 48 | for user_id in tqdm(user_ids): 49 | if not self.unblock(user_id): 50 | self.error_delay() 51 | broken_items.append(user_id) 52 | self.logger.info("DONE: Total unblocked %d users." % self.total["unblocks"]) 53 | return broken_items 54 | 55 | 56 | def block_bots(self): 57 | self.logger.info("Going to block bots.") 58 | your_followers = self.followers 59 | your_likers = self.get_user_likers(self.user_id) 60 | not_likers = list(set(your_followers) - set(your_likers)) 61 | random.shuffle(not_likers) 62 | for user in tqdm(not_likers): 63 | if not self.check_not_bot(user): 64 | self.logger.info( 65 | "Found bot: " 66 | "https://instagram.com/%s/" % self.get_user_info(user)["username"] 67 | ) 68 | self.block(user) 69 | -------------------------------------------------------------------------------- /remove_ghosts.py: -------------------------------------------------------------------------------- 1 | from instabot import Bot 2 | from argparse import ArgumentParser 3 | from getpass import getpass 4 | from time import sleep 5 | from tqdm import tqdm 6 | from colorama import Fore 7 | from colorama import Style 8 | 9 | import subprocess 10 | import sys 11 | import os 12 | 13 | # parsing the arguments for one-line login 14 | parser = ArgumentParser() 15 | parser.add_argument('-u', '--username', metavar='username', nargs=1, help='Direct login info') 16 | parser.add_argument('-p', '--password', metavar='password', nargs=1, help='Direct login info') 17 | parser.add_argument('-l', '--list', metavar='list', nargs=1, help='Provide path to list of usernames or users to remove') 18 | args = parser.parse_args() 19 | 20 | bot = Bot() 21 | clear = lambda: os.system('cls' if os.name=='nt' else 'clear') 22 | whitelist = [] 23 | 24 | def remove_ghots(): 25 | 26 | # get all user's followers 27 | success('Getting followers...') 28 | all_followers = bot.get_user_followers(username) 29 | success(f'Found {len(all_followers)} followers\n') 30 | 31 | # get all user's posts 32 | success('Getting all posts...') 33 | all_posts = bot.get_total_user_medias(username) 34 | total_posts = len(all_posts) 35 | 36 | # trim posts to avoid rate limit 37 | success(f'Found {total_posts} posts\n') 38 | if(total_posts > 100): 39 | success('Limiting search to last 100 posts only') 40 | all_posts = all_posts[:100] 41 | 42 | # get every person that ever liked any post 43 | success('Getting all likers...') 44 | all_likers = [] 45 | 46 | for post in tqdm(all_posts): 47 | post_likers = bot.get_media_likers(post) 48 | 49 | for liker in post_likers: 50 | if liker not in all_likers: 51 | all_likers.append(liker) 52 | 53 | success(f'Found {len(all_likers)} unique likers\n') 54 | 55 | # substract lists and get everyone that follows the user but didn't like anything 56 | success('Getting not-likers...') 57 | not_likers = list(set(all_followers) - set(all_likers)) 58 | success(f'Found {len(not_likers)} people who didn\'t like any of your posts\n') 59 | 60 | if len(not_likers) > 0: 61 | while(True): # wait for the firm confirmation 62 | block = input('[>] Remove them? [yes/n]: ') 63 | if(block == 'yes' or block =='Yes' or block == 'YES'): 64 | break 65 | if(block == 'n' or block =='N'): 66 | sys.exit() 67 | 68 | # remove every follower that is not whitelisted 69 | print() 70 | success('Removing ghost followers...') 71 | for user in not_likers: 72 | if user not in whitelist: 73 | remove_user(user) 74 | else: 75 | warning(f'Skipping {user} - whitelisted') 76 | 77 | print() 78 | success('Finished', True) 79 | sys.exit() 80 | 81 | def remove_from_list(filename): 82 | with open(filename, 'r') as f: 83 | users = f.read().splitlines() 84 | for user in users: 85 | if user.isnumeric(): 86 | remove_user(user) 87 | else: 88 | remove_user(bot.get_username_from_user_id(user)) 89 | 90 | def remove_user(user): 91 | error = 0 92 | while True: 93 | if bot.remove_follower(user): 94 | success(f'Removed {user}' + ' '*10) 95 | error = 0 96 | sleep(.3) 97 | break 98 | else: 99 | if error == 0: 100 | error('', 'Could not remove user, possible rate limit, retrying...') 101 | error += 1 102 | sleep(10) 103 | continue 104 | 105 | ########################################## 106 | 107 | def success(str, as_input=False): 108 | output = f'{Fore.GREEN}[OK]{Style.RESET_ALL} {str}' 109 | if as_input: 110 | input(output) 111 | else: 112 | print(output) 113 | 114 | def warning(str, as_input=False): 115 | output = f'{Fore.YELLOW}[!]{Style.RESET_ALL} {str}' 116 | if as_input: 117 | input(output) 118 | else: 119 | print(output) 120 | 121 | def error(str, e='', as_input=False): 122 | output = f'{Fore.RED}[ERROR]{Style.RESET_ALL} {str}: {e}' 123 | if as_input: 124 | input(output) 125 | else: 126 | print(output) 127 | 128 | ########################################## 129 | 130 | def parse_whitelist(): 131 | global whitelist 132 | 133 | # check if whitelist exists else create and write first line 134 | if not os.path.exists('whitelist.txt'): 135 | warning('Whitelist not found, creating...') 136 | 137 | with open('whitelist.txt', 'w+') as f: 138 | f.write('!! PROVIDE USERNAMES OR IDs IN SEPARATE LINES !!') 139 | 140 | return True 141 | # check if provided users are IDs else inform and exit 142 | else: 143 | with open('whitelist.txt', 'r') as f: 144 | lines = f.read().splitlines() 145 | if len(lines) > 1: 146 | del lines[0] 147 | for line in lines: 148 | if not line.isnumeric(): 149 | try: 150 | line = bot.get_user_id_from_username(line) 151 | except: 152 | return False 153 | whitelist.append(line) 154 | return True 155 | 156 | def check_files(): 157 | try: 158 | bot.check_if_replaced() 159 | return True 160 | except: 161 | return False 162 | 163 | print() 164 | if not check_files(): 165 | res = subprocess.run('pip show instabot', stdout=subprocess.PIPE, shell=True).stdout.decode('utf-8').splitlines() 166 | for line in res: 167 | if line.split(' ')[0] == 'Location:': 168 | path = line.split(' ')[1] 169 | error('', f'Make sure you replaced "bot_block.py" & "bot.py" files in {path}', True) 170 | sys.exit() 171 | error('', 'Something is wrong with "instabot" library', True) 172 | sys.exit() 173 | 174 | # read the credentials from arguments, if empty ask for them 175 | username = args.username[0] if args.username else input('[>] Username: ') 176 | password = args.password[0] if args.password else getpass('[>] Password: ') # hidden input for password 177 | 178 | ########################################## 179 | 180 | try: 181 | print() 182 | bot.login(username=username, password=password) 183 | 184 | if parse_whitelist(): 185 | success('Whitelist correct') 186 | else: 187 | error('', 'Could not parse whitelist, make sure IDs and usernames are correct', True) 188 | sys.exit() 189 | 190 | sleep(1) 191 | clear() 192 | 193 | if args.list: 194 | remove_from_list(args.list[0]) 195 | else: 196 | remove_ghots() 197 | 198 | except Exception as e: 199 | if str(e) == "'ds_user'": 200 | error('Cookie error', 'Delete "config" folder and try again') 201 | else: 202 | error('Error occured', e) -------------------------------------------------------------------------------- /to_replace/bot.py: -------------------------------------------------------------------------------- 1 | version = "0.117.0" 2 | import atexit 3 | import datetime 4 | import logging 5 | import os 6 | import random 7 | import signal 8 | import time 9 | 10 | from instabot import utils 11 | 12 | # from instabot.api.api import API 13 | from ..api import API 14 | 15 | from .state.bot_state import BotState 16 | from .state.bot_cache import BotCache 17 | from .bot_archive import archive, archive_medias, unarchive_medias 18 | from .bot_block import block, block_bots, block_users, unblock, unblock_users, remove_follower, check_if_replaced 19 | from .bot_checkpoint import load_checkpoint, save_checkpoint 20 | from .bot_comment import ( 21 | comment, 22 | comment_geotag, 23 | comment_hashtag, 24 | comment_medias, 25 | comment_user, 26 | comment_users, 27 | is_commented, 28 | reply_to_comment, 29 | ) 30 | from .bot_delete import delete_comment, delete_media, delete_medias 31 | from .bot_direct import ( 32 | approve_pending_thread_requests, 33 | send_hashtag, 34 | send_like, 35 | send_media, 36 | send_medias, 37 | send_message, 38 | send_messages, 39 | send_photo, 40 | send_profile, 41 | ) 42 | from .bot_filter import check_media, check_not_bot, check_user, filter_medias 43 | from .bot_follow import ( 44 | approve_pending_follow_requests, 45 | follow, 46 | follow_followers, 47 | follow_following, 48 | follow_users, 49 | reject_pending_follow_requests, 50 | ) 51 | from .bot_get import ( 52 | convert_to_user_id, 53 | get_archived_medias, 54 | get_comment, 55 | get_comment_likers, 56 | get_geotag_medias, 57 | get_geotag_users, 58 | get_hashtag_medias, 59 | get_hashtag_users, 60 | get_last_user_medias, 61 | get_link_from_media_id, 62 | get_locations_from_coordinates, 63 | get_media_commenters, 64 | get_media_comments, 65 | get_media_comments_all, 66 | get_media_id_from_link, 67 | get_media_info, 68 | get_media_likers, 69 | get_media_owner, 70 | get_messages, 71 | get_pending_follow_requests, 72 | get_pending_thread_requests, 73 | get_popular_medias, 74 | get_self_story_viewers, 75 | get_timeline_medias, 76 | get_timeline_users, 77 | get_total_hashtag_medias, 78 | get_total_user_medias, 79 | get_user_followers, 80 | get_user_following, 81 | get_user_id_from_username, 82 | get_user_info, 83 | get_user_likers, 84 | get_user_medias, 85 | get_user_reel, 86 | get_user_stories, 87 | get_user_tags_medias, 88 | get_username_from_user_id, 89 | get_your_medias, 90 | search_users, 91 | get_muted_friends, 92 | ) 93 | from .bot_like import ( 94 | like, 95 | like_comment, 96 | like_followers, 97 | like_following, 98 | like_geotag, 99 | like_hashtag, 100 | like_location_feed, 101 | like_media_comments, 102 | like_medias, 103 | like_timeline, 104 | like_user, 105 | like_users, 106 | ) 107 | from .bot_photo import download_photo, download_photos, upload_photo 108 | from .bot_stats import save_user_stats 109 | from .bot_story import download_stories, upload_story_photo, watch_users_reels 110 | from .bot_support import ( 111 | check_if_file_exists, 112 | console_print, 113 | extract_urls, 114 | read_list_from_file, 115 | ) 116 | from .bot_unfollow import ( 117 | unfollow, 118 | unfollow_everyone, 119 | unfollow_non_followers, 120 | unfollow_users, 121 | ) 122 | from .bot_unlike import ( 123 | unlike, 124 | unlike_comment, 125 | unlike_media_comments, 126 | unlike_medias, 127 | unlike_user, 128 | ) 129 | from .bot_video import download_video, upload_video 130 | 131 | current_path = os.path.abspath(os.getcwd()) 132 | 133 | 134 | class Bot(object): 135 | def __init__( 136 | self, 137 | base_path=current_path + "/config/", 138 | whitelist_file="whitelist.txt", 139 | blacklist_file="blacklist.txt", 140 | comments_file="comments.txt", 141 | followed_file="followed.txt", 142 | unfollowed_file="unfollowed.txt", 143 | skipped_file="skipped.txt", 144 | friends_file="friends.txt", 145 | proxy=None, 146 | max_likes_per_day=random.randint(50, 100), 147 | max_unlikes_per_day=random.randint(50, 100), 148 | max_follows_per_day=random.randint(50, 100), 149 | max_unfollows_per_day=random.randint(50, 100), 150 | max_comments_per_day=random.randint(50, 100), 151 | max_blocks_per_day=random.randint(50, 100), 152 | max_unblocks_per_day=random.randint(50, 100), 153 | max_likes_to_like=random.randint(50, 100), 154 | min_likes_to_like=random.randint(50, 100), 155 | max_messages_per_day=random.randint(50, 100), 156 | filter_users=False, 157 | filter_private_users=False, 158 | filter_users_without_profile_photo=False, 159 | filter_previously_followed=False, 160 | filter_business_accounts=False, 161 | filter_verified_accounts=False, 162 | max_followers_to_follow=5000, 163 | min_followers_to_follow=10, 164 | max_following_to_follow=2000, 165 | min_following_to_follow=10, 166 | max_followers_to_following_ratio=15, 167 | max_following_to_followers_ratio=15, 168 | min_media_count_to_follow=3, 169 | max_following_to_block=2000, 170 | like_delay=random.randint(300, 600), 171 | unlike_delay=random.randint(300, 600), 172 | follow_delay=random.randint(300, 600), 173 | unfollow_delay=random.randint(300, 600), 174 | comment_delay=random.randint(300, 600), 175 | block_delay=random.randint(300, 600), 176 | unblock_delay=random.randint(300, 600), 177 | message_delay=random.randint(300, 600), 178 | stop_words=("shop", "store", "free"), 179 | blacklist_hashtags=["#shop", "#store", "#free"], 180 | blocked_actions_protection=True, 181 | blocked_actions_sleep=True, 182 | blocked_actions_sleep_delay=random.randint(600, 1200), 183 | verbosity=True, 184 | device=None, 185 | save_logfile=True, 186 | log_filename=None, 187 | loglevel_file=logging.DEBUG, 188 | loglevel_stream=logging.INFO, 189 | log_follow_unfollow=True, 190 | ): 191 | self.api = API( 192 | device=device, 193 | base_path=base_path, 194 | save_logfile=save_logfile, 195 | log_filename=log_filename, 196 | loglevel_file=loglevel_file, 197 | loglevel_stream=loglevel_stream, 198 | ) 199 | self.log_follow_unfollow = log_follow_unfollow 200 | self.base_path = base_path 201 | 202 | self.state = BotState() 203 | 204 | self.delays = { 205 | "like": like_delay, 206 | "unlike": unlike_delay, 207 | "follow": follow_delay, 208 | "unfollow": unfollow_delay, 209 | "comment": comment_delay, 210 | "block": block_delay, 211 | "unblock": unblock_delay, 212 | "message": message_delay, 213 | } 214 | 215 | # limits - follow 216 | self.filter_users = filter_users 217 | self.filter_private_users = filter_private_users 218 | self.filter_users_without_profile_photo = filter_users_without_profile_photo 219 | self.filter_business_accounts = filter_business_accounts 220 | self.filter_verified_accounts = filter_verified_accounts 221 | self.filter_previously_followed = filter_previously_followed 222 | 223 | self.max_per_day = { 224 | "likes": max_likes_per_day, 225 | "unlikes": max_unlikes_per_day, 226 | "follows": max_follows_per_day, 227 | "unfollows": max_unfollows_per_day, 228 | "comments": max_comments_per_day, 229 | "blocks": max_blocks_per_day, 230 | "unblocks": max_unblocks_per_day, 231 | "messages": max_messages_per_day, 232 | } 233 | 234 | self.blocked_actions_protection = blocked_actions_protection 235 | 236 | self.blocked_actions_sleep = blocked_actions_sleep 237 | self.blocked_actions_sleep_delay = blocked_actions_sleep_delay 238 | 239 | self.max_likes_to_like = max_likes_to_like 240 | self.min_likes_to_like = min_likes_to_like 241 | self.max_followers_to_follow = max_followers_to_follow 242 | self.min_followers_to_follow = min_followers_to_follow 243 | self.max_following_to_follow = max_following_to_follow 244 | self.min_following_to_follow = min_following_to_follow 245 | self.max_followers_to_following_ratio = max_followers_to_following_ratio 246 | self.max_following_to_followers_ratio = max_following_to_followers_ratio 247 | self.min_media_count_to_follow = min_media_count_to_follow 248 | self.stop_words = stop_words 249 | self.blacklist_hashtags = blacklist_hashtags 250 | 251 | # limits - block 252 | self.max_following_to_block = max_following_to_block 253 | 254 | # current following and followers 255 | self.cache = BotCache() 256 | 257 | # Adjust file paths 258 | followed_file = os.path.join(base_path, followed_file) 259 | unfollowed_file = os.path.join(base_path, unfollowed_file) 260 | skipped_file = os.path.join(base_path, skipped_file) 261 | friends_file = os.path.join(base_path, friends_file) 262 | comments_file = os.path.join(base_path, comments_file) 263 | blacklist_file = os.path.join(base_path, blacklist_file) 264 | whitelist_file = os.path.join(base_path, whitelist_file) 265 | 266 | # Database files 267 | self.followed_file = utils.file(followed_file) 268 | self.unfollowed_file = utils.file(unfollowed_file) 269 | self.skipped_file = utils.file(skipped_file) 270 | self.friends_file = utils.file(friends_file) 271 | self.comments_file = utils.file(comments_file) 272 | self.blacklist_file = utils.file(blacklist_file) 273 | self.whitelist_file = utils.file(whitelist_file) 274 | 275 | self.proxy = proxy 276 | self.verbosity = verbosity 277 | 278 | self.logger = self.api.logger 279 | self.logger.info("Instabot version: " + version + " Started") 280 | self.logger.debug("Bot imported from {}".format(__file__)) 281 | 282 | @property 283 | def user_id(self): 284 | # For compatibility 285 | return self.api.user_id 286 | 287 | @property 288 | def username(self): 289 | # For compatibility 290 | return self.api.username 291 | 292 | @property 293 | def password(self): 294 | # For compatibility 295 | return self.api.password 296 | 297 | @property 298 | def last_json(self): 299 | # For compatibility 300 | return self.api.last_json 301 | 302 | @property 303 | def blacklist(self): 304 | # This is a fast operation because 305 | # `get_user_id_from_username` is cached. 306 | return [ 307 | self.convert_to_user_id(i) 308 | for i in self.blacklist_file.list 309 | if i is not None 310 | ] 311 | 312 | @property 313 | def whitelist(self): 314 | # This is a fast operation because 315 | # `get_user_id_from_username` is cached. 316 | return [ 317 | self.convert_to_user_id(i) 318 | for i in self.whitelist_file.list 319 | if i is not None 320 | ] 321 | 322 | @property 323 | def following(self): 324 | now = time.time() 325 | last = self.last.get("updated_following", now) 326 | if self._following is None or (now - last) > 7200: 327 | self.console_print("`bot.following` is empty, will download.", "green") 328 | self._following = self.get_user_following(self.user_id) 329 | self.last["updated_following"] = now 330 | return self._following 331 | 332 | @property 333 | def followers(self): 334 | now = time.time() 335 | last = self.last.get("updated_followers", now) 336 | if self._followers is None or (now - last) > 7200: 337 | self.console_print("`bot.followers` is empty, will download.", "green") 338 | self._followers = self.get_user_followers(self.user_id) 339 | self.last["updated_followers"] = now 340 | return self._followers 341 | 342 | @property 343 | def start_time(self): 344 | return self.state.start_time 345 | 346 | @start_time.setter 347 | def start_time(self, value): 348 | self.state.start_time = value 349 | 350 | @property 351 | def total(self): 352 | return self.state.total 353 | 354 | @total.setter 355 | def total(self, value): 356 | self.state.total = value 357 | 358 | @property 359 | def sleeping_actions(self): 360 | return self.state.sleeping_actions 361 | 362 | @sleeping_actions.setter 363 | def sleeping_actions(self, value): 364 | self.state.sleeping_actions = value 365 | 366 | @property 367 | def blocked_actions(self): 368 | return self.state.blocked_actions 369 | 370 | @blocked_actions.setter 371 | def blocked_actions(self, value): 372 | self.state.blocked_actions = value 373 | 374 | @property 375 | def last(self): 376 | return self.state.last 377 | 378 | @last.setter 379 | def last(self, value): 380 | self.state.last = value 381 | 382 | @property 383 | def _following(self): 384 | return self.cache.following 385 | 386 | @_following.setter 387 | def _following(self, value): 388 | self.cache.following = value 389 | 390 | @property 391 | def _followers(self): 392 | return self.cache.followers 393 | 394 | @_followers.setter 395 | def _followers(self, value): 396 | self.cache.followers = value 397 | 398 | @property 399 | def _user_infos(self): 400 | return self.cache.user_infos 401 | 402 | @_user_infos.setter 403 | def _user_infos(self, value): 404 | self.cache.user_infos = value 405 | 406 | @property 407 | def _usernames(self): 408 | return self.cache.usernames 409 | 410 | @_usernames.setter 411 | def _usernames(self, value): 412 | self.cache.usernames = value 413 | 414 | @staticmethod 415 | def version(): 416 | try: 417 | from pip._vendor import pkg_resources 418 | except ImportError: 419 | import pkg_resources 420 | return next( 421 | ( 422 | p.version 423 | for p in pkg_resources.working_set 424 | if p.project_name.lower() == "instabot" 425 | ), 426 | "No match", 427 | ) 428 | 429 | def logout(self, *args, **kwargs): 430 | self.api.logout() 431 | self.logger.info( 432 | "Bot stopped. " "Worked: %s", datetime.datetime.now() - self.start_time 433 | ) 434 | self.print_counters() 435 | 436 | def login(self, **args): 437 | """if login function is run threaded, for example in scheduled job, 438 | signal will fail because it 'only works in main thread'. 439 | In this case, you may want to call login(is_threaded=True). 440 | """ 441 | if self.proxy: 442 | args["proxy"] = self.proxy 443 | if self.api.login(**args) is False: 444 | return False 445 | self.prepare() 446 | atexit.register(self.print_counters) 447 | if "is_threaded" in args: 448 | if args["is_threaded"]: 449 | return True 450 | signal.signal(signal.SIGTERM, self.print_counters) 451 | return True 452 | 453 | def prepare(self): 454 | storage = load_checkpoint(self) 455 | if storage is not None: 456 | ( 457 | total, 458 | self.blocked_actions, 459 | self.api.total_requests, 460 | self.start_time, 461 | ) = storage 462 | 463 | for k, v in total.items(): 464 | self.total[k] = v 465 | 466 | def print_counters(self, *args, **kwargs): 467 | save_checkpoint(self) 468 | for key, val in self.total.items(): 469 | if val > 0: 470 | self.logger.info( 471 | "Total {}: {}{}".format( 472 | key, 473 | val, 474 | "/" + str(self.max_per_day[key]) 475 | if self.max_per_day.get(key) 476 | else "", 477 | ) 478 | ) 479 | for key, val in self.blocked_actions.items(): 480 | if val: 481 | self.logger.info("Blocked {}".format(key)) 482 | self.logger.info("Total requests: {}".format(self.api.total_requests)) 483 | 484 | def delay(self, key): 485 | """ 486 | Sleep only if elapsed time since 487 | `self.last[key]` < `self.delay[key]`. 488 | """ 489 | last_action, target_delay = self.last[key], self.delays[key] 490 | elapsed_time = time.time() - last_action 491 | if elapsed_time < target_delay: 492 | t_remaining = target_delay - elapsed_time 493 | time.sleep(t_remaining * random.uniform(0.25, 1.25)) 494 | self.last[key] = time.time() 495 | 496 | def error_delay(self): 497 | time.sleep(10) 498 | 499 | def small_delay(self): 500 | time.sleep(random.uniform(0.75, 3.75)) 501 | 502 | def very_small_delay(self): 503 | time.sleep(random.uniform(0.175, 0.875)) 504 | 505 | def reached_limit(self, key): 506 | current_date = datetime.datetime.now() 507 | passed_days = (current_date.date() - self.start_time.date()).days 508 | if passed_days > 0: 509 | self.reset_counters() 510 | return self.max_per_day[key] - self.total[key] <= 0 511 | 512 | def reset_counters(self): 513 | for k in self.total: 514 | self.total[k] = 0 515 | for k in self.blocked_actions: 516 | self.blocked_actions[k] = False 517 | self.start_time = datetime.datetime.now() 518 | 519 | def reset_cache(self): 520 | self._following = None 521 | self._followers = None 522 | self._user_infos = {} 523 | self._usernames = {} 524 | 525 | # getters 526 | def get_user_stories(self, user_id): 527 | """ 528 | Returns array of stories links 529 | """ 530 | return get_user_stories(self, user_id) 531 | 532 | def get_user_reel(self, user_id): 533 | return get_user_reel(self, user_id) 534 | 535 | def get_self_story_viewers(self, story_id): 536 | return get_self_story_viewers(self, story_id) 537 | 538 | def get_pending_follow_requests(self): 539 | return get_pending_follow_requests(self) 540 | 541 | def get_your_medias(self, as_dict=False): 542 | """ 543 | Returns your media ids. With parameter 544 | as_dict=True returns media as dict. 545 | :type as_dict: bool 546 | """ 547 | return get_your_medias(self, as_dict) 548 | 549 | def get_archived_medias(self, as_dict=False): 550 | """ 551 | Returns your archived media ids. With parameter 552 | as_dict=True returns media as dict. 553 | :type as_dict: bool 554 | """ 555 | return get_archived_medias(self, as_dict) 556 | 557 | def get_timeline_medias(self): 558 | return get_timeline_medias(self) 559 | 560 | def get_popular_medias(self): 561 | return get_popular_medias(self) 562 | 563 | def get_user_medias(self, user_id, filtration=True, is_comment=False): 564 | return get_user_medias(self, user_id, filtration, is_comment) 565 | 566 | def get_total_user_medias(self, user_id): 567 | return get_total_user_medias(self, user_id) 568 | 569 | def get_last_user_medias(self, user_id, count): 570 | """ 571 | Returns the last number of posts specified in count in media ids array. 572 | :type count: int 573 | :param count: Count of posts 574 | :return: array 575 | """ 576 | return get_last_user_medias(self, user_id, count) 577 | 578 | def get_hashtag_medias(self, hashtag, filtration=True): 579 | return get_hashtag_medias(self, hashtag, filtration) 580 | 581 | def get_total_hashtag_medias(self, hashtag, amount=100, filtration=False): 582 | return get_total_hashtag_medias(self, hashtag, amount, filtration) 583 | 584 | def get_geotag_medias(self, geotag, filtration=True): 585 | return get_geotag_medias(self, geotag, filtration) 586 | 587 | def get_locations_from_coordinates(self, latitude, longitude): 588 | return get_locations_from_coordinates(self, latitude, longitude) 589 | 590 | def get_media_info(self, media_id): 591 | return get_media_info(self, media_id) 592 | 593 | def get_timeline_users(self): 594 | return get_timeline_users(self) 595 | 596 | def get_hashtag_users(self, hashtag): 597 | return get_hashtag_users(self, hashtag) 598 | 599 | def get_geotag_users(self, geotag): 600 | return get_geotag_users(self, geotag) 601 | 602 | def get_user_id_from_username(self, username): 603 | return get_user_id_from_username(self, username) 604 | 605 | def get_user_tags_medias(self, user_id): 606 | return get_user_tags_medias(self, user_id) 607 | 608 | def get_username_from_user_id(self, user_id): 609 | return get_username_from_user_id(self, user_id) 610 | 611 | def get_user_info(self, user_id, use_cache=True): 612 | return get_user_info(self, user_id, use_cache) 613 | 614 | def get_user_followers(self, user_id, nfollows=None): 615 | return get_user_followers(self, user_id, nfollows) 616 | 617 | def get_user_following(self, user_id, nfollows=None): 618 | return get_user_following(self, user_id, nfollows) 619 | 620 | def get_comment_likers(self, comment_id): 621 | return get_comment_likers(self, comment_id) 622 | 623 | def get_media_likers(self, media_id): 624 | return get_media_likers(self, media_id) 625 | 626 | def get_media_comments(self, media_id, only_text=False): 627 | return get_media_comments(self, media_id, only_text) 628 | 629 | def get_media_comments_all(self, media_id, only_text=False, count=False): 630 | return get_media_comments_all(self, media_id, only_text, count) 631 | 632 | def get_comment(self): 633 | return get_comment(self) 634 | 635 | def get_media_commenters(self, media_id): 636 | return get_media_commenters(self, media_id) 637 | 638 | def get_media_owner(self, media): 639 | return get_media_owner(self, media) 640 | 641 | def get_user_likers(self, user_id, media_count=10): 642 | return get_user_likers(self, user_id, media_count) 643 | 644 | def get_media_id_from_link(self, link): 645 | return get_media_id_from_link(self, link) 646 | 647 | def get_link_from_media_id(self, link): 648 | return get_link_from_media_id(self, link) 649 | 650 | def get_messages(self): 651 | return get_messages(self) 652 | 653 | def search_users(self, query): 654 | return search_users(self, query) 655 | 656 | def get_muted_friends(self, muted_content="stories"): 657 | return get_muted_friends(self, muted_content) 658 | 659 | def convert_to_user_id(self, usernames): 660 | return convert_to_user_id(self, usernames) 661 | 662 | def get_pending_thread_requests(self): 663 | return get_pending_thread_requests(self) 664 | 665 | # like 666 | 667 | def like( 668 | self, 669 | media_id, 670 | check_media=True, 671 | container_module="feed_short_url", 672 | feed_position=0, 673 | username=None, 674 | user_id=None, 675 | hashtag_name=None, 676 | hashtag_id=None, 677 | entity_page_name=None, 678 | entity_page_id=None, 679 | ): 680 | 681 | return like( 682 | self, 683 | media_id, 684 | check_media, 685 | container_module=container_module, 686 | feed_position=feed_position, 687 | username=username, 688 | user_id=user_id, 689 | hashtag_name=hashtag_name, 690 | hashtag_id=hashtag_id, 691 | entity_page_name=entity_page_name, 692 | entity_page_id=entity_page_id, 693 | ) 694 | 695 | def like_comment(self, comment_id): 696 | return like_comment(self, comment_id) 697 | 698 | def like_medias( 699 | self, 700 | media_ids, 701 | check_media=True, 702 | container_module="feed_timeline", 703 | username=None, 704 | user_id=None, 705 | hashtag_name=None, 706 | hashtag_id=None, 707 | entity_page_name=None, 708 | entity_page_id=None, 709 | ): 710 | 711 | return like_medias( 712 | self, 713 | media_ids, 714 | check_media, 715 | container_module=container_module, 716 | username=username, 717 | user_id=user_id, 718 | hashtag_name=hashtag_name, 719 | hashtag_id=hashtag_id, 720 | entity_page_name=entity_page_name, 721 | entity_page_id=entity_page_id, 722 | ) 723 | 724 | def like_timeline(self, amount=None): 725 | return like_timeline(self, amount) 726 | 727 | def like_media_comments(self, media_id): 728 | return like_media_comments(self, media_id) 729 | 730 | def like_user(self, user_id, amount=None, filtration=True): 731 | return like_user(self, user_id, amount, filtration) 732 | 733 | def like_hashtag(self, hashtag, amount=None): 734 | return like_hashtag(self, hashtag, amount) 735 | 736 | def like_geotag(self, geotag, amount=None): 737 | return like_geotag(self, geotag, amount) 738 | 739 | def like_users(self, user_ids, nlikes=None, filtration=True): 740 | return like_users(self, user_ids, nlikes, filtration) 741 | 742 | def like_location_feed(self, place, amount): 743 | return like_location_feed(self, place, amount) 744 | 745 | def like_followers(self, user_id, nlikes=None, nfollows=None): 746 | return like_followers(self, user_id, nlikes, nfollows) 747 | 748 | def like_following(self, user_id, nlikes=None, nfollows=None): 749 | return like_following(self, user_id, nlikes, nfollows) 750 | 751 | # unlike 752 | 753 | def unlike(self, media_id): 754 | return unlike(self, media_id) 755 | 756 | def unlike_comment(self, comment_id): 757 | return unlike_comment(self, comment_id) 758 | 759 | def unlike_media_comments(self, media_id): 760 | return unlike_media_comments(self, media_id) 761 | 762 | def unlike_medias(self, media_ids): 763 | return unlike_medias(self, media_ids) 764 | 765 | def unlike_user(self, user): 766 | return unlike_user(self, user) 767 | 768 | # story 769 | def download_stories(self, username): 770 | return download_stories(self, username) 771 | 772 | def upload_story_photo(self, photo, upload_id=None): 773 | return upload_story_photo(self, photo, upload_id) 774 | 775 | def watch_users_reels(self, user_ids, max_users=100): 776 | return watch_users_reels(self, user_ids, max_users=max_users) 777 | 778 | # photo 779 | def download_photo( 780 | self, media_id, folder="photos", filename=None, save_description=False 781 | ): 782 | return download_photo(self, media_id, folder, filename, save_description) 783 | 784 | def download_photos(self, medias, folder="photos", save_description=False): 785 | return download_photos(self, medias, folder, save_description) 786 | 787 | def upload_photo( 788 | self, photo, caption=None, upload_id=None, from_video=False, options={} 789 | ): 790 | """Upload photo to Instagram 791 | @param photo Path to photo file (String) 792 | @param caption Media description (String) 793 | @param upload_id Unique upload_id (String). When None, then 794 | generate automatically 795 | @param from_video A flag that signals whether the photo is loaded 796 | from the video or by itself 797 | (Boolean, DEPRECATED: not used) 798 | @param options Object with difference options, 799 | e.g. configure_timeout, rename (Dict) 800 | Designed to reduce the number of function 801 | arguments! This is the simplest request object. 802 | 803 | @return Object with state of uploading to 804 | Instagram (or False) 805 | """ 806 | return upload_photo(self, photo, caption, upload_id, from_video, options) 807 | 808 | # video 809 | def upload_video(self, video, caption="", thumbnail=None, options={}): 810 | """Upload video to Instagram 811 | 812 | @param video Path to video file (String) 813 | @param caption Media description (String) 814 | @param thumbnail Path to thumbnail for video (String). When None, 815 | then thumbnail is generated automatically 816 | @param options Object with difference options, e.g. 817 | configure_timeout, rename_thumbnail, rename (Dict) 818 | Designed to reduce the number of function arguments! 819 | 820 | @return Object with Instagram upload state (or False) 821 | """ 822 | return upload_video(self, video, caption, thumbnail, options) 823 | 824 | def download_video( 825 | self, media_id, folder="videos", filename=None, save_description=False 826 | ): 827 | return download_video(self, media_id, folder, filename, save_description) 828 | 829 | # follow 830 | def follow(self, user_id, check_user=True): 831 | return follow(self, user_id, check_user) 832 | 833 | def follow_users(self, user_ids, nfollows=None): 834 | return follow_users(self, user_ids, nfollows) 835 | 836 | def follow_followers(self, user_id, nfollows=None): 837 | return follow_followers(self, user_id, nfollows) 838 | 839 | def follow_following(self, user_id, nfollows=None): 840 | return follow_following(self, user_id, nfollows) 841 | 842 | # unfollow 843 | def unfollow(self, user_id): 844 | return unfollow(self, user_id) 845 | 846 | def unfollow_users(self, user_ids): 847 | return unfollow_users(self, user_ids) 848 | 849 | def unfollow_non_followers(self, n_to_unfollows=None): 850 | return unfollow_non_followers(self, n_to_unfollows) 851 | 852 | def unfollow_everyone(self): 853 | return unfollow_everyone(self) 854 | 855 | def approve_pending_follow_requests(self): 856 | return approve_pending_follow_requests(self) 857 | 858 | def reject_pending_follow_requests(self): 859 | return reject_pending_follow_requests(self) 860 | 861 | # direct 862 | def send_message(self, text, user_ids, thread_id=None): 863 | return send_message(self, text, user_ids, thread_id) 864 | 865 | def send_messages(self, text, user_ids): 866 | return send_messages(self, text, user_ids) 867 | 868 | def send_media(self, media_id, user_ids, text=None, thread_id=None): 869 | return send_media(self, media_id, user_ids, text, thread_id) 870 | 871 | def send_medias(self, media_id, user_ids, text=None): 872 | return send_medias(self, media_id, user_ids, text) 873 | 874 | def send_hashtag(self, hashtag, user_ids, text="", thread_id=None): 875 | return send_hashtag(self, hashtag, user_ids, text, thread_id) 876 | 877 | def send_profile(self, profile_user_id, user_ids, text="", thread_id=None): 878 | return send_profile(self, profile_user_id, user_ids, text, thread_id) 879 | 880 | def send_like(self, user_ids, thread_id=None): 881 | return send_like(self, user_ids, thread_id) 882 | 883 | def send_photo(self, user_ids, filepath, thread_id=None): 884 | return send_photo(self, user_ids, filepath, thread_id) 885 | 886 | def approve_pending_thread_requests(self): 887 | return approve_pending_thread_requests(self) 888 | 889 | # delete 890 | def delete_media(self, media_id): 891 | return delete_media(self, media_id) 892 | 893 | def delete_medias(self, medias): 894 | return delete_medias(self, medias) 895 | 896 | def delete_comment(self, media_id, comment_id): 897 | return delete_comment(self, media_id, comment_id) 898 | 899 | # archive 900 | def archive(self, media_id, undo=False): 901 | return archive(self, media_id, undo) 902 | 903 | def unarchive(self, media_id): 904 | return archive(self, media_id, True) 905 | 906 | def archive_medias(self, medias): 907 | return archive_medias(self, medias) 908 | 909 | def unarchive_medias(self, medias): 910 | return unarchive_medias(self, medias) 911 | 912 | # comment 913 | def comment(self, media_id, comment_text): 914 | return comment(self, media_id, comment_text) 915 | 916 | def reply_to_comment(self, media_id, comment_text, parent_comment_id): 917 | return reply_to_comment(self, media_id, comment_text, parent_comment_id) 918 | 919 | def comment_hashtag(self, hashtag, amount=None): 920 | return comment_hashtag(self, hashtag, amount) 921 | 922 | def comment_medias(self, medias): 923 | return comment_medias(self, medias) 924 | 925 | def comment_user(self, user_id, amount=None): 926 | return comment_user(self, user_id, amount) 927 | 928 | def comment_users(self, user_ids, ncomments=None): 929 | return comment_users(self, user_ids, ncomments) 930 | 931 | def comment_geotag(self, geotag): 932 | return comment_geotag(self, geotag) 933 | 934 | def is_commented(self, media_id): 935 | return is_commented(self, media_id) 936 | 937 | # block 938 | def block(self, user_id): 939 | return block(self, user_id) 940 | 941 | def unblock(self, user_id): 942 | return unblock(self, user_id) 943 | 944 | def block_users(self, user_ids): 945 | return block_users(self, user_ids) 946 | 947 | def unblock_users(self, user_ids): 948 | return unblock_users(self, user_ids) 949 | 950 | def block_bots(self): 951 | return block_bots(self) 952 | 953 | def remove_follower(self, user_id): 954 | return remove_follower(self, user_id) 955 | 956 | def check_if_replaced(self): 957 | check_if_replaced(self) 958 | 959 | # filter 960 | def filter_medias( 961 | self, media_items, filtration=True, quiet=False, is_comment=False 962 | ): 963 | return filter_medias(self, media_items, filtration, quiet, is_comment) 964 | 965 | def check_media(self, media): 966 | return check_media(self, media) 967 | 968 | def check_user(self, user, unfollowing=False): 969 | return check_user(self, user, unfollowing) 970 | 971 | def check_not_bot(self, user): 972 | return check_not_bot(self, user) 973 | 974 | # support 975 | def check_if_file_exists(self, file_path, quiet=False): 976 | return check_if_file_exists(file_path, quiet) 977 | 978 | def extract_urls(self, text): 979 | return extract_urls(text) 980 | 981 | def read_list_from_file(self, file_path): 982 | return read_list_from_file(file_path) 983 | 984 | def console_print(self, text, color=None): 985 | return console_print(self, text, color) 986 | 987 | # stats 988 | def save_user_stats(self, username, path=""): 989 | return save_user_stats(self, username, path=path) 990 | --------------------------------------------------------------------------------