├── .DS_Store ├── Readme.md ├── examples ├── .DS_Store ├── Dockerfile ├── account_login.py ├── accounts.txt ├── api_broker.py ├── bot_handler.py ├── bot_wrapper.py ├── messaging_service.py ├── messaging_service │ ├── .DS_Store │ ├── messaged.txt │ ├── messages_db.csv │ └── skipped.txt ├── sessions │ ├── .DS_Store │ ├── logs │ │ └── .DS_Store │ ├── ready_for_messaging │ │ └── .DS_Store │ └── removed │ │ └── .DS_Store ├── simulator.py └── webservice.py ├── ig_api ├── .DS_Store ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-39.pyc │ ├── _app.cpython-39.pyc │ ├── _direct.cpython-39.pyc │ ├── _feed.cpython-39.pyc │ ├── _logging.cpython-39.pyc │ ├── _logging_client_events.cpython-39.pyc │ ├── _login.cpython-39.pyc │ ├── _media.cpython-39.pyc │ ├── _misc.cpython-39.pyc │ ├── _news.cpython-39.pyc │ ├── _reels.cpython-39.pyc │ ├── _requests.cpython-39.pyc │ ├── _search.cpython-39.pyc │ ├── _session.cpython-39.pyc │ ├── _user.cpython-39.pyc │ ├── constants.cpython-39.pyc │ └── feed.cpython-39.pyc ├── _app.py ├── _direct.py ├── _feed.py ├── _logging.py ├── _logging_client_events.py ├── _login.py ├── _media.py ├── _misc.py ├── _news.py ├── _reels.py ├── _requests.py ├── _search.py ├── _session.py ├── _user.py └── constants.py ├── requirements.txt └── setup.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/.DS_Store -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # ig_api 2 | ##### instagram 1.97 / ios api wrapper 3 | 4 | After 2 yrs of experience in instagram automation, i am putting my code, that i used open source. This is a basic api wrapper i used for automation. In the examples section you'll find a production ready bot-handler, that will handle unlimited bots. We had upto 200 accounts running on one instance. To use the scripts properly you'll have to get a proper accounts setup. While I could write a whole book about the right account setup here are a few tips. 5 | 6 | 1. Good proxy (never use more than five accounts on one proxy except if the proxy is really good) 7 | 2. Last location of login has to match the proxies location 8 | 3. Phone-Id, Phone have to be ideally the same, also the App-Version has to match 9 | 4. Do some manual actions, to build up your trust-score, which is connected to your device-id 10 | 11 | In the ideal case you fulfill all 4 requirements. In that case, the risk of getting your account banned is low (if you do not do crazy things - like 2000 messages/day) 12 | 13 | 14 | # How to use: 15 | 16 | install the package: 17 | ```bash 18 | python3 setup.py install 19 | ``` 20 | 21 | create a new python file and send a message to a specific user. 22 | ```python 23 | api = ig_api("username", "password", "proxy") 24 | api.startup_app() 25 | api.send_message_to_username("username", "text") 26 | ``` 27 | 28 | # Example 29 | 30 | In the examples folder you'll find our setup we ran with over 200 accounts. To test this you'll have to login the accounts first. You'll have to create sessions, that can later be used by the bot_handler.py. To login accounts, you'll have to write the account information into the accounts.txt. I will go into further detail about this, since i requires much more experience to run a bigger instance. -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/.DS_Store -------------------------------------------------------------------------------- /examples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | WORKDIR /opt/app 3 | COPY . /opt/app 4 | COPY requirements.txt /tmp/ 5 | RUN pip install --requirement /tmp/requirements.txt -------------------------------------------------------------------------------- /examples/account_login.py: -------------------------------------------------------------------------------- 1 | from ig_api import ig_api 2 | import uuid 3 | 4 | 5 | def convert_to_valid_uuid(uuid_test): 6 | try: 7 | uuid_obj = uuid.UUID(uuid_test, version=4) 8 | except ValueError: 9 | return False 10 | return str(uuid_obj) 11 | 12 | 13 | verified_users = [] 14 | errors = [] 15 | 16 | print("Paste in account file:") 17 | account_file = open(input(), "r") 18 | lines = account_file.readlines() 19 | 20 | for line in lines: 21 | line = line.split(",") 22 | user = line[0] 23 | password = line[1] 24 | device_id = line[2] 25 | proxy = line[3].replace('\n', '').replace('\r', '') 26 | 27 | login_bot = ig_api(user, password, proxy) 28 | 29 | if login_bot.startup_app(device_id=convert_to_valid_uuid(device_id)): 30 | verified_users.append(user) 31 | 32 | print("Verified user:") 33 | print(verified_users) 34 | print("Failed:") 35 | print(errors) 36 | -------------------------------------------------------------------------------- /examples/accounts.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/accounts.txt -------------------------------------------------------------------------------- /examples/api_broker.py: -------------------------------------------------------------------------------- 1 | from webservice import create_server_handler 2 | from bot_handler import bot_handler 3 | from datetime import datetime 4 | from messaging_service import messaging_service 5 | import socketserver 6 | import json 7 | import threading 8 | 9 | 10 | class api_broker: 11 | 12 | def initiate_webserver(self): 13 | print("{time} - Starting webservice ...".format(time=datetime.now().strftime("%d.%m.%Y, %H:%M:%S"))) 14 | self.serverHandler = create_server_handler(self) 15 | self.httpd = socketserver.TCPServer(("", 8083), self.serverHandler) 16 | self.httpd.serve_forever() 17 | 18 | def get_bot_handler_bots(self): 19 | response = [] 20 | for bot in self.bot_handler.bots: 21 | response.append({ 22 | "id": bot.bot_id, 23 | "username": bot.username, 24 | "proxy": bot.proxy, 25 | "status": bot.api.status, 26 | "messages_sent": bot.messages_sent, 27 | "shifts_skipped": bot.shifts_skipped, 28 | "device_id": bot.api.device_id 29 | }) 30 | 31 | return json.dumps(response) 32 | 33 | def run_threaded(self, job_function, args): 34 | job_thread = threading.Thread(target=job_function, args=args) 35 | job_thread.start() 36 | 37 | def __init__(self): 38 | self.bot_handler = bot_handler(self, { 39 | "start_with_messaging_service": True, 40 | "start_with_warmup_service": False 41 | }) 42 | 43 | self.messaging_service = messaging_service(self.bot_handler) 44 | self.run_threaded(self.initiate_webserver, ()) 45 | self.run_threaded(self.bot_handler.initiate_bot_handler, ()) 46 | 47 | 48 | api_broker() 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/bot_handler.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | import time 3 | import json 4 | import threading 5 | import schedule 6 | from bot_wrapper import bot_wrapper 7 | from datetime import datetime 8 | 9 | class bot_handler: 10 | 11 | def run_schedule_service(self): 12 | while True: 13 | schedule.run_pending() 14 | time.sleep(1) 15 | 16 | def session_active(self, session_file): 17 | for bot in self.bots: 18 | if bot.session_file == session_file: 19 | return True 20 | return False 21 | 22 | def refresh_sessions(self): 23 | print("[BOTHANDLER][{}] refresh sessions".format(datetime.now().strftime("%d.%m.%Y, %H:%M:%S"))) 24 | remove_array = [] 25 | for bot in self.bots: 26 | if not bot.api.logged_in: 27 | try: 28 | bot.api.delete_session() 29 | except Exception as e: 30 | print("[BOTHANDLER] error occured, while deleting") 31 | print(e) 32 | remove_array.append(bot) 33 | 34 | for bot in remove_array: 35 | self.bots.remove(bot) 36 | 37 | session_files = glob("sessions/*.json") 38 | 39 | for session_file in session_files: 40 | if not self.session_active(session_file): 41 | self.run_threaded(self.start_session_from_session_file, (session_file,)) 42 | # we do a little time sleep to prevent same proxy detection 43 | time.sleep(5) 44 | 45 | def start_session_from_session_file(self, session_file_path): 46 | print("[BOTHANDLER] added session from " + session_file_path) 47 | with open(session_file_path, "r") as session_file: 48 | session_json = json.loads(session_file.read()) 49 | username = session_json["username"] 50 | password = session_json["password"] 51 | proxy = session_json["proxy"] 52 | self.bots.append(bot_wrapper(self, username, password, proxy, self.options)) 53 | 54 | def get(self, id): 55 | for bot in self.bots: 56 | if bot.bot_id == id: 57 | return bot 58 | return None 59 | 60 | 61 | def run_threaded(self, job_function, args): 62 | job_thread = threading.Thread(target=job_function, args=args) 63 | job_thread.start() 64 | 65 | def initiate_bot_handler(self): 66 | self.run_threaded(self.refresh_sessions, ()) 67 | schedule.every().hour.do(lambda: self.run_threaded(self.refresh_sessions, ())) 68 | self.run_threaded(self.run_schedule_service, ()) 69 | 70 | def __init__(self, api_broker, options): 71 | self.bots = [] 72 | self.options = options 73 | self.api_broker = api_broker 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/bot_wrapper.py: -------------------------------------------------------------------------------- 1 | from ig_api import ig_api 2 | import uuid 3 | import time 4 | import json 5 | import threading 6 | import random 7 | import schedule 8 | from simulator import simulator 9 | 10 | class bot_wrapper: 11 | 12 | def log(self, log_text): 13 | self.api.log(log_text) 14 | 15 | def messaging_shift(self): 16 | self.log("[BOT][{}] performing messaging shift".format(self.username)) 17 | self.api.startup_app() 18 | time.sleep(random.randint(3, 30)) 19 | 20 | self.messages = self.messaging_service.get_messages(amount=random.randint(self.messages_per_shift_min, self.messages_per_shift_max)) 21 | self.send_messages() 22 | 23 | def perform_random_actions(self): 24 | 25 | if random.random() < 0.1: 26 | self.simulator.like_random_from_timeline() 27 | 28 | if random.random() < 0.1: 29 | self.api.pull_to_refresh() 30 | time.sleep(random.randint(5, 15)) 31 | 32 | if random.random() < 0.03: 33 | self.simulator.simulate_viewing_reels() 34 | time.sleep(random.randint(5, 15)) 35 | 36 | if random.random() < 0.1: 37 | self.simulator.simulate_scrolling() 38 | time.sleep(random.randint(5, 15)) 39 | 40 | if random.random() < 0.05: 41 | self.simulator.simulate_random_search() 42 | time.sleep(random.randint(5, 15)) 43 | 44 | def send_messages(self): 45 | try: 46 | for message in self.messages: 47 | self.perform_random_actions() 48 | if self.api.logged_in and self.api.status != "feedback_required": 49 | user_id = message[0] 50 | username = message[1] 51 | text = message[2] 52 | 53 | if self.send_message(user_id, username, text): 54 | self.messaging_service.mark_messaged(user_id) 55 | self.messages_sent += 1 56 | else: 57 | self.messaging_service.mark_skipped(user_id) 58 | 59 | time.sleep(random.randint(50, 180)) 60 | 61 | if self.api.status == "feedback_required": 62 | wait_shifts = 3 63 | if self.shifts_skipped >= wait_shifts: 64 | self.api.status = "valid" 65 | self.shifts_skipped = 0 66 | 67 | self.shifts_skipped += 1 68 | self.warm_up_shift() 69 | 70 | 71 | except Exception as e: 72 | self.log(e) 73 | # pass 74 | 75 | self.messages = [] 76 | 77 | def send_message(self, user_id, username, text): 78 | if self.simulator.check_simulate_search_user(user_id, username): 79 | self.api.get_search_dynamic_sections() 80 | self.simulator.simulate_show_user(user_id) 81 | return self.api.send_text_message_to_userid(user_id, text) 82 | return False 83 | 84 | def send_message_to_username(self, username, text): 85 | return self.api.send_text_message_to_username(username, text) 86 | 87 | def get_random_time_string(self, from_hour, to_hour): 88 | return "{:02d}:{:02d}".format(random.randint(from_hour, (to_hour - 1)), random.randint(0, 59)) 89 | 90 | def schedule_new_shifts(self): 91 | shift1 = self.get_random_time_string(7, 9) 92 | shift2 = self.get_random_time_string(13, 16) 93 | shift3 = self.get_random_time_string(19, 21) 94 | self.log("[BOT] Scheduled shifts: {}, {}, {}".format(shift1, shift2, shift3)) 95 | schedule.every().day.at(shift1).do( 96 | lambda: self.run_threaded_once(self.messaging_shift, ())) 97 | schedule.every().day.at(shift2).do( 98 | lambda: self.run_threaded_once(self.messaging_shift, ())) 99 | schedule.every().day.at(shift3).do( 100 | lambda: self.run_threaded_once(self.messaging_shift, ())) 101 | 102 | def setup_messaging_service(self, messaging_service): 103 | self.log("[BOT][{}] setup messaging service".format(self.username)) 104 | self.messaging_service = messaging_service 105 | schedule.every().day.at("04:00").do( 106 | lambda: self.run_threaded(self.schedule_new_shifts, ())) 107 | 108 | def setup_warmup_service(self): 109 | self.log("[BOT][{}] setup warm up shifts".format(self.username)) 110 | schedule.every().day.at(self.get_random_time_string(18, 21)).do( 111 | lambda: self.run_threaded(self.warm_up_shift, ())) 112 | 113 | def warm_up_shift(self): 114 | self.log("[BOT][{}] perform warm up shift".format(self.username)) 115 | time.sleep(random.randint(15, 45)) 116 | warm_up_functions = [self.simulator.simulate_viewing_reels, self.simulator.like_random_from_timeline, self.simulator.simulate_random_search, self.simulator.simulate_scrolling, self.simulator.simulate_scrolling, self.api.pull_to_refresh] 117 | random.shuffle(warm_up_functions) 118 | 119 | for function in warm_up_functions: 120 | if random.random() > 0.5: 121 | function() 122 | time.sleep(random.randint(15, 45)) 123 | 124 | def run_threaded(self, job_function, args): 125 | job_thread = threading.Thread(target=job_function, args=args) 126 | job_thread.start() 127 | 128 | def run_threaded_once(self, job_function, args): 129 | job_thread = threading.Thread(target=job_function, args=args) 130 | job_thread.start() 131 | return schedule.CancelJob 132 | 133 | def donwload_inbox(self, amount): 134 | self.log("[BOT][{}] downloading inbox".format(self.username)) 135 | threads = [] 136 | oldest_cursor = None 137 | 138 | if self.api.inbox: 139 | for thread in self.api.inbox["threads"]: 140 | threads.append(thread) 141 | 142 | if "oldest_cursor" in self.api.inbox: 143 | oldest_cursor = self.api.inbox["oldest_cursor"] 144 | else: 145 | self.api.inbox = self.api.get_inbox(reason="pull_to_refresh") 146 | for thread in self.api.inbox["threads"]: 147 | threads.append(thread) 148 | if "oldest_cursor" in self.api.inbox: 149 | oldest_cursor = self.api.inbox["oldest_cursor"] 150 | 151 | while oldest_cursor is not None and len(threads) > amount: 152 | try: 153 | next_page = self.api.get_inbox(reason="pagination", cursor=oldest_cursor) 154 | 155 | if next_page: 156 | for thread in next_page["threads"]: 157 | threads.append(thread) 158 | if "oldest_cursor" in next_page: 159 | oldest_cursor = next_page["oldest_cursor"] 160 | else: 161 | oldest_cursor = None 162 | except: 163 | oldest_cursor = None 164 | time.sleep(random.randint(20, 40)) 165 | 166 | self.save_threads(threads) 167 | 168 | def save_threads(self, threads): 169 | save_path = "inbox/" + self.username + "_threads.json" 170 | 171 | inbox = { 172 | "user": self.username, 173 | "updated": time.time(), 174 | "inbox": threads 175 | } 176 | 177 | with open(save_path, "w+") as file: 178 | file.write(json.dumps(inbox)) 179 | 180 | def __init__(self, bot_handler, username, password, proxy, options): 181 | self.bot_id = str(uuid.uuid4()) 182 | self.messaging_service = None 183 | self.username = username 184 | self.password = password 185 | self.proxy = proxy 186 | self.session_file = "sessions/" + username + "_session.json" 187 | self.log_file = "sessions/" + username + "_log.txt" 188 | self.messages_per_shift_min = 5 189 | self.messages_per_shift_max = 12 190 | # to help skipping shifts 191 | self.shifts_skipped = 0 192 | # to store messages from messaging service 193 | self.messages = [] 194 | self.messages_sent = 0 195 | self.api = ig_api(self.username, self.password, self.proxy) 196 | self.bot_handler = bot_handler 197 | self.simulator = simulator(self.api) 198 | 199 | if self.bot_handler and options["start_with_messaging_service"]: 200 | self.setup_messaging_service(self.bot_handler.api_broker.messaging_service) 201 | 202 | if options["start_with_warmup_service"]: 203 | self.setup_warmup_service() 204 | 205 | self.api.startup_app() 206 | -------------------------------------------------------------------------------- /examples/messaging_service.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | class messaging_service: 5 | 6 | def check_if_in_queue(self, user_id): 7 | for bot in self.bot_handler.bots: 8 | for message in bot.messages: 9 | if message[0] == user_id: 10 | return True 11 | return False 12 | 13 | def skipped(self, user_id): 14 | with open(self.skipped_file) as f: 15 | if user_id in f.read(): 16 | return True 17 | return False 18 | 19 | def messaged(self, user_id): 20 | with open(self.messaged_file) as f: 21 | if user_id in f.read(): 22 | return True 23 | return False 24 | 25 | def mark_skipped(self, user_id): 26 | skipped = open(self.skipped_file, "a+") 27 | with skipped: 28 | skipped.write(user_id + "\n") 29 | 30 | def mark_messaged(self, user_id): 31 | messaged_file_handler = open(self.messaged_file, "a+") 32 | with messaged_file_handler: 33 | messaged_file_handler.write(user_id + "\n") 34 | 35 | def file_len(self, file_path): 36 | with open(file_path) as f: 37 | i = 0 38 | for i, l in enumerate(f): 39 | pass 40 | return i + 1 41 | 42 | def get_messages(self, amount): 43 | messages = [] 44 | database_handle = open(self.message_database, "r") 45 | 46 | with database_handle: 47 | reader = csv.reader(database_handle, delimiter=",") 48 | line_count = 0 49 | for line in reader: 50 | 51 | if len(messages) >= amount: 52 | continue 53 | 54 | if line_count != 0: 55 | user_id = line[0] 56 | username = line[1] 57 | message = line[2] 58 | 59 | if not self.check_if_in_queue(user_id) and not self.messaged(user_id) and not self.skipped(user_id): 60 | messages.append([user_id, username, message]) 61 | 62 | line_count += 1 63 | 64 | return messages 65 | 66 | def __init__(self, bot_handler): 67 | self.status = "healthy" 68 | self.skipped_file = "messaging_service/skipped.txt" 69 | self.messaged_file = "messaging_service/messaged.txt" 70 | self.message_database = "messaging_service/messages_db.csv" 71 | self.bot_handler = bot_handler 72 | -------------------------------------------------------------------------------- /examples/messaging_service/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/messaging_service/.DS_Store -------------------------------------------------------------------------------- /examples/messaging_service/messaged.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/messaging_service/messaged.txt -------------------------------------------------------------------------------- /examples/messaging_service/messages_db.csv: -------------------------------------------------------------------------------- 1 | pk,username,text,name -------------------------------------------------------------------------------- /examples/messaging_service/skipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/messaging_service/skipped.txt -------------------------------------------------------------------------------- /examples/sessions/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/sessions/.DS_Store -------------------------------------------------------------------------------- /examples/sessions/logs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/sessions/logs/.DS_Store -------------------------------------------------------------------------------- /examples/sessions/ready_for_messaging/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/sessions/ready_for_messaging/.DS_Store -------------------------------------------------------------------------------- /examples/sessions/removed/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/examples/sessions/removed/.DS_Store -------------------------------------------------------------------------------- /examples/simulator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | import string 4 | import json 5 | import os 6 | 7 | 8 | class simulator: 9 | 10 | def simulate_scrolling(self): 11 | self.api.log("[SIMULATOR][{}] simulate viewing timeline".format(self.api.username)) 12 | try: 13 | # do it 1 to 3 times 14 | for x in range(random.randint(1, 3)): 15 | self.api.get_timeline(reason="pagination") 16 | time.sleep(random.uniform(1, 3)) 17 | self.api.log_feed_suggestion() 18 | time.sleep(random.uniform(5, 30)) 19 | except Exception as e: 20 | self.api.log(e) 21 | self.api.log("[SIMULATOR][{}] Error, while simulate scrolling".format(self.api.username)) 22 | 23 | def simulate_viewing_reels(self): 24 | self.api.log("[SIMULATOR][{}] simulate viewing reels".format(self.api.username)) 25 | try: 26 | self.api.simulate_viewing_reels() 27 | except: 28 | self.api.log("[SIMULATOR][{}] Error, while viewing reels".format(self.api.username)) 29 | 30 | def like_random_from_timeline(self): 31 | for feed_item in self.api.timeline["feed_items"]: 32 | if "media_or_ad" in feed_item: 33 | if random.random() > 0.7: 34 | item_id = feed_item["media_or_ad"]["id"] 35 | self.api.log("[SIMULATOR][{}] liking media with id {}".format(self.api.username, item_id)) 36 | self.api.like_media(item_id) 37 | 38 | def check_simulate_search_user(self, user_id, username): 39 | self.api.get_search_dynamic_sections() 40 | time.sleep(random.uniform(1, 3)) 41 | step = int(len(username) / 3) 42 | self.api.topsearch(username[0:step]) 43 | time.sleep(random.uniform(1, 3)) 44 | self.api.topsearch(username[0:(step + step)]) 45 | time.sleep(random.uniform(1, 3)) 46 | final_search = self.api.topsearch(username) 47 | time.sleep(random.uniform(1, 3)) 48 | 49 | if final_search: 50 | try: 51 | search_json = self.api.get_json(final_search) 52 | if "list" in search_json: 53 | for entry in search_json["list"]: 54 | if "user" in entry: 55 | if str(entry["user"]["pk"]) == str(user_id): 56 | return True 57 | self.api.log("[SIMULATOR][{}] user {} not found".format(self.api.username, username)) 58 | except Exception as e: 59 | self.api.log(e) 60 | self.api.log("[SIMULATOR][{}] Error while searching".format(self.api.username)) 61 | 62 | return False 63 | 64 | def simulate_show_user(self, user_id): 65 | user_info_request = self.api.get_user_info(user_id) 66 | if user_info_request: 67 | user_json = self.api.get_json(user_info_request) 68 | if user_json["user"]["is_private"] == False: 69 | self.api.get_user_feed(user_id) 70 | self.api.get_user_story(user_id) 71 | self.api.get_user_highlights(user_id) 72 | self.api.get_friendship(user_id) 73 | 74 | def simulate_random_search(self): 75 | self.api.log("[SIMULATOR][{}] simulate random search".format(self.api.username)) 76 | self.api.get_search_dynamic_sections() 77 | time.sleep(random.uniform(1, 3)) 78 | randomsearchstring = "".join(random.choice(string.ascii_letters) for i in range(random.randint(4, 9))) 79 | self.api.topsearch(randomsearchstring[random.randint(2, 3):]) 80 | time.sleep(random.uniform(1, 3)) 81 | 82 | last_search = self.api.topsearch(randomsearchstring) 83 | 84 | if last_search: 85 | try: 86 | search_json = self.api.get_json(last_search) 87 | if "list" in search_json: 88 | if len(search_json["list"]) > 0: 89 | first_user_pk = search_json["list"][0]["user"]["pk"] 90 | first_user_name = search_json["list"][0]["user"]["username"] 91 | time.sleep(random.uniform(2, 5)) 92 | self.api.register_recent_search_click(first_user_name, first_user_pk) 93 | self.simulate_show_user(first_user_name) 94 | except Exception as e: 95 | self.api.log(e) 96 | self.api.log("[ERROR][{}] simulating random search".format(self.api.username)) 97 | 98 | def __init__(self, api): 99 | self.api = api 100 | -------------------------------------------------------------------------------- /examples/webservice.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler 2 | import json 3 | import re 4 | 5 | 6 | def create_server_handler(broker): 7 | class Server(BaseHTTPRequestHandler): 8 | def _set_headers(self): 9 | try: 10 | self.send_response(200) 11 | self.send_header("Content-type", "application/json") 12 | self.end_headers() 13 | except: 14 | pass 15 | 16 | def do_HEAD(self): 17 | try: 18 | self._set_headers() 19 | except: 20 | pass 21 | 22 | def do_GET(self): 23 | try: 24 | self._set_headers() 25 | if re.search("/bot/(.*)/timeline", self.path): 26 | bot_id = self.path.split("/")[-2] 27 | bot = self.broker.bot_handler.get(bot_id) 28 | if bot: 29 | timeline = bot.api.timeline 30 | self.wfile.write(json.dumps(timeline).encode()) 31 | 32 | if re.search("/bot/(.*)/inbox", self.path): 33 | bot_id = self.path.split("/")[-2] 34 | bot = self.broker.bot_handler.get(bot_id) 35 | if bot: 36 | inbox = bot.api.inbox 37 | self.wfile.write(json.dumps(inbox).encode()) 38 | 39 | if re.search("/bot/(.*)/reels", self.path): 40 | bot_id = self.path.split("/")[-2] 41 | bot = self.broker.bot_handler.get(bot_id) 42 | if bot: 43 | reels = bot.api.reels 44 | self.wfile.write(json.dumps(reels).encode()) 45 | 46 | if re.search("/bot/(.*)/reels_media", self.path): 47 | bot_id = self.path.split("/")[-2] 48 | bot = self.broker.bot_handler.get(bot_id) 49 | if bot: 50 | reels_media = bot.api.reels_media 51 | self.wfile.write(json.dumps(reels_media).encode()) 52 | 53 | if re.search("/bot/(.*)/explore", self.path): 54 | bot_id = self.path.split("/")[-2] 55 | bot = self.broker.bot_handler.get(bot_id) 56 | if bot: 57 | explore = bot.api.explore 58 | self.wfile.write(json.dumps(explore).encode()) 59 | 60 | elif re.search("/bots", self.path): 61 | self.wfile.write(self.broker.get_bot_handler_bots().encode()) 62 | except: 63 | pass 64 | 65 | 66 | def __init__(self, *args, **kwargs): 67 | self.broker = broker 68 | super(Server, self).__init__(*args, **kwargs) 69 | 70 | def log_message(self, format, *args): 71 | pass 72 | 73 | return Server 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /ig_api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/.DS_Store -------------------------------------------------------------------------------- /ig_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .constants import * 2 | from ._logging_client_events import logging_client_events 3 | 4 | class ig_api: 5 | from ._app import startup_app, startup_flow, prelogin_flow, get_client_time 6 | from ._user import convert_username_to_userid, get_user_feed, get_user_info, get_user_story, get_user_highlights, get_friendship, search_username, follow 7 | from ._feed import get_timeline, get_video_feed, log_feed_suggestion, simulate_feed_view_info, timeline_pull_to_refresh, simulate_seen_organic_items, pull_to_refresh, log_timeline_events 8 | from ._login import setup_logged_in_headers, setup_prelogin_headers, login, encrypt_password, relogin 9 | from ._session import restore_from_session_file, save_session_ids, save_login_session, save_claim, delete_session, generate_new_session_ids, generate_new_uuids, generate_random_uuid, get_seed 10 | from ._requests import check_request_status, check_set_headers, post, get, set_proxy, get_json, sign_json, update_pigeon_time 11 | from ._reels import get_all_seen_reels, get_all_organice_seen_reels, get_seen_sponsored_reels, simulate_viewing_reels, inject_ad_reels, get_reels_media, refresh_reels 12 | from ._direct import send_text_message, create_thread, send_text_message_to_userid, send_text_message_to_username, get_inbox, get_presence 13 | from ._search import register_recent_search_click, topsearch, get_search_dynamic_sections 14 | from ._misc import graphql_get_doc, get_topical_explore, random_batch, post_mobile_config, register_to_push, get_viewable_statuses, notifications_badge, sync, sync_launcher, discover_ayml, update_locale, create_nav_chain 15 | from ._media import get_media_comments, like_media 16 | from ._news import get_news, mark_news_seen 17 | from ._logging import log 18 | 19 | def __init__(self, username, password, proxy=None): 20 | self.username = username 21 | self.password = password 22 | self.session_id_lifetime = 1200000 23 | self.timeline = [] 24 | self.timeline_last_fetch_time = self.get_client_time() 25 | self.reels = [] 26 | self.ad_reels = [] 27 | self.media_reels = [] 28 | self.video_feed = [] 29 | self.explore = [] 30 | self.inbox = [] 31 | self.proxy = proxy if proxy else "" 32 | self.session_file = "sessions/" + self.username + "_session.json" 33 | self.log_file = "sessions/logs/" + self.username + "_log.txt" 34 | self.status = "initializing" 35 | self.logged_in = False 36 | self.logging_client_events = logging_client_events(self) 37 | -------------------------------------------------------------------------------- /ig_api/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_app.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_app.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_direct.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_direct.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_feed.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_feed.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_logging.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_logging.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_logging_client_events.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_logging_client_events.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_login.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_login.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_media.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_media.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_misc.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_misc.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_news.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_news.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_reels.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_reels.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_requests.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_requests.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_search.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_session.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_session.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/_user.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/_user.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/constants.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/constants.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/__pycache__/feed.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crylonblue/ig_api/0c4ef40359716783a963db2636349c950c36ec85/ig_api/__pycache__/feed.cpython-39.pyc -------------------------------------------------------------------------------- /ig_api/_app.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from . import constants 3 | import time 4 | import random 5 | import json 6 | 7 | 8 | def startup_app(self, device_id=None): 9 | # we are generating new session ids 10 | self.session = requests.session() 11 | self.claim = "0" 12 | 13 | # we are adding the default header 14 | # pass by value 15 | self.headers = dict(constants.HEADERS) 16 | self.session.headers.update(self.headers) 17 | 18 | # update the time for pigeon 19 | self.update_pigeon_time() 20 | 21 | # misc 22 | self.is_charging = random.randint(0, 1) 23 | 24 | # if we restore the session 25 | 26 | if self.restore_from_session_file(): 27 | self.log("[INFO] login using session file") 28 | 29 | # set proxy to savefile 30 | self.set_proxy(self.proxy) 31 | 32 | if (time.time() - int(self.session_start)) > self.session_id_lifetime: 33 | self.log("[INFO] previous session elapsed, generate new session") 34 | self.generate_new_session_ids() 35 | self.save_session_ids() 36 | 37 | if self.startup_flow(fresh_login=False): 38 | self.logged_in = True 39 | 40 | else: 41 | self.log("[INFO] fresh login") 42 | 43 | # simulate new session 44 | self.session_start = time.time() 45 | self.generate_new_session_ids() 46 | 47 | # simulate new device 48 | self.generate_new_uuids() 49 | 50 | # if we have a device id preset 51 | if device_id: 52 | print("[INFO] setting up custom device id") 53 | self.device_id = device_id 54 | 55 | self.log( 56 | "[INFO] device_id: {device_id}\npigeon_session_id: {pigeon_session_id}\ndevice_token: {device_token}\nvoip_ios_token: {ios_voip_device_token}".format( 57 | device_id=self.device_id, pigeon_session_id=self.pigeon_session_id, device_token=self.device_token, 58 | ios_voip_device_token=self.ios_voip_device_token)) 59 | # set proxy 60 | self.set_proxy(self.proxy) 61 | # get public keys and set defaults 62 | if not self.prelogin_flow(): 63 | self.log("[INFO] ERROR OCCURRED ON PRELOGIN FLOW") 64 | return False 65 | # login 66 | self.logged_in = self.login(self.username, self.password) 67 | 68 | if self.logged_in: 69 | self.startup_flow() 70 | 71 | if self.logged_in: 72 | self.log("[INFO] logged in {}!".format(self.username)) 73 | self.status = "valid" 74 | return True 75 | else: 76 | self.status = "login_failure" 77 | try: 78 | self.delete_session() 79 | except: 80 | pass 81 | return False 82 | 83 | 84 | def startup_flow(self, fresh_login=True): 85 | self.sync() 86 | self.sync_launcher() 87 | interop_response = self.get("direct_v2/has_interop_upgraded") 88 | 89 | if interop_response: 90 | if interop_response.status_code != 200: 91 | if interop_response.status_code == 403: 92 | response_json = self.get_json(interop_response) 93 | if "message" in response_json: 94 | if response_json["message"] == "login_required" and response_json["logout_reason"] == 8: 95 | self.relogin() 96 | return False 97 | else: 98 | return False 99 | else: 100 | return False 101 | 102 | if not fresh_login: 103 | self.post_mobile_config() 104 | 105 | self.get("qp/get_cooldowns/") 106 | self.get_timeline() 107 | self.refresh_reels() 108 | self.graphql_get_doc("3323463714396709", {"include_fbpay_enabled": True, "include_fbpay_is_connected": False}) 109 | 110 | if not fresh_login: 111 | self.graphql_get_doc("2796210233799562") 112 | self.graphql_get_doc("2476027279143426") 113 | self.get("pro_home/badge_pro_home_entry_point/") 114 | self.get("archive/reel/profile_archive_badge/?timezone_offset=7200") 115 | self.get_news() 116 | 117 | self.get_inbox() 118 | 119 | self.notifications_badge() 120 | 121 | # self.register_to_push("ios", self.device_token) 122 | # self.register_to_push("ios_voip", self.device_token) 123 | 124 | if not fresh_login: 125 | self.discover_ayml() 126 | 127 | self.get("multiple_accounts/get_account_family/") 128 | 129 | business_eligibility_params = { 130 | "product_types": "branded_content,igtv_revshare,user_pay" 131 | } 132 | 133 | self.get("business/eligibility/get_monetization_products_eligibility_data/", params=business_eligibility_params) 134 | 135 | self.get("business/branded_content/should_require_professional_account/") 136 | 137 | camera_models_data = { 138 | "model_request_blobs": json.dumps([{ 139 | "type": "nametag", 140 | "nametag_model_version": "1", 141 | "supported_model_compression_type": "TAR_BROTLI,NONE" 142 | }]), 143 | "_uuid": self.device_id, 144 | "_uid": self.ds_user_id 145 | } 146 | 147 | self.post("creatives/camera_models/", data=self.sign_json(camera_models_data)) 148 | 149 | self.get("users/" + self.ds_user_id + "/info/?device_id=" + self.device_id) 150 | 151 | self.get("fbsearch/recent_searches/") 152 | 153 | banyan_params = { 154 | "views": json.dumps([ 155 | "story_share_sheet", 156 | "direct_user_search_nullstate", 157 | "reshare_share_sheet", 158 | "group_stories_share_sheet", 159 | "forwarding_recipient_sheet", 160 | "share_extension", 161 | "direct_user_search_keypressed" 162 | ]) 163 | } 164 | 165 | self.get("banyan/banyan/", params=banyan_params) 166 | 167 | self.get("civic_action/get_voting_feed_banner/") 168 | 169 | if fresh_login: 170 | self.get("profiling/client_network_trace_sampling/") 171 | 172 | self.get("linked_accounts/get_linkage_status_v2/") 173 | 174 | self.get("friendships/autocomplete_user_list/?version=2") 175 | 176 | bootstrap_params = { 177 | "surfaces": json.dumps([ 178 | "coefficient_ios_section_test_bootstrap_ranking", 179 | "coefficient_besties_list_ranking", 180 | "coefficient_rank_recipient_user_suggestion", 181 | "autocomplete_user_list" 182 | ]) 183 | } 184 | 185 | self.get("scores/bootstrap/users/", params=bootstrap_params) 186 | 187 | self.random_batch() 188 | 189 | if fresh_login: 190 | self.update_locale() 191 | 192 | if not fresh_login: 193 | self.get_topical_explore() 194 | 195 | self.logging_client_events.add_log(self.logging_client_events.get_fb_token_access_control_log()) 196 | self.logging_client_events.add_log(self.logging_client_events.get_badging_event_log()) 197 | self.logging_client_events.add_log(self.logging_client_events.get_navigation_tab_log()) 198 | self.logging_client_events.add_log(self.logging_client_events.get_direct_inbox_fetch_success_log()) 199 | 200 | if fresh_login: 201 | self.logging_client_events.add_log(self.logging_client_events.get_database_create_log()) 202 | 203 | if random.random() < 0.5: 204 | self.logging_client_events.add_log(self.logging_client_events.get_graphql_subscription_log()) 205 | 206 | self.logging_client_events.add_log(self.logging_client_events.get_sso_status_log()) 207 | 208 | if random.random() < 0.5: 209 | self.logging_client_events.add_log(self.logging_client_events.get_autocomplete_store_load_users_log()) 210 | 211 | self.logging_client_events.add_log(self.logging_client_events.get_location_event_log()) 212 | 213 | return True 214 | 215 | 216 | def prelogin_flow(self): 217 | self.log("[INFO] pre login flow ...") 218 | 219 | self.headers["x-device-id"] = self.device_id 220 | self.session.headers.update(self.headers) 221 | 222 | response = self.post("qe/sync/", data={ 223 | "id": self.device_id, 224 | "server_config_retrieval": "1" 225 | }) 226 | 227 | if not response: 228 | return False 229 | 230 | self.publickeyid = int(response.headers['ig-set-password-encryption-key-id']) 231 | self.publickey = response.headers["ig-set-password-encryption-pub-key"] 232 | 233 | if "ig-set-x-mid" in response.headers: 234 | self.mid = str(response.headers["ig-set-x-mid"]) 235 | else: 236 | self.mid = "0" 237 | 238 | if "ig-set-authorization" in response.headers: 239 | self.auth = response.headers["ig-set-authorization"] 240 | 241 | self.ds_user_id = response.headers["ig-set-ig-u-ds-user-id"] 242 | self.ig_u_rur = response.headers["ig-set-ig-u-rur"] 243 | 244 | self.setup_prelogin_headers() 245 | 246 | return True 247 | 248 | 249 | def get_client_time(self): 250 | return "%.6f" % time.time() 251 | -------------------------------------------------------------------------------- /ig_api/_direct.py: -------------------------------------------------------------------------------- 1 | import random 2 | import json 3 | import time 4 | 5 | def send_text_message(self, thread_id, text): 6 | client_context = "".join(["{}".format(random.randint(0, 9)) for num in range(0, 19)]) 7 | 8 | send_data = { 9 | "action": "send_item", 10 | "_uuid": self.device_id, 11 | "client_context": client_context, 12 | "device_id": self.device_id, 13 | "offline_threading_id": client_context, 14 | "mutation_token": client_context, 15 | "text": text, 16 | "thread_id": thread_id, 17 | "nav_chain": self.create_nav_chain("IGProfileViewController"), 18 | "send_attribution": "direct_inbox", 19 | "is_shh_mode": 0 20 | } 21 | 22 | return self.post("direct_v2/threads/broadcast/text/", data=send_data) 23 | 24 | def create_thread(self, recipients): 25 | 26 | thread_context = self.generate_random_uuid() 27 | 28 | thread_data = { 29 | "client_context": thread_context, 30 | "_uuid": self.device_id, 31 | "recipient_users": json.dumps(recipients), 32 | "_uid": self.ds_user_id 33 | } 34 | 35 | thread_context = self.post("direct_v2/create_group_thread/", data=self.sign_json(thread_data)) 36 | 37 | if thread_context.status_code == 200: 38 | thread_response_json = self.get_json(thread_context) 39 | thread_id = thread_response_json["thread_id"] 40 | 41 | return thread_id 42 | else: 43 | return None 44 | 45 | def send_text_message_to_userid(self, user_id, text): 46 | 47 | thread_id = self.create_thread([user_id]) 48 | if thread_id: 49 | time.sleep(random.randint(2, 5)) 50 | send_request = self.send_text_message(thread_id, text) 51 | 52 | if send_request.status_code == 200: 53 | return True 54 | 55 | return False 56 | 57 | def send_text_message_to_username(self, username, text): 58 | try: 59 | user_id = self.convert_username_to_userid(username) 60 | 61 | if user_id: 62 | return self.send_text_message_to_userid(user_id, text) 63 | except Exception as e: 64 | self.log("[ERROR][{}] While sending message".format(self.username)) 65 | self.log(e) 66 | 67 | return False 68 | 69 | def get_inbox(self, reason="cold_start_fetch", cursor=None): 70 | #reasons: cold_start_fetch, pull_to_refresh, pagination 71 | 72 | inbox_params = { 73 | "persistentBadging": True, 74 | "folder": "0", 75 | "fetch_reason": reason, 76 | "thread_message_limit" : "10", 77 | "limit": "20" 78 | } 79 | 80 | if cursor != None: 81 | inbox_params["cursor"] = cursor 82 | 83 | inbox_request = self.get("direct_v2/inbox/", params=inbox_params) 84 | 85 | self.inbox = self.get_json(inbox_request) 86 | 87 | return inbox_request 88 | 89 | 90 | def get_presence(self, additional_params=False): 91 | if additional_params: 92 | params = { 93 | "recent_thread_limit": "25", 94 | "suggested_followers_limit": "150" 95 | } 96 | else: 97 | params = None 98 | 99 | return self.get("direct_v2/get_presence/", params=params) 100 | -------------------------------------------------------------------------------- /ig_api/_feed.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | import random 3 | 4 | 5 | def pull_to_refresh(self): 6 | # simulate pull refresh in app 7 | self.timeline_pull_to_refresh() 8 | self.refresh_reels("pull_to_refresh") 9 | 10 | 11 | def timeline_pull_to_refresh(self): 12 | self.get_timeline(reason="pull_to_refresh") 13 | 14 | 15 | def get_timeline(self, reason="cold_start_fetch"): 16 | # reasons: cold_start_fetch, pull_to_refresh, pagination 17 | 18 | timeline_data = { 19 | "is_async_ads_double_request": "0", 20 | "session_id": self.ds_user_id + "_" + self.session_id, 21 | "_uuid": self.device_id, 22 | "X-CM-Latency": "6000", 23 | "device_id": self.device_id, 24 | "is_async_ads_rti": "0", 25 | "has_seen_aart_on": "0", 26 | "request_id": self.ds_user_id + "_" + self.generate_random_uuid(), 27 | "rti_delivery_backend": "0", 28 | "X-CM-Bandwidth-KBPS": str(random.randint(7000, 10000)), 29 | "battery_level": random.randint(10, 100), 30 | "is_dark_mode": self.is_dark_mode, 31 | "is_charging": self.is_charging, 32 | "will_sound_on": "0", 33 | "timezone_offset": "7200", 34 | "bloks_versioning_id": constants.BLOKS_VERSION, 35 | "reason": reason, 36 | "att_permission_status": "2", 37 | "phone_id": self.device_id, 38 | "is_async_ads_in_headload_enabled": "0" 39 | } 40 | 41 | if reason != "cold_start_fetch": 42 | timeline_data["feed_view_info"] = self.simulate_feed_view_info() 43 | timeline_data["seen_organic_items"] = self.simulate_seen_organic_items() 44 | # self.logging_client_events.add_log(self.logging_client_events.get_main_feed_request_succeed_log()) 45 | else: 46 | timeline_data["feed_view_info"] = "[]" 47 | 48 | if reason == "pull_to_refresh": 49 | timeline_data["is_pull_to_refresh"] = "1" 50 | 51 | timeline_request = self.post("feed/timeline/", data=timeline_data) 52 | 53 | if timeline_request: 54 | if timeline_request.status_code == 200: 55 | self.timeline = self.get_json(timeline_request) 56 | self.timeline_last_time_fetch = self.get_client_time() 57 | self.log_timeline_events(timeline_request) 58 | return timeline_request 59 | else: 60 | self.log(timeline_request.content) 61 | return False 62 | else: 63 | return False 64 | 65 | 66 | def get_video_feed(self): 67 | data = { 68 | "tab_type": "clips_tab", 69 | "session_id": self.session_id, 70 | "_uuid": self.device_id, 71 | "container_module": "clips_viewer_clips_tab", 72 | "pct_reels": "0" 73 | } 74 | 75 | request = self.post("discover/videos_feed/", data=data) 76 | 77 | self.video_feed = self.get_json(request) 78 | 79 | return request 80 | 81 | 82 | def log_feed_suggestion(self, action="seen"): 83 | log_data = { 84 | "is_business": "0", 85 | "_uuid": self.device_id, 86 | "type": "feed_aysf", 87 | "position": "4", 88 | "action": action 89 | } 90 | 91 | return self.post("feedsuggestion/log/", data=log_data) 92 | 93 | 94 | def simulate_feed_view_info(self): 95 | # we simulate that we've seen every element in the feed 96 | feed_view_info = [] 97 | if "feed_items" in self.timeline: 98 | for feed_item in self.timeline["feed_items"]: 99 | 100 | if "media_or_ad" in feed_item: 101 | item = feed_item["media_or_ad"] 102 | ts = item["taken_at"] 103 | media_id = str(item["id"]).split("_")[0] 104 | time_info = {"50": random.randint(150, 3000)} 105 | feed_view_info.append({ 106 | "media_id": media_id, 107 | "ts": ts, 108 | "media_pct": 1, 109 | "time_info": time_info 110 | }) 111 | 112 | return feed_view_info 113 | 114 | 115 | def simulate_seen_organic_items(self): 116 | # we simulate that we've seen every element of the timeline 117 | seen_organic_items = [] 118 | if "feed_items" in self.timeline: 119 | for feed_item in self.timeline["feed_items"]: 120 | if "media_or_ad" in feed_item: 121 | item = feed_item["media_or_ad"] 122 | timestamp = item["device_timestamp"] 123 | item_id = item["id"] 124 | 125 | seen_organic_items.append({ 126 | "item_id": item_id, 127 | "seen_states": [{ 128 | "media_id": item_id, 129 | "media_time_spent": [random.randint(150, 2000), random.randint(150, 2000), 130 | random.randint(150, 2000), random.randint(150, 2000)], 131 | "impression_timestamp": timestamp + random.randint(10, 30), 132 | "media_percent_visible": random.uniform(0.8, 1) if random.uniform(0, 1) > 0.5 else 1 133 | }] 134 | }) 135 | 136 | return seen_organic_items 137 | 138 | 139 | def log_timeline_events(self, timeline_request): 140 | timeline_request_data = self.get_json(timeline_request) 141 | if "feed_items" in timeline_request_data: 142 | for feed_item in timeline_request_data["feed_items"]: 143 | if "media_or_ad" in feed_item: 144 | request_id = timeline_request_data["request_id"] 145 | session_id = timeline_request_data["session_id"] 146 | follow_status = "not_following" 147 | m_pk = feed_item["media_or_ad"]["pk"] 148 | m_t = feed_item["media_or_ad"]["media_type"] 149 | tracking_token = feed_item["media_or_ad"]["organic_tracking_token"] 150 | if "user" in feed_item["media_or_ad"]: 151 | a_pk = feed_item["media_or_ad"]["user"]["pk"] 152 | if "friendship_status" in feed_item["media_or_ad"]["user"]: 153 | if "following" in feed_item["media_or_ad"]["user"]["friendship_status"]: 154 | if feed_item["media_or_ad"]["user"]["friendship_status"]["following"]: 155 | follow_status = "following" 156 | self.logging_client_events.add_log( 157 | self.logging_client_events.get_instagram_organic_impression_log(request_id, follow_status, 158 | m_pk, m_t, tracking_token, 159 | a_pk)) 160 | self.logging_client_events.add_log( 161 | self.logging_client_events.get_instagram_organic_time_spent_log(m_pk, tracking_token, 162 | request_id, m_t, a_pk, 163 | session_id)) 164 | -------------------------------------------------------------------------------- /ig_api/_logging.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def log(self, log_text): 5 | if not os.path.isfile(self.log_file): 6 | with open(self.log_file, "w"): 7 | pass 8 | 9 | with open(self.log_file, "a") as log_file: 10 | log_file.write(str(log_text) + "\n") 11 | -------------------------------------------------------------------------------- /ig_api/_logging_client_events.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import json 3 | import requests 4 | import base64 5 | import time 6 | import random 7 | from . import constants 8 | 9 | 10 | class logging_client_events: 11 | def send_logs_compressed(self): 12 | self.send_log(self.create_log_message(), True) 13 | 14 | def send_logs_uncompressed(self): 15 | self.send_log(self.create_log_message(), False) 16 | 17 | def add_log(self, log_name): 18 | self.message.append(log_name) 19 | self.push_if_ready() 20 | 21 | def push_if_ready(self): 22 | if len(self.message) > random.randint(4, 12): 23 | self.send_logs_compressed() 24 | self.message = [] 25 | 26 | def send_log(self, message_dict, compression=False): 27 | data = { 28 | "format": "json", 29 | "access_token": constants.APP_ID + "|" + constants.ACCESS_TOKEN, 30 | "sent_time": self.api.get_client_time(), 31 | "system_uptime": self.get_system_uptime() 32 | } 33 | 34 | if compression: 35 | compressed_message = base64.b64encode(zlib.compress(bytes(json.dumps(message_dict).encode()), 1)).decode() 36 | data["compressed"] = 1 37 | data["message"] = compressed_message 38 | else: 39 | data["message"] = json.dumps(message_dict) 40 | 41 | try: 42 | log_request = self.session.post("https://graph.instagram.com/logging_client_events", data=data) 43 | self.api.log("[LOG][POST] {} - logging_client_events".format(log_request.status_code)) 44 | self.seq += 1 45 | except: 46 | self.api.log("[LOG][ERROR] while logging client events") 47 | 48 | 49 | def create_log_message(self): 50 | log_message = { 51 | "data": self.message, 52 | "app_id": "124024574287414", 53 | "channel": "regular", 54 | "time": time.time(), 55 | "app_ver": "197.0.0.20.119 (305020938)", 56 | "device_id": self.api.device_id, 57 | "family_device_id": self.api.device_id, 58 | "session_id": self.api.pigeon_session_id, 59 | "log_type": "client_event", 60 | "app_uid": self.api.ds_user_id, 61 | "seq": self.seq 62 | } 63 | 64 | return log_message 65 | 66 | def get_random_log_time(self): 67 | return time.time() - random.uniform(2, 10) 68 | 69 | def get_system_uptime(self): 70 | return int(time.time() - self.system_start_time) 71 | 72 | def get_instagram_ad_delivery_log(self, m_pk, tracking_token, a_pk, ad_id): 73 | return { 74 | "extra": { 75 | "follow_status": "not_following", 76 | "media_loading_progress": 0, 77 | "m_pk": m_pk, 78 | "source_of_action": "feed_timeline", 79 | "tracking_token": tracking_token, 80 | "a_pk": a_pk, 81 | "is_dark_mode": self.api.is_dark_mode, 82 | "ad_id": ad_id, 83 | "radio_type": "wifi-none", 84 | "delivery_flags": "n" 85 | }, 86 | "tags": 0, 87 | "module": "feed_time_line", 88 | "name": "instagram_ad_delivery", 89 | "time": self.get_random_log_time(), 90 | "sampling_rate": 1 91 | } 92 | 93 | def get_main_feed_request_succeed_log(self, reason, media_ids, feed_ranking_session_id, feed_ranking_request_id): 94 | unseen_indexes = [0, 1, 2, 3, 4, 5, 6] 95 | fetch_action = "reload" 96 | if reason == "pagination": 97 | fetch_action = "load_more" 98 | return { 99 | "extra": { 100 | "last_fetch_time": self.api.timeline_last_fetch_time, 101 | "has_unshown_items": False, 102 | "blocked_reason": "not_blocked", 103 | "radio_type": "wifi-none", 104 | "view_will_appear": False, 105 | "has_new_items": False, 106 | "application_state": "active", 107 | "application_will_become_active": False, 108 | "view_info_count": 0, 109 | "startup_origin": "normal", 110 | "media_ids": media_ids, 111 | "unseen_indexes": unseen_indexes, 112 | "is_onscreen": True, 113 | "current_module": "feed_timeline", 114 | "request_duration": random.randint(500, 2000), 115 | "view_did_appear": True, 116 | "feed_ranking_session_id": feed_ranking_session_id, 117 | "interaction_events": [], 118 | "reason": reason, 119 | "fetch_action": fetch_action, 120 | "new_items_delivered": False, 121 | "feed_ranking_request_id": feed_ranking_request_id, 122 | "time_since_background": 0, 123 | "pk": self.api.ds_user_id 124 | }, 125 | "tags": 0, 126 | "module": "feed_time_line", 127 | "name": "ig_main_feed_request_succeeded", 128 | "time": self.get_random_log_time(), 129 | "sampling_rate": 1 130 | } 131 | 132 | def get_reel_tray_impression(self, story_ranking_token, reel_id): 133 | return { 134 | "extra": { 135 | "is_live_streaming": False, 136 | "is_new_reel": 1, 137 | "radio_type": "wifi-none", 138 | "viewed_reel_count": 0, 139 | "unfetched_reel_count": 0, 140 | "live_reel_count": 0, 141 | "tray_position": 0, 142 | "new_reel_count": 9, 143 | "is_besties_reel": False, 144 | "viewed_replay_reel_count": 0, 145 | "story_ranking_token": story_ranking_token, 146 | "has_my_reel": True, 147 | "new_replay_reel_count": 0, 148 | "tray_session_id": self.api.tray_session_id, 149 | "muted_live_reel_count": 0, 150 | "reel_age": 0, 151 | "has_my_replay_reel": False, 152 | "reel_label_type": "timestamp", 153 | "is_pride_reel": False, 154 | "pk": self.api.ds_user_id, 155 | "a_pk": reel_id, 156 | "reel_type": "story", 157 | "muted_replay_reel_count": 0, 158 | "reel_id": reel_id, 159 | "muted_reel_count": 0 160 | }, 161 | "tags": 0, 162 | "module": "feed_time_line", 163 | "name": "reel_tray_impression", 164 | "time": self.get_random_log_time(), 165 | "sampling_rate": 1 166 | } 167 | 168 | def get_reel_tray_refresh_log(self): 169 | return { 170 | "extra": { 171 | "viewed_reel_count": 0, 172 | "was_successful": True, 173 | "tray_refresh_time": random.uniform(0.5, 2), 174 | "new_replay_reel_count": 0, 175 | "tray_refresh_type": "network", 176 | "tray_session_id": self.api.tray_session_id, 177 | "unfetched_reel_count": 0, 178 | "has_my_replay_reel": False, 179 | "story_ranking_token": self.api.reels["story_ranking_token"], 180 | "live_reel_count": 0, 181 | "pk": "48634776751", 182 | "muted_replay_reel_count": 0, 183 | "has_my_reel": False, 184 | "muted_reel_count": 0, 185 | "muted_live_reel_count": 0, 186 | "radio_type": "wifi-none", 187 | "new_reel_count": 0, 188 | "viewed_replay_reel_count": 0 189 | }, 190 | "tags": 0, 191 | "module": "feed_timeline", 192 | "name": "reel_tray_refresh", 193 | "time": self.get_random_log_time(), 194 | "sampling_rate": 1 195 | } 196 | 197 | def get_database_create_log(self): 198 | return { 199 | "extra": { 200 | "success": True, 201 | "radio_type": "wifi-none", 202 | "pk": self.api.ds_user_id 203 | }, 204 | "tags": 0, 205 | "module": "ig_sqlite_kit", 206 | "name": "sqlite_database_create", 207 | "time": self.get_random_log_time(), 208 | "sampling_rate": 1 209 | } 210 | 211 | def ig_remove_invalid_drafts_log(self): 212 | return { 213 | "extra": { 214 | "pk": self.api.ds_user_id, 215 | "invalid_drafts_count": 0, 216 | "radio_type": "wifi-none" 217 | }, 218 | "tags": 0, 219 | "module": "ig_creation_draft", 220 | "name": "ig_remove_invalid_drafts", 221 | "time": self.get_random_log_time(), 222 | "sampling_rate": 1 223 | } 224 | 225 | def get_feed_view_info_enabled_log(self): 226 | return { 227 | "extra": { 228 | "enabled": True, 229 | "radio_type": "wifi-none", 230 | "pk": self.api.ds_user_id 231 | }, 232 | "tags": 1, 233 | "module": "app", 234 | "name": "ig_ios_main_feed_view_info_enabled", 235 | "time": self.get_random_log_time(), 236 | "sampling_rate": 1 237 | } 238 | 239 | def get_fb_token_access_control_log(self): 240 | return { 241 | "extra": { 242 | "event_type": "token_access", 243 | "pk": self.api.ds_user_id, 244 | "caller_class": "IGMainFeedNetworkSourceRequestConfig", 245 | "caller_name": "ig_ios_feed_network_source_core", 246 | "radio_type": "wifi-none" 247 | }, 248 | "tags": 1, 249 | "module": "app", 250 | "name": "fx_legacy_fb_token_on_ig_access_control", 251 | "time": self.get_random_log_time(), 252 | "sampling_rate": 1 253 | } 254 | 255 | def get_badging_event_log(self): 256 | return { 257 | "extra": { 258 | "radio_type": "wifi-none", 259 | "use_case_id": "profile", 260 | "event_type": "impression", 261 | "badge_value": 0, 262 | "badge_position": "bottom_navigation_bar", 263 | "badge_display_style": "dot_badge", 264 | "pk": self.api.ds_user_id 265 | }, 266 | "tags": 1, 267 | "module": "app", 268 | "name": "badging_event", 269 | "time": self.get_random_log_time(), 270 | "sampling_rate": 1 271 | } 272 | 273 | def get_instagram_stories_request_sent_log(self, request_id): 274 | return { 275 | "extra": { 276 | "app_session_id": self.api.session_id, 277 | "tray_session_id": self.api.tray_session_id, 278 | "request_type": "cold_start", 279 | "request_id": request_id, 280 | "pk": "48634776751", 281 | "radio_type": "wifi-none" 282 | }, 283 | "tags": 1, 284 | "module": "reel_feed_timeline", 285 | "name": "instagram_stories_request_sent", 286 | "time": self.get_random_log_time(), 287 | "sampling_rate": 1 288 | } 289 | 290 | def get_instagram_stories_request_completed_log(self, request_id): 291 | return { 292 | "extra": { 293 | "app_session_id": self.api.session_id, 294 | "tray_session_id": self.api.tray_session_id, 295 | "request_id": request_id, 296 | "request_type": "cold_start", 297 | "pk": self.api.ds_user_id, 298 | "radio_type": "wifi-none" 299 | }, 300 | "tags": 1, 301 | "module": "reel_feed_timeline", 302 | "name": "instagram_stories_request_completed", 303 | "time": self.get_random_log_time(), 304 | "sampling_rate": 1 305 | } 306 | 307 | def get_instagram_organic_impression_log(self, feed_request_id, follow_status, media_pk, media_type, tracking_token, a_pk): 308 | return { 309 | "extra": { 310 | "session_id": self.api.ds_user_id + "_" + self.api.session_id, 311 | "feed_request_id": feed_request_id, 312 | "inventory_source": "media_or_ad", 313 | "media_loading_progress": 0, 314 | "follow_status": follow_status, 315 | "radio_type": "wifi-none", 316 | "nav_chain": "IGMainFeedViewController:feed_timeline:{}".format(random.randint(1, 4)), 317 | "action": "feed_timeline", 318 | "application_state": "active", 319 | "m_pk": media_pk, 320 | "m_t": media_type, 321 | "is_dark_mode": self.api.is_dark_mode, 322 | "tracking_token": tracking_token, 323 | "current_module": "feed_timeline", 324 | "is_checkout_enabled": False, 325 | "source_of_action": "feed_timeline", 326 | "pk": self.api.ds_user_id, 327 | "delivery_flags": "n", 328 | "m_ix": random.randint(0, 10), 329 | "a_pk": a_pk, 330 | "two_measurement_debugging_fields": {} 331 | }, 332 | "tags": 1, 333 | "module": "feed_time_line", 334 | "name": "instagram_organic_impression", 335 | "time": self.get_random_log_time(), 336 | "sampling_rate": 1 337 | } 338 | 339 | def get_organic_impression_viewed_log(self, follow_status, m_pk, request_id, m_t, a_pk, session_id): 340 | return { 341 | "extra": { 342 | "follow_status": follow_status, 343 | "m_ix": random.randint(0, 10), 344 | "media_loading_progress": 3, 345 | "m_pk": m_pk, 346 | "application_state": "active", 347 | "source_of_action": "feed_timeline", 348 | "tracking_token": tracking_token, 349 | "inventory_source": "media_or_ad", 350 | "feed_request_id": request_id, 351 | "m_t": m_t, 352 | "a_pk": a_pk, 353 | "is_dark_mode": "1", 354 | "pk": self.api.ds_user_id, 355 | "session_id": session_id, 356 | "is_igtv": True, 357 | "delivery_flags": "n", 358 | "radio_type": "wifi-none" 359 | }, 360 | "tags": 32, 361 | "module": "feed_timeline", 362 | "name": "instagram_organic_viewed_impression", 363 | "time": self.get_random_log_time(), 364 | "sampling_rate": 1 365 | } 366 | 367 | def get_navigation_tab_log(self): 368 | return { 369 | "extra": { 370 | "tabs": [ 371 | "main_home", 372 | "main_search", 373 | "main_camera", 374 | "main_inbox", 375 | "main_profile" 376 | ], 377 | "pk": self.api.ds_user_id, 378 | "headers": [], 379 | "app_start_type": "", 380 | "app_device_id": self.api.device_id, 381 | "radio_type": "wifi-none" 382 | }, 383 | "tags": 1, 384 | "module": "app", 385 | "name": "ig_navigation_tab_impression", 386 | "time": self.get_random_log_time(), 387 | "sampling_rate": 1 388 | } 389 | 390 | def get_comment_impression_log(self, like_count, ca_pk, parent_c_pk, a_ck, m_pk): 391 | return { 392 | "extra": { 393 | "radio_type": "wifi-none", 394 | "like_count": like_count, 395 | "ca_pk": ca_pk, 396 | "is_covered": False, 397 | "parent_c_pk": parent_c_pk, 398 | "e_counter_channel": "", 399 | "a_pk": a_ck, 400 | "m_pk": m_pk, 401 | "c_pk": 17941748713535197, 402 | "pk": self.api.ds_user_id 403 | }, 404 | "tags": 1, 405 | "module": "feed_timeline", 406 | "name": "comment_impression", 407 | "time": self.get_random_log_time(), 408 | "sampling_rate": 1 409 | } 410 | 411 | def get_instagram_organic_time_spent_log(self, m_pk, tracking_token, feed_request_id, m_t, a_pk, feed_session_id): 412 | random_timespent = random.uniform(1, 5) 413 | random_timespent_ms = int(random_timespent * 1000) 414 | return { 415 | "extra": { 416 | "follow_status": "following", 417 | "m_ix": random.randint(0, 10), 418 | "timespent": random_timespent, 419 | "m_pk": m_pk, 420 | "application_state": "active", 421 | "source_of_action": "feed_timeline", 422 | "timespent_in_ms": random_timespent_ms, 423 | "tracking_token": tracking_token, 424 | "inventory_source": "media_or_ad", 425 | "feed_request_id": feed_request_id, 426 | "m_t": m_t, 427 | "feed_ts_source": "new_fmk", 428 | "a_pk": a_pk, 429 | "is_dark_mode": self.api.is_dark_mode, 430 | "session_id": feed_session_id, 431 | "radio_type": "wifi-none", 432 | "pk": self.api.ds_user_id 433 | }, 434 | "tags": 0, 435 | "module": "feed_timeline", 436 | "name": "instagram_organic_time_spent", 437 | "time": self.get_random_log_time(), 438 | "sampling_rate": 1 439 | } 440 | 441 | def get_video_displayed_log(self, like_count, ca_pk, parent_c_pk, a_pk, m_pk, c_pk, pk): 442 | return { 443 | "extra": { 444 | "radio_type": "wifi-none", 445 | "like_count": like_count, 446 | "ca_pk": ca_pk, 447 | "is_covered": False, 448 | "parent_c_pk": parent_c_pk, 449 | "e_counter_channel": "", 450 | "a_pk": a_pk, 451 | "m_pk": m_pk, 452 | "c_pk": c_pk, 453 | "pk": pk 454 | }, 455 | "tags": 1, 456 | "module": "feed_timeline", 457 | "name": "comment_impression", 458 | "time": self.get_random_log_time(), 459 | "sampling_rate": 1 460 | } 461 | 462 | def get_direct_inbox_fetch_success_log(self, fetch_reason="cold_start"): 463 | return { 464 | "extra": { 465 | "action": "attempt", 466 | "radio_type": "wifi-none", 467 | "fetch_uuid": self.api.device_id, 468 | "fetch_type": "snapshot", 469 | "fetch_reason": fetch_reason, 470 | "pk": self.api.ds_user_id, 471 | "page_size": "20" 472 | }, 473 | "tags": 0, 474 | "module": "ig_direct", 475 | "name": "ig_direct_inbox_fetch_success_rate", 476 | "time": self.get_random_log_time(), 477 | "sampling_rate": 1 478 | } 479 | 480 | def get_graphql_subscription_log(self): 481 | return { 482 | "extra": { 483 | "event_type": "client_subscribe", 484 | "ig_user_id": self.api.ds_user_id, 485 | "mqtt_subtopic": "1/graphqlsubscriptions/17867973967082385/{\"input_data\":{\"user_id\":\"" + str(self.api.ds_user_id) + "\"}}", 486 | "pk": self.api.ds_user_id, 487 | "radio_type": "wifi-none" 488 | }, 489 | "tags": 1, 490 | "module": "app", 491 | "name": "ig_graphql_subscription_event", 492 | "time": self.get_random_log_time(), 493 | "sampling_rate": 1 494 | } 495 | 496 | def get_sso_status_log(self): 497 | return { 498 | "extra": { 499 | "containermodule": "waterfall_log_in", 500 | "pk": self.api.ds_user_id, 501 | "enabled": "NO", 502 | "enable_igid": self.api.ds_user_id, 503 | "radio_type": "wifi-none" 504 | }, 505 | "tags": 1, 506 | "module": "app", 507 | "name": "sso_status", 508 | "time": self.get_random_log_time(), 509 | "sampling_rate": 1 510 | } 511 | 512 | def get_autocomplete_store_load_users_log(self): 513 | return { 514 | "extra": { 515 | "pk": self.api.ds_user_id, 516 | "current_user": "not_nil", 517 | "radio_type": "wifi-none" 518 | }, 519 | "tags": 1, 520 | "module": "app", 521 | "name": "autocomplete_store_load_users", 522 | "time": self.get_random_log_time(), 523 | "sampling_rate": 1 524 | } 525 | 526 | def get_location_event_log(self): 527 | return { 528 | "extra": { 529 | "pk": self.api.ds_user_id, 530 | "ls_state": "OFF", 531 | "precise": True, 532 | "reason": "NOT_DETERMINED", 533 | "radio_type": "wifi-none" 534 | }, 535 | "tags": 1, 536 | "module": "IGLocationKit", 537 | "name": "location_state_event", 538 | "time": self.get_random_log_time(), 539 | "sampling_rate": 1 540 | } 541 | 542 | def __init__(self, api): 543 | self.session = requests.Session() 544 | self.api = api 545 | self.system_start_time = time.time() - random.randint(10000, 100000) 546 | self.access_token = self.api.get_seed() 547 | self.session.headers.update({ 548 | "user-agent": constants.USER_AGENT, 549 | "x-ig-bandwidth-speed-kbps": "0.000", 550 | "accept-language": "de-DE;q=1.0", 551 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 552 | "x-tigon-is-retry": "False", 553 | "accept-encoding": "zstd, gzip, deflate", 554 | "x-fb-http-engine": "Liger", 555 | "x-fb-client-ip": "True", 556 | "x-fb-server-cluster": "True" 557 | }) 558 | self.seq = 2 559 | self.event_count = 1 560 | self.api = api 561 | self.message = [] 562 | -------------------------------------------------------------------------------- /ig_api/_login.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | import hashlib 3 | import time 4 | import base64 5 | import random 6 | from Cryptodome.Random import get_random_bytes 7 | from Cryptodome.Cipher import AES, PKCS1_v1_5 8 | import datetime 9 | import struct 10 | import json 11 | from Cryptodome.PublicKey import RSA 12 | 13 | 14 | def setup_logged_in_headers(self): 15 | self.headers["authorization"] = self.authorization 16 | self.headers["ig-u-rur"] = self.u_rur 17 | self.headers["ig-u-ds-user-id"] = self.ds_user_id 18 | self.headers["x-ig-device-id"] = self.device_id 19 | self.headers["x-ig-family-device-id"] = self.device_id 20 | self.headers["x-mid"] = str(self.mid) 21 | self.headers["ig-intended-user-id"] = self.ds_user_id 22 | self.headers["x-ig-www-claim"] = str(self.claim) 23 | self.headers["x-pigeon-session-id"] = self.pigeon_session_id 24 | self.session.headers.update(self.headers) 25 | 26 | 27 | def setup_prelogin_headers(self): 28 | self.headers["x-mid"] = str(self.mid) 29 | self.headers["ds_user_id"] = self.ds_user_id 30 | self.headers["ig-intended-user-id"] = "0" 31 | self.session.headers.update(self.headers) 32 | 33 | 34 | def login(self, username, password): 35 | try: 36 | self.username = username 37 | self.password = password 38 | 39 | dict_data = { 40 | "phone_id": self.device_id, 41 | "reg_login": "0", 42 | "device_id": self.device_id, 43 | "has_seen_aart_on": "0", 44 | "att_permission_status": "2", 45 | "username": self.username, 46 | "login_attempt_count": "0", 47 | "enc_password": self.encrypt_password(self.password) 48 | } 49 | 50 | signature = self.sign_json(dict_data) 51 | 52 | response = self.post("accounts/login/", data=signature, headers=self.session.headers) 53 | 54 | if response.status_code == 200: 55 | logged_in_json = self.get_json(response) 56 | 57 | if logged_in_json["status"] == "ok": 58 | self.save_login_session(response.headers) 59 | return True 60 | else: 61 | self.log(logged_in_json) 62 | return False 63 | 64 | elif response.status_code == 400: 65 | self.log(response.content) 66 | try: 67 | response_json = self.get_json(response) 68 | 69 | if response_json["message"] == "challenge_required": 70 | time.sleep(2) 71 | challenge_path = response_json["challenge"]["api_path"][1:] 72 | challenge_options = self.get(challenge_path) 73 | challenge_options_json = self.get_json(challenge_options) 74 | time.sleep(random.randint(2, 4)) 75 | 76 | if "step_name" in challenge_options_json: 77 | if challenge_options_json["step_name"] == "select_verify_method": 78 | if "email" in challenge_options_json["step_data"]: 79 | choicereq = self.post(challenge_path, data=self.sign_json({"choice": "1", "guid": self.device_id, "device_id": self.device_id})) 80 | self.log("[CHALLENGE] Email Verification send to: {}".format(challenge_options_json["step_data"]["email"])) 81 | security_code = input("[CHALLENGE] Insert Code:") 82 | time.sleep(random.randint(2, 4)) 83 | solving_request = self.post(challenge_path, data=self.sign_json({"security_code": security_code, "guid": self.device_id, "device_id": self.device_id})) 84 | self.save_login_session(solving_request.headers) 85 | return True 86 | else: 87 | self.log("[CHALLENGE] Unable to solve challenge") 88 | return False 89 | 90 | except: 91 | self.log("[ERROR] Trying solving challenge") 92 | return False 93 | 94 | else: 95 | self.log("[WARNING] Login failed: " + self.get_json(response)["message"]) 96 | return False 97 | except: 98 | self.log("[ERROR] While trying to login") 99 | return False 100 | 101 | def relogin(self): 102 | if self.login(self.username, self.password): 103 | if self.startup_flow(): 104 | self.logged_in = True 105 | else: 106 | return False 107 | 108 | 109 | def encrypt_password(self, password): 110 | key = get_random_bytes(32) 111 | iv = get_random_bytes(12) 112 | time = int(datetime.datetime.now().timestamp()) 113 | 114 | pubkey = base64.b64decode(self.publickey) 115 | 116 | rsa_key = RSA.importKey(pubkey) 117 | rsa_cipher = PKCS1_v1_5.new(rsa_key) 118 | encrypted_key = rsa_cipher.encrypt(key) 119 | 120 | aes = AES.new(key, AES.MODE_GCM, nonce=iv) 121 | aes.update(str(time).encode('utf-8')) 122 | 123 | encrypted_password, cipher_tag = aes.encrypt_and_digest(bytes(password, 'utf-8')) 124 | 125 | encrypted = bytes([1, 126 | self.publickeyid, 127 | *list(iv), 128 | *list(struct.pack(' 1 else False, 24 | "timezone_offset": "7200", 25 | "omit_cover_media": True, 26 | "session_id": self.session_id, 27 | "use_sectional_payload": "1", 28 | "reels_configuration": "hide_hero", 29 | "is_prefetch": True, 30 | "network_transfer_rate": "120.25" 31 | } 32 | 33 | request = self.get("discover/topical_explore/", params=topical_explore_params) 34 | 35 | self.explore = self.get_json(request) 36 | 37 | return request 38 | 39 | 40 | def discover_ayml(self): 41 | data = { 42 | "_uuid": self.device_id, 43 | "paginate": True, 44 | "module": "self_profile", 45 | "num_media": random.randint(1, 5) 46 | } 47 | 48 | return self.post("discover/ayml/", data=data) 49 | 50 | 51 | def random_batch(self): 52 | random_batch = random.choice(constants.BATCHES) 53 | 54 | batch_data = { 55 | "is_sdk": "0", 56 | "_uuid": self.device_id, 57 | "_uid": self.ds_user_id, 58 | "surfaces_to_queries": json.dumps({random_batch["id"]: constants.BATCH_QUERY}), 59 | "vc_policy": "default", 60 | "version": "1", 61 | "surfaces_to_triggers": json.dumps({random_batch["id"]: random.choice(random_batch["triggers"])}), 62 | "scale": "3.000000" 63 | } 64 | 65 | return self.post("qp/batch_fetch/", data=self.sign_json(batch_data)) 66 | 67 | 68 | def post_mobile_config(self): 69 | mobile_config = { 70 | "_uuid": self.device_id, 71 | "device_id": self.device_id, 72 | "_uid": self.ds_user_id, 73 | "bool_opt_policy": "0", 74 | "unit_type": "2", 75 | "fetch_type": "ASYNC_FULL", 76 | "query_hash": str(self.get_seed()) + str(self.get_seed()), 77 | "name_to_id": "true", 78 | "api_version": "3" 79 | } 80 | 81 | return self.post("launcher/mobileconfig/", data=self.sign_json(mobile_config)) 82 | 83 | 84 | def register_to_push(self, device_type, device_token): 85 | push_register_data = { 86 | "_uuid": self.device_id, 87 | "device_id": self.device_id, 88 | "device_token": device_token, 89 | "family_device_id": self.device_id, 90 | "device_app_installations": json.dumps({"threads": False, "igtv": False, "instagram": True}), 91 | "users": self.ds_user_id, 92 | "device_type": device_type 93 | } 94 | 95 | return self.post("push/register/?platform={platform_id}&device_type={device_type}".format(platform_id="18", 96 | device_type=device_type), 97 | data=push_register_data) 98 | 99 | 100 | def get_viewable_statuses(self): 101 | params = { 102 | "include_authors": True 103 | } 104 | 105 | return self.get("status/get_viewable_statuses/", params=params) 106 | 107 | 108 | def notifications_badge(self): 109 | data = { 110 | "user_ids": self.ds_user_id, 111 | "device_id": self.device_id, 112 | "_uuid": self.device_id 113 | } 114 | 115 | return self.post("notifications/badge/", data=data) 116 | 117 | 118 | def sync(self): 119 | sync_data = { 120 | "id": self.ds_user_id, 121 | "_uuid": self.device_id, 122 | "_uid": self.ds_user_id, 123 | "server_config_retrieval": "1" 124 | } 125 | return self.post("qe/sync/", data=self.sign_json(sync_data)) 126 | 127 | 128 | def sync_launcher(self): 129 | sync_data = { 130 | "use_mc": "1", 131 | "id": self.ds_user_id, 132 | "_uuid": self.device_id, 133 | "_uid": self.ds_user_id, 134 | "server_config_retrieval": "1" 135 | } 136 | 137 | return self.post("launcher/sync/", data=self.sign_json(sync_data)) 138 | 139 | 140 | def update_locale(self): 141 | update_locale_data = { 142 | "locale": "", 143 | "_uuid": self.device_id, 144 | "_uid": self.ds_user_id 145 | } 146 | 147 | return self.post("users/update_user_locale/", data=update_locale_data) 148 | 149 | 150 | 151 | def create_nav_chain(self, target): 152 | view_controllers = [ 153 | { 154 | "name": "IGMainFeedViewController", 155 | "container_modules": [ 156 | "feed_timeline" 157 | ] 158 | }, 159 | { 160 | "name": "IGDirectInboxNavigationController", 161 | "container_modules": [ 162 | "direct_inbox" 163 | ] 164 | }, 165 | { 166 | "name": "IGDirectThreadViewController", 167 | "container_modules": [ 168 | "direct_thread" 169 | ] 170 | }, 171 | { 172 | "name": "IGProfileViewController", 173 | "container_modules": [ 174 | "profile" 175 | ] 176 | }, 177 | { 178 | "name": "IGContextualFeedViewController", 179 | "container_modules": [ 180 | "feed_contextual_profile" 181 | ] 182 | }, 183 | { 184 | "name": "IGExploreViewController", 185 | "container_modules": [ 186 | "explore_popular" 187 | ] 188 | } 189 | ] 190 | 191 | chain_action = random.randint(2, 6) 192 | nav_chain_string = "" 193 | for i in range(random.randint(2, 5)): 194 | view_controller = random.choice(view_controllers) 195 | nav_chain_string += "{}:{}:{},".format(view_controller["name"], random.choice(view_controller["container_modules"]), chain_action) 196 | chain_action += random.randint(2, 4) 197 | 198 | for view_controller in view_controllers: 199 | if view_controller["name"] == target: 200 | nav_chain_string += "{}:{}:{}".format(view_controller["name"], random.choice(view_controller["container_modules"]), chain_action) 201 | 202 | return nav_chain_string 203 | -------------------------------------------------------------------------------- /ig_api/_news.py: -------------------------------------------------------------------------------- 1 | 2 | def get_news(self, max_id=None, first_record_timestamp=None): 3 | params = { 4 | "mark_as_seen": False, 5 | "timezone_offset": 7200 6 | } 7 | 8 | if max_id and first_record_timestamp: 9 | params = { 10 | "max_id": max_id, 11 | "last_checked": str(float(self.get_client_time()) - 30), 12 | "pagination_first_record_timestamp": first_record_timestamp, 13 | "mark_as_seen": False, 14 | "timezone_offset": 7200 15 | } 16 | 17 | request = self.get("news/inbox/", params=params) 18 | 19 | self.news = self.get_json(request) 20 | 21 | return request 22 | 23 | 24 | def mark_news_seen(self): 25 | data = { 26 | "_uuid": self.device_id 27 | } 28 | 29 | return self.post("news/inbox_seen/") -------------------------------------------------------------------------------- /ig_api/_reels.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | import time 3 | import random 4 | 5 | 6 | def get_all_seen_reels(self): 7 | reels = {} 8 | 9 | for reel in self.reels["tray"]: 10 | user_id = reel["user"]["pk"] 11 | reel_id = reel["id"] 12 | latest_reel_media = reel["latest_reel_media"] 13 | if "media_ids" in reel: 14 | for media_id in reel["media_ids"]: 15 | unique_reel_id = str(media_id) + "_" + str(reel_id) + "_" + str(user_id) 16 | reels[unique_reel_id] = [str(latest_reel_media) + "_" + str(int(time.time()) - random.randint(10, 30))] 17 | 18 | return reels 19 | 20 | 21 | def get_all_organice_seen_reels(self): 22 | organic_seen_reels = [] 23 | 24 | for reel in self.reels["tray"]: 25 | item_id = reel["id"] 26 | seen_states = [] 27 | 28 | for media_id in reel["media_ids"]: 29 | seen_states.append({ 30 | "media_id": media_id, 31 | "media_time_spent": [random.randint(150, 3000)], 32 | "impression_timestamp": int(time.time()) - random.randint(10, 100), 33 | "media_percent_visible": 1 34 | }) 35 | 36 | organic_seen_reels.append({ 37 | "item_id": item_id, 38 | "seen_states": seen_states 39 | }) 40 | 41 | return organic_seen_reels 42 | 43 | 44 | def get_seen_sponsored_reels(self): 45 | seen_sponsored_reels = [] 46 | for reel_id, reel_content in self.ad_reels["reels"].items(): 47 | 48 | item_id = str(reel_content["id"]).split("_")[0] 49 | 50 | if "items" in reel_content: 51 | media_id = reel_content["items"][0]["id"] 52 | 53 | seen_sponsored_reels.append({ 54 | "item_type": random.randint(2, 3), 55 | "inventory_source": "", 56 | "item_id": item_id, 57 | "seen_states": [{ 58 | "media_id": media_id, 59 | "media_time_spent": [random.randint(150, 2500)], 60 | "impression_timestamp": int(time.time()) - random.randint(10, 30), 61 | "media_percent_visible": 1 62 | }] 63 | 64 | }) 65 | 66 | return seen_sponsored_reels 67 | 68 | 69 | def simulate_viewing_reels(self): 70 | try: 71 | self.log("[INFO][{}] simulate viewing stories".format(self.username)) 72 | 73 | self.get_reels_media() 74 | 75 | self.log("[INFO][{}] got media reels".format(self.username)) 76 | time.sleep(random.randint(2, 5)) 77 | 78 | ad_reel_request = self.inject_ad_reels() 79 | 80 | self.ad_reels = self.get_json(ad_reel_request) 81 | 82 | self.log("[INFO][{}] got ad reels".format(self.username)) 83 | 84 | time.sleep(random.randint(5, 10)) 85 | 86 | seen_data = { 87 | "seen_sponsored_reels": self.get_seen_sponsored_reels(), 88 | "_uuid": self.device_id, 89 | "_uid": self.ds_user_id, 90 | "reels": self.get_all_seen_reels(), 91 | "live_vods_skipped": {}, 92 | "skip_timestamp_update": "1", 93 | "live_vods": {}, 94 | "container_module": "reel_feed_timeline", 95 | "reel_media_skipped": {}, 96 | "seen_organic_reels": self.get_all_organice_seen_reels() 97 | } 98 | 99 | request = self.post("media/seen/?reel=1&live_vod=0&reel_skipped=0&live_vod_skipped=0", 100 | data=self.sign_json(seen_data), headers=None, version=2) 101 | 102 | if request.status_code == 200: 103 | return True 104 | else: 105 | return False 106 | except Exception as e: 107 | 108 | self.log(e) 109 | self.log("[ERROR][{}] While simulate viewing reels".format(self.username)) 110 | 111 | 112 | def inject_ad_reels(self): 113 | post_data = { 114 | "is_media_based_enabled": "1", 115 | "_uuid": self.device_id, 116 | "_uid": self.ds_user_id, 117 | "ad_and_netego_request_information": [], 118 | "X-CM-Latency": "7.322", 119 | "ad_and_netego_realtime_information": [], 120 | "has_seen_aart_on": "0", 121 | "ad_request_index": "0", 122 | "is_inventory_based_request_enabled": "1", 123 | "is_ad_pod_enabled": "0", 124 | "X-CM-Bandwidth-KBPS": str(random.randint(7000, 10000)), 125 | "battery_level": random.randint(10, 100), 126 | "is_dark_mode": self.is_dark_mode, 127 | "tray_session_id": self.tray_session_id, 128 | "num_items_in_pool": "0", 129 | "is_charging": self.is_charging, 130 | "reel_position": "0", 131 | "viewer_session_id": self.generate_random_uuid(without_hyphen=True), 132 | "surface_q_id": "3303646759746658", 133 | "will_sound_on": "0", 134 | "tray_user_ids": [reel["user"]["pk"] for reel in self.reels["tray"]], 135 | "earliest_request_position": "0", 136 | "is_media_based_insertion_enabled": "1", 137 | "att_permission_status": "2", 138 | "phone_id": self.device_id, 139 | "entry_point_index": "0", 140 | "is_first_page": "1" 141 | } 142 | 143 | request = self.post("feed/injected_reels_media/", data=self.sign_json(post_data)) 144 | 145 | return request 146 | 147 | 148 | def get_reels_media(self): 149 | user_ids = [reel["user"]["pk"] for reel in self.reels["tray"]] 150 | self.reels_media_request_id = self.ds_user_id + "_" + self.generate_random_uuid() 151 | reels_media_data = { 152 | "user_ids": user_ids, 153 | "source": "feed_timeline_stories_netego", 154 | "_uuid": self.device_id, 155 | "tray_session_id": self.tray_session_id, 156 | "_uid": self.ds_user_id, 157 | "request_id": self.reels_media_request_id, 158 | "supported_capabilities_new": constants.SUPPORTED_CAPABILITIES, 159 | "inject_at_beginning": "0" 160 | } 161 | 162 | request = self.post("feed/reels_media/", data=self.sign_json(reels_media_data)) 163 | self.reels_media = self.get_json(request) 164 | return request 165 | 166 | 167 | def refresh_reels(self, reason="cold_start"): 168 | # reasons: cold_start, pagination, pull_to_refresh 169 | # feed/reels_tray/ 170 | request_id = self.ds_user_id + "_" + self.generate_random_uuid() 171 | try: 172 | self.tray_session_id = self.generate_random_uuid(without_hyphen=True) 173 | reels_data = { 174 | "reason": reason, 175 | "_uuid": self.device_id, 176 | "tray_session_id": self.tray_session_id, 177 | "_uid": self.ds_user_id, 178 | "request_id": request_id, 179 | "supported_capabilities_new": constants.SUPPORTED_CAPABILITIES, 180 | "timezone_offset": "7200" 181 | } 182 | 183 | request = self.post("feed/reels_tray/", data=self.sign_json(reels_data)) 184 | 185 | self.reels = self.get_json(request) 186 | self.logging_client_events.add_log(self.logging_client_events.get_reel_tray_refresh_log()) 187 | self.logging_client_events.add_log(self.logging_client_events.get_instagram_stories_request_sent_log(request_id)) 188 | self.logging_client_events.add_log(self.logging_client_events.get_instagram_stories_request_completed_log(request_id)) 189 | return request 190 | except: 191 | self.log("[ERROR][{}] while fetching reels".format(self.username)) 192 | return None 193 | -------------------------------------------------------------------------------- /ig_api/_requests.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | import requests 3 | import time 4 | import base64 5 | import random 6 | import urllib 7 | import json 8 | import zstd 9 | 10 | 11 | 12 | def check_request_status(self, request): 13 | if request.status_code == 403: 14 | self.status = "unauthorized" 15 | self.log("got unauthorized from: {}".format(self.username)) 16 | self.log(request.content) 17 | 18 | if self.logged_in: 19 | self.logged_in = False 20 | self.delete_session() 21 | 22 | if self.logged_in and request.status_code == 429: 23 | self.status = "too_many_requests" 24 | self.log("GOT 429, too many requests") 25 | 26 | if request.status_code == 400: 27 | 28 | self.log(request.content) 29 | json_response = json.loads(request.text) 30 | 31 | if json_response["message"] == "challenge_required": 32 | self.logged_in = False 33 | self.status = "challenge" 34 | if self.logged_in: 35 | self.delete_session() 36 | elif json_response["message"] == "feedback_required": 37 | self.status = "feedback_required" 38 | 39 | 40 | def check_set_headers(self, request): 41 | if "x-ig-set-www-claim" in request.headers: 42 | if self.claim != request.headers["x-ig-set-www-claim"]: 43 | self.save_claim(request.headers["x-ig-set-www-claim"]) 44 | self.claim = request.headers["x-ig-set-www-claim"] 45 | self.headers["x-ig-www-claim"] = str(self.claim) 46 | self.session.headers.update(self.headers) 47 | 48 | if "ig-set-ig-u-shbid" in request.headers: 49 | self.headers["ig-u-shbid"] = request.headers["ig-set-ig-u-shbid"] 50 | self.session.headers.update(self.headers) 51 | 52 | if "ig-set-ig-u-shbts" in request.headers: 53 | self.headers["ig-u-shbts"] = request.headers["ig-set-ig-u-shbts"] 54 | self.session.headers.update(self.headers) 55 | 56 | if "ig-set-ig-u-ig-direct-region-hint" in request.headers: 57 | self.headers["ig-u-ig-direct-region-hint"] = request.headers["ig-set-ig-u-ig-direct-region-hint"] 58 | self.session.headers.update(self.headers) 59 | 60 | if "ig-set-ig-u-rur" in request.headers: 61 | self.u_rur = request.headers["ig-set-ig-u-rur"] 62 | self.headers["ig-u-rur"] = self.u_rur 63 | self.session.headers.update(self.headers) 64 | 65 | if "ig-set-password-encryption-pub-key" in request.headers: 66 | self.publickey = request.headers["ig-set-password-encryption-pub-key"] 67 | 68 | if "ig-set-password-encryption-key-id" in request.headers: 69 | self.publickeyid = int(request.headers["ig-set-password-encryption-key-id"]) 70 | 71 | 72 | def post(self, endpoint, data=None, headers=None, version=1): 73 | try: 74 | if headers: 75 | self.session.headers.update(headers) 76 | 77 | self.update_pigeon_time() 78 | 79 | api_url = constants.API_URL if version == 1 else constants.API_URL_V2 80 | 81 | post_request = self.session.post(api_url + endpoint, data=data) 82 | 83 | self.log("[POST][{}] {} - {}".format(self.username, post_request.status_code, endpoint)) 84 | 85 | self.check_set_headers(post_request) 86 | 87 | self.check_request_status(post_request) 88 | 89 | return post_request 90 | 91 | except Exception as e: 92 | self.log("[ERROR][{}] - {}".format(self.username, endpoint)) 93 | self.log(e) 94 | return False 95 | 96 | 97 | def get(self, endpoint, params=None, headers=None): 98 | try: 99 | if headers: 100 | self.session.headers.update(headers) 101 | 102 | self.update_pigeon_time() 103 | 104 | get_request = self.session.get(constants.API_URL + endpoint, params=params) 105 | 106 | self.log("[GET][{}] {} - {}".format(self.username, get_request.status_code, endpoint)) 107 | 108 | self.check_set_headers(get_request) 109 | 110 | self.check_request_status(get_request) 111 | 112 | return get_request 113 | except: 114 | self.log("[ERROR][{}] - {}".format(self.username, endpoint)) 115 | return False 116 | 117 | 118 | def set_proxy(self, proxy): 119 | self.log("[INFO] set proxy {}".format(proxy)) 120 | self.session.proxies["http"] = proxy 121 | self.session.proxies["https"] = proxy 122 | 123 | 124 | def get_json(self, request): 125 | if request: 126 | try: 127 | #zstd compression 128 | if request.content.startswith(b"\x28\xb5\x2f\xfd"): 129 | return json.loads(zstd.decompress(request.content)) 130 | else: 131 | return json.loads(request.text) 132 | except Exception as e: 133 | self.log("[ERROR] While trying to parse json") 134 | self.log(e) 135 | return request.content 136 | return False 137 | 138 | 139 | def sign_json(self, raw_data): 140 | return "signed_body=SIGNATURE." + urllib.parse.quote_plus(json.dumps(raw_data, separators=(',', ':'))) 141 | 142 | def update_pigeon_time(self): 143 | self.headers["x-pigeon-rawclienttime"] = self.get_client_time() 144 | self.session.headers.update(self.headers) 145 | -------------------------------------------------------------------------------- /ig_api/_search.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | 3 | def register_recent_search_click(self, instagram_name, user_id): 4 | register_data = { 5 | "_uuid": self.device_id, 6 | "entity_type": "user", 7 | "entity_name": instagram_name, 8 | "entity_id": user_id 9 | } 10 | 11 | return self.post("fbsearch/register_recent_search_click/", data=register_data) 12 | 13 | 14 | def topsearch(self, query): 15 | params = { 16 | "context": "blended", 17 | "query": query, 18 | "search_surface": "top_search_page", 19 | "is_typeahead": "true", 20 | "timezone_offset": "7200" 21 | } 22 | 23 | response = self.get("fbsearch/topsearch_flat/", params=params) 24 | 25 | if response: 26 | return response 27 | else: 28 | return False 29 | 30 | 31 | def get_search_dynamic_sections(self): 32 | params = { 33 | "type": "blended", 34 | "rank_token": self.ds_user_id + "_" + self.generate_random_uuid() 35 | } 36 | 37 | response = self.get("fbsearch/nullstate_dynamic_sections/", params=params) 38 | 39 | return response 40 | 41 | -------------------------------------------------------------------------------- /ig_api/_session.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | import uuid 3 | import hashlib 4 | import os 5 | import random 6 | import json 7 | 8 | def generate_new_session_ids(self): 9 | self.pigeon_session_id = self.generate_random_uuid() 10 | self.session_id = self.generate_random_uuid() 11 | 12 | def generate_new_uuids(self): 13 | self.log("[INFO] generating fresh uuids ...") 14 | self.device_id = self.generate_random_uuid() 15 | self.device_token = str(self.get_seed("device_token")) + str(self.get_seed("device_token_second_part")) 16 | self.ios_voip_device_token = str(self.get_seed("ios_voip_device_token")) + str(self.get_seed("ios_voip_device_token")) 17 | self.generate_new_session_ids() 18 | 19 | def generate_random_uuid(self, without_hyphen=False): 20 | return str(uuid.uuid4()).replace("-", "") if without_hyphen else str(uuid.uuid4()).upper() 21 | 22 | def get_seed(self, *args): 23 | m = hashlib.md5() 24 | m.update(b"".join([arg.encode("utf-8") for arg in args])) 25 | return m.hexdigest() 26 | 27 | def restore_from_session_file(self): 28 | if os.path.isfile(self.session_file): 29 | with open(self.session_file, "r") as session_file: 30 | session_json = json.load(session_file) 31 | self.authorization = session_json["authorization"] 32 | self.password = session_json["password"] 33 | self.u_rur = session_json["ig-u-rur"] 34 | self.ds_user_id = session_json["ig-u-ds-user-id"] 35 | self.mid = session_json["x-mid"] 36 | self.device_id = session_json["device_id"] 37 | self.username = session_json["username"] 38 | self.is_dark_mode = session_json["is_dark_mode"] 39 | self.proxy = session_json["proxy"] 40 | self.session_id = session_json["session_id"] 41 | self.pigeon_session_id = session_json["pigeon_session_id"] 42 | self.device_token = session_json["device_token"] 43 | self.ios_voip_device_token = session_json["ios_voip_device_token"] 44 | self.log("[INFO] restore {} session, {} pigeon session".format(self.session_id, self.pigeon_session_id)) 45 | self.session_start = session_json["session_start"] 46 | if "x-ig-www-claim" in session_json: 47 | self.claim = session_json["x-ig-www-claim"] 48 | self.setup_logged_in_headers() 49 | return True 50 | else: 51 | return False 52 | 53 | 54 | def save_session_ids(self): 55 | if os.path.isfile(self.session_file): 56 | save_file = None 57 | with open(self.session_file, "r") as json_file: 58 | save_file = json.loads(json_file.read()) 59 | 60 | save_file["session_id"] = self.session_id 61 | save_file["pigeon_session_id"] = self.pigeon_session_id 62 | 63 | with open(self.session_file, "w") as json_file: 64 | json.dump(save_file, json_file) 65 | 66 | 67 | def save_login_session(self, login_headers, claim=None): 68 | self.log("[INFO][{}] setup sso headers".format(self.username)) 69 | self.authorization = login_headers["ig-set-authorization"] 70 | self.u_rur = login_headers["ig-set-ig-u-rur"] 71 | self.ds_user_id = login_headers["ig-set-ig-u-ds-user-id"] 72 | self.headers["authorization"] = self.authorization 73 | self.headers["ig-u-rur"] = self.u_rur 74 | self.headers["ig-u-ds-user-id"] = self.ds_user_id 75 | 76 | self.session.headers.update(self.headers) 77 | self.is_dark_mode = random.randint(0, 1) 78 | self.is_charging = random.randint(0, 1) 79 | 80 | save_file = { 81 | "authorization": self.authorization, 82 | "ig-u-rur": self.u_rur, 83 | "ig-u-ds-user-id": self.ds_user_id, 84 | "x-mid": self.mid, 85 | "device_id": self.device_id, 86 | "device_token": self.device_token, 87 | "ios_voip_device_token": self.ios_voip_device_token, 88 | "username": self.username, 89 | "password": self.password, 90 | "is_dark_mode": self.is_dark_mode, 91 | "proxy": self.proxy, 92 | "session_id": self.session_id, 93 | "pigeon_session_id": self.pigeon_session_id, 94 | "session_start": self.session_start 95 | } 96 | 97 | with open(self.session_file, "w") as json_file: 98 | json.dump(save_file, json_file) 99 | 100 | 101 | def save_claim(self, claim): 102 | if os.path.isfile(self.session_file): 103 | save_file = None 104 | with open(self.session_file, "r") as json_file: 105 | save_file = json.loads(json_file.read()) 106 | 107 | save_file["x-ig-www-claim"] = claim 108 | 109 | with open(self.session_file, "w") as json_file: 110 | json.dump(save_file, json_file) 111 | 112 | 113 | def delete_session(self): 114 | self.log("[API][{}] session broken, deleting session".format(self.username)) 115 | if os.path.isfile(self.session_file): 116 | os.rename(self.session_file, "sessions/removed/" + self.username + "_session.json") 117 | -------------------------------------------------------------------------------- /ig_api/_user.py: -------------------------------------------------------------------------------- 1 | from . import constants 2 | 3 | 4 | def convert_username_to_userid(self, username): 5 | request = self.search_username(username) 6 | if request: 7 | return str(request["user"]["pk"]) 8 | else: 9 | return None 10 | 11 | 12 | def get_user_feed(self, user_id): 13 | params = { 14 | "session_id": self.session_id, 15 | "seen_organic_items": self.simulate_seen_organic_items(), 16 | "source": "grid", 17 | "exclude_comment": True 18 | } 19 | 20 | return self.get("feed/user/{}/".format(user_id), params=params) 21 | 22 | 23 | def get_user_info(self, user_id): 24 | params = { 25 | "device_id": self.device_id 26 | } 27 | 28 | return self.get("users/{}/info/".format(user_id), params=params) 29 | 30 | 31 | def get_user_story(self, user_id): 32 | params = { 33 | "supported_capabilities_new": constants.SUPPORTED_CAPABILITIES 34 | } 35 | 36 | return self.get("feed/user/{}/story/".format(user_id), params=params) 37 | 38 | 39 | def get_user_highlights(self, user_id): 40 | params = { 41 | "supported_capabilities_new": constants.SUPPORTED_CAPABILITIES 42 | } 43 | 44 | return self.get("highlights/{}/highlights_tray/".format(user_id), params=params) 45 | 46 | 47 | def get_friendship(self, user_id): 48 | return self.get("friendships/show/{}/".format(user_id)) 49 | 50 | 51 | def search_username(self, username): 52 | response = self.get("users/" + username + "/usernameinfo/") 53 | 54 | if response.status_code == 200: 55 | return self.get_json(response) 56 | else: 57 | return None 58 | 59 | 60 | def follow(self, user_id): 61 | follow_data = { 62 | "_uuid": self.device_id, 63 | "_uid": self.ds_user_id, 64 | "user_id": user_id, 65 | "device_id": self.device_id, 66 | "container_module": "newsfeed_you" 67 | } 68 | 69 | request = self.post("friendships/create/{}/".format(user_id), data=self.sign_json(follow_data)) 70 | 71 | return request 72 | -------------------------------------------------------------------------------- /ig_api/constants.py: -------------------------------------------------------------------------------- 1 | API_URL = "https://i.instagram.com/api/v1/" 2 | API_URL_V2 = "https://i.instagram.com/api/v2/" 3 | 4 | LOGGING_URL = "https://graph.instagram.com/logging_client_events" 5 | 6 | APP_ID = "124024574287414" 7 | 8 | APP_VER = "197.0.0.20.119 (305020938)" 9 | 10 | USER_AGENT = "Instagram 197.0.0.20.119 (iPhone10,6; iOS 14_4_2; de_DE; de-DE; scale=3.00; 1125x2436; 275424340) AppleWebKit/420+" 11 | 12 | BLOKS_VERSION = "29d3248efc1cfc10a0dbafd84eb58fd7eebc6b99e626c5bfa0fe615b8ff784d9" 13 | 14 | 15 | BATCH_QUERY = "Query IGQPQuery: Viewer {\n viewer() {\n eligible_promotions.ig_parameters().surface_nux_id().external_gating_permitted_qps().include_holdouts(true).trigger_name().supports_client_filters(true).trigger_context_v2() {\n edges {\n log_eligibility_waterfall,\n is_holdout,\n priority,\n time_range {\n start,\n end\n },\n node {\n id,\n promotion_id,\n logging_data,\n max_impressions,\n triggers,\n template {\n name,\n parameters {\n name,\n required,\n string_value,\n bool_value,\n color_value\n }\n },\n contextual_filters {\n clause_type,\n filters {\n filter_type,\n passes_if_not_supported,\n unknown_action,\n value {\n name,\n required,\n bool_value,\n int_value,\n string_value\n },\n extra_datas {\n name,\n required,\n bool_value,\n int_value,\n string_value\n }\n },\n clauses {\n clause_type,\n filters {\n filter_type,\n passes_if_not_supported,\n unknown_action,\n value {\n name,\n required,\n bool_value,\n int_value,\n string_value\n },\n extra_datas {\n name,\n required,\n bool_value,\n int_value,\n string_value\n }\n },\n clauses {\n clause_type,\n filters {\n filter_type,\n passes_if_not_supported,\n unknown_action,\n value {\n name,\n required,\n bool_value,\n int_value,\n string_value\n },\n extra_datas {\n name,\n required,\n bool_value,\n int_value,\n string_value\n }\n }\n }\n }\n },\n creatives {\n title {\n text\n },\n content {\n text\n },\n footer {\n text,\n aggregated_ranges {\n count,\n length,\n offset\n },\n ranges {\n entity_is_weak_reference,\n length,\n offset,\n override_uri,\n entity {\n url\n }\n }\n },\n social_context {\n text\n },\n social_context_images,\n primary_action{\n title {\n text\n },\n url,\n limit,\n dismiss_promotion\n },\n secondary_action {\n title {\n text\n },\n url,\n limit,\n dismiss_promotion\n },\n dismiss_action{\n title {\n text\n },\n url,\n limit,\n dismiss_promotion\n },\n image.scale() {\n uri,\n width,\n height\n },\n dark_mode_image.scale() {\n uri,\n width,\n height\n }\n },\n is_server_force_pass\n }\n }\n }\n }\n}\n" 16 | 17 | BATCHES = [ 18 | { 19 | "id": "4669", 20 | "triggers": [["instagram_inbox_header"], ["instagram_other_logged_in_user_id_loaded"], ["instagram_profile_page"], ["instagram_other_profile_page_header"], ["instagram_other_profile_page_header"]] 21 | }, 22 | { 23 | "id": "8971", 24 | "triggers": [["instagram_self_profile_floating_banner_prompt"]] 25 | }, 26 | { 27 | "id": "5736", 28 | "triggers": [["instagram_explore_prompt"],["instagram_profile_page_prompt"],["instagram_other_profile_page_prompt"]] 29 | }, 30 | { 31 | "id": "5829", 32 | "triggers": [["instagram_profile_promotions_cta_tooltip", "instagram_self_profile_tooltip"], ["instagram_other_checkout_profile_tooltip", "instagram_other_profile_tooltip"],["instagram_explore_tooltip"]] 33 | } 34 | ] 35 | 36 | HEADERS = { 37 | "priority": "u=2, i", 38 | "ig-intended-user-id": "0", 39 | "x-ig-connection-speed": "3kbps", 40 | #"x-ig-device-id": "E5592027-D411-4C9C-8943-777069389330", 41 | "x-ig-app-startup-country": "unknown", 42 | "x-ig-capabilities": "36r/Fx8=", 43 | #"x-pigeon-rawclienttime": "1626967292.90395", 44 | "x-ig-device-locale": "de-DE", 45 | "x-ig-abr-connection-speed-kbps": "0", 46 | "accept-language": "de-DE;q=1.0", 47 | "user-agent": USER_AGENT, 48 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 49 | "x-ig-app-locale": "de", 50 | "x-ig-bandwidth-speed-kbps": "0.000", 51 | "x-ig-mapped-locale": "de_DE", 52 | "x-ig-www-claim": "0", 53 | #"x-mid": "YPgzUwAAAAGqbPZDdG5DILY5S-Dc", 54 | "x-bloks-is-panorama-enabled": "true", 55 | "x-bloks-version-id": BLOKS_VERSION, 56 | #"x-pigeon-session-id": "1B8AE061-D4ED-4957-9FC2-6970A6AC8D83", 57 | "x-ig-app-id": APP_ID, 58 | "x-ig-connection-type": "WiFi", 59 | "x-tigon-is-retry": "False", 60 | "accept-encoding": "zstd, gzip, deflate", 61 | "x-fb-http-engine": "Liger", 62 | "x-fb-client-ip": "True", 63 | "x-fb-server-cluster": "True" 64 | } 65 | 66 | 67 | ACCESS_TOKEN = "84a456d620314b6e92a16d8ff1c792dc" 68 | 69 | SUPPORTED_CAPABILITIES = [ 70 | { 71 | "name": "hair_segmentation", 72 | "value": "hair_segmentation_enabled" 73 | }, 74 | { 75 | "name": "body_tracking", 76 | "value": "body_tracking_enabled" 77 | }, 78 | { 79 | "name": "GYROSCOPE", 80 | "value": "GYROSCOPE_ENABLED" 81 | }, 82 | { 83 | "name": "HALF_FLOAT_RENDER_PASS", 84 | "value": "HALF_FLOAT_RENDER_PASS_ENABLED" 85 | }, 86 | { 87 | "name": "VERTEX_TEXTURE_FETCH", 88 | "value": "VERTEX_TEXTURE_FETCH_ENABLED" 89 | }, 90 | { 91 | "name": "DEPTH_SHADER_READ", 92 | "value": "DEPTH_SHADER_READ_ENABLED" 93 | }, 94 | { 95 | "name": "MULTIPLE_RENDER_TARGETS", 96 | "value": "MULTIPLE_RENDER_TARGETS_ENABLED" 97 | }, 98 | { 99 | "name": "SUPPORTED_SDK_VERSIONS", 100 | "value": "73.0,74.0,75.0,76.0,77.0,78.0,79.0,80.0,81.0,82.0,83.0,84.0,85.0,86.0,87.0,88.0,89.0,90.0,91.0,92.0,93.0,94.0,95.0,96.0,97.0,98.0,99.0,100.0,101.0,102.0,103.0,104.0,105.0,106.0,107.0,108.0" 101 | }, 102 | { 103 | "name": "COMPRESSION", 104 | "value": "PVR_COMPRESSION" 105 | }, 106 | { 107 | "name": "FACE_TRACKER_VERSION", 108 | "value":"14" 109 | }] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi>=2019.11.28 2 | chardet>=3.0.4 3 | future>=0.18.2 4 | huepy>=1.2.1 5 | idna>=2.8 6 | pysocks>=1.7.1 7 | pytz>=2019.3 8 | requests>=2.22.0 9 | requests-toolbelt>=0.9.1 10 | responses>=0.10.9 11 | schedule>=0.6.0 12 | six>=1.14.0 13 | tqdm>=4.41.1 14 | urllib3>=1.25.7 15 | mock>=3.0.5 16 | moviepy>=1.0.1 17 | Pillow>=6.2.2 18 | pytest>=4.6.9 19 | rsa>=4.0 20 | pycryptodome>=3.9.7 21 | pycryptodomex>=3.9.7 22 | python-dotenv 23 | bs4 24 | imap_tools 25 | html5lib 26 | zstd -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='ig_api', version='1.0', packages=find_packages()) --------------------------------------------------------------------------------