├── sessions └── sessionsfolder ├── videos └── videocachefolder ├── images └── imagechachefolder ├── dVideo.py ├── requirements.txt ├── language.json ├── README.md ├── ImageUtils.py ├── InstagramDownloader.py └── Api.py /sessions/sessionsfolder: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /videos/videocachefolder: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/imagechachefolder: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dVideo.py: -------------------------------------------------------------------------------- 1 | 2 | class dVideo(object): 3 | def __init__(self, header, upload_id, recipient): 4 | self.username = None 5 | self.download_from = None 6 | self.header = header 7 | self.upload_id = upload_id 8 | self.recipient = recipient -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.11.28 2 | chardet==3.0.4 3 | decorator==4.4.1 4 | idna==2.8 5 | imageio==2.8.0 6 | imageio-ffmpeg==0.4.1 7 | moviepy==1.0.1 8 | numpy==1.18.1 9 | Pillow==9.0.0 10 | proglog==0.1.9 11 | requests==2.22.0 12 | requests-toolbelt==0.9.1 13 | setuptools==45.2.0 14 | tqdm==4.43.0 15 | urllib3==1.26.5 16 | -------------------------------------------------------------------------------- /language.json: -------------------------------------------------------------------------------- 1 | { 2 | "requested": "I'm not following this account yet, however I saved your request of account. We will determine later if we should follow.", 3 | "images_not_supported": "Images are not supported yet.", 4 | "video_to_long": "This video is longer than 60s therefore we're unable to send it via dms.", 5 | "links_not_supported": "Links are currently not supported. If its a video resend it using the arrow button.", 6 | "blocked": "The post you were sending is either a post from someone who blocked us or an IGTV post. IGTV is not supported.", 7 | "deleted": "That account/post is currently shadowbanned. Try again later." 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # instaadownload 2 | 3 | Requirements: 4 | - [Python 3](https://www.python.org/downloads/) 5 | - [FFMPEG](https://ffmpeg.org/download.html) (installed and added to the PATH variable) 6 | 7 | Setup: 8 | 1. Clone the repo 9 | 2. Edit the InstagramDownloader.py file. At the bottom should be a username and a password variable 10 | 3. search for self.admins and put in the usernames you want to be admins 11 | 4. install the requirements (pip install -r requirements.txt) in the correct folder 12 | 5. run python InstagramDownloader.py and wait for 3 logins to happen 13 | 14 | 15 | ## Admincommands: (if youre in the self.admins) 16 | ### Priority 17 | - !upgrade [user] 18 | - !downgrade [user] 19 | 20 | ### Antispam: 21 | - !remove [username] (removes queue items from given username) 22 | 23 | ### Other 24 | - !delay - avg delay by priority level 25 | - !reset - resets the delay log 26 | 27 | - !most - user with most items in queue (used to find spammers) 28 | 29 | - !day - downloads of the day (bugging and sometimes switches the current day and the next day) 30 | -------------------------------------------------------------------------------- /ImageUtils.py: -------------------------------------------------------------------------------- 1 | 2 | import struct 3 | import imghdr 4 | 5 | 6 | def getImageSize(fname): 7 | with open(fname, 'rb') as fhandle: 8 | head = fhandle.read(24) 9 | if len(head) != 24: 10 | raise RuntimeError("Invalid Header") 11 | if imghdr.what(fname) == 'png': 12 | check = struct.unpack('>i', head[4:8])[0] 13 | if check != 0x0d0a1a0a: 14 | raise RuntimeError("PNG: Invalid check") 15 | width, height = struct.unpack('>ii', head[16:24]) 16 | elif imghdr.what(fname) == 'gif': 17 | width, height = struct.unpack('H', fhandle.read(2))[0] - 2 29 | # We are at a SOFn block 30 | fhandle.seek(1, 1) # Skip `precision' byte. 31 | height, width = struct.unpack('>HH', fhandle.read(4)) 32 | else: 33 | raise RuntimeError("Unsupported format") 34 | return width, height 35 | 36 | -------------------------------------------------------------------------------- /InstagramDownloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | import time 4 | import json 5 | import pickle 6 | import threading 7 | import random 8 | 9 | import requests 10 | from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip 11 | 12 | from Api import InstagramAPI 13 | from Api import InstagramLogin 14 | 15 | from pathlib import Path 16 | 17 | import logging 18 | logging.basicConfig( 19 | format='%(asctime)s %(levelname)-8s %(message)s', 20 | level=logging.INFO, 21 | datefmt='%Y-%m-%d %H:%M:%S') 22 | 23 | from logging.handlers import TimedRotatingFileHandler 24 | logname = "insta.log" 25 | handler = TimedRotatingFileHandler(logname, when="midnight", interval=1) 26 | handler.suffix = "%Y%m%d" 27 | formatter = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s") 28 | handler.setFormatter(formatter) 29 | logging.getLogger().addHandler(handler) 30 | 31 | import pickle 32 | 33 | class Config(object): 34 | def __init__(self, filename): 35 | self.filename = filename 36 | self.lock = threading.Lock() 37 | self.delaylist = {} 38 | 39 | #self.save_worker = threading.Thread(target=self.save_worker_func) 40 | #self.save_worker.start() 41 | if not os.path.exists(filename): 42 | self.config = {} 43 | self.config["user_count"] = 0 44 | self.config["users"] = [] 45 | self.config["stats"] = [] 46 | self.config["request_users"] = [] 47 | self.save_config() 48 | else: 49 | self.file = open(filename) 50 | self.config = json.load(self.file) 51 | 52 | 53 | self.language_file = Path("./language.json") 54 | if not os.path.exists(self.language_file): 55 | lng = {} 56 | with open(self.language_file, "w") as language: 57 | json.dump(lng, language, indent=4) 58 | 59 | 60 | 61 | 62 | def has_key(self, json, key): 63 | try: 64 | item = json[key] 65 | return True 66 | except Exception: 67 | return False 68 | 69 | def find_index(self, json_object, key, name): 70 | index = 0 71 | for dict in json_object: 72 | try: 73 | if dict[key] == name: 74 | return index 75 | except : 76 | pass 77 | index+=1 78 | return -1 79 | 80 | def save_config(self): 81 | self.lock.acquire() 82 | with open(self.filename, "w") as config_file: 83 | json.dump(self.config, config_file) 84 | self.lock.release() 85 | 86 | def create_list_item(self, json_list, item): 87 | self.lock.acquire() 88 | json_list.append(item) 89 | self.lock.release() 90 | 91 | def clean(self): 92 | list = [] 93 | new_users = [] 94 | for u in self.config["users"]: 95 | if not u["userid"] in list: 96 | new_users.append(u) 97 | list.append(u["userid"]) 98 | self.config["users"] = new_users 99 | return len(self.config["users"]) - len(new_users) 100 | 101 | # DAY STATS 102 | 103 | def create_day(self, day): 104 | new_day = {"day": str(day), 105 | "date": datetime.now().strftime("%Y-%m-%d"), 106 | "downloads": 0} 107 | self.create_list_item(self.config["stats"], new_day) 108 | 109 | return len(self.config["stats"]) - 1 110 | 111 | def get_day_unsafe(self): 112 | day = int((time.time() - time.time() % 86400) / 86400) 113 | index = self.find_index(self.config["stats"], "day", str(day)) 114 | if index == -1: 115 | index = self.create_day(day) 116 | 117 | return self.config["stats"][index] 118 | 119 | def get_day(self): 120 | day = self.get_day_unsafe() 121 | return json.loads(json.dumps(day)) 122 | 123 | def day_add_download(self): 124 | day = self.get_day_unsafe() 125 | self.lock.acquire() 126 | day["downloads"] += 1 127 | self.lock.release() 128 | 129 | # USER STATS 130 | 131 | def create_user(self, userid, username): 132 | i = self.find_index(self.config["users"], "userid", str(userid)) 133 | if i != -1: 134 | self.config["users"][i]["username"] = username 135 | 136 | return i 137 | 138 | 139 | new_user = {"userid": str(userid), 140 | "username": username, 141 | "downloads": 0, 142 | "priority": 1, 143 | "latest_item_time": 0, 144 | "downloaded_from": []} 145 | 146 | self.lock.acquire() 147 | self.config["user_count"] += 1 148 | self.lock.release() 149 | self.create_list_item(self.config["users"], new_user) 150 | 151 | 152 | return len(self.config["users"]) - 1 153 | 154 | def get_user_unsafe(self, userid, create=False, username = ""): 155 | index = self.find_index(self.config["users"], "userid", str(userid)) 156 | if index == -1: 157 | if create and username != "": 158 | self.create_user(userid, username) 159 | else: 160 | return None 161 | return self.config["users"][index] 162 | 163 | def get_user(self, userid): 164 | user = self.get_user_unsafe(userid) 165 | if user == None: 166 | return None 167 | return json.loads(json.dumps(user)) 168 | 169 | def user_add_download(self, userid, username, downloaded_from): 170 | user = self.get_user_unsafe(userid, create=True, username=username) 171 | if user == None: 172 | return False 173 | self.lock.acquire() 174 | user["downloads"] += 1 175 | self.lock.release() 176 | 177 | index = self.find_index(user["downloaded_from"], "username", downloaded_from) 178 | if index == -1: 179 | self.create_list_item(user["downloaded_from"], {"username": downloaded_from, 180 | "downloads": 1}) 181 | else: 182 | self.lock.acquire() 183 | user["downloaded_from"][index]["downloads"] += 1 184 | self.lock.release() 185 | 186 | return True 187 | 188 | def user_set_itemtime(self, userid, username, item_time): 189 | user = self.get_user_unsafe(userid, create=True, username=username) 190 | self.lock.acquire() 191 | user["latest_item_time"] = item_time 192 | self.lock.release() 193 | 194 | def upgrade_priority(self, username): 195 | index = self.find_index(self.config["users"], "username", username) 196 | if index == -1: 197 | return "none" 198 | user = self.config["users"][index] 199 | user["priority"] += 1 200 | 201 | return user["priority"] 202 | 203 | 204 | def downgrade_priority(self, username): 205 | index = self.find_index(self.config["users"], "username", username) 206 | if index == -1: 207 | return "none" 208 | user = self.config["users"][index] 209 | user["priority"] -= 1 210 | 211 | return user["priority"] 212 | # REQUESTED STATS 213 | 214 | def create_requested(self, username): 215 | new_requested = {"username": username, 216 | "requested": 0, 217 | "requestors": []} 218 | self.create_list_item(self.config["request_users"], new_requested) 219 | 220 | def get_requested_unsafe(self, username): 221 | index = self.find_index(self.config["request_users"], "username", username) 222 | if index == -1: 223 | self.create_requested(username) 224 | index == len(self.config["request_users"]) - 1 225 | return self.config["request_users"][index] 226 | 227 | def get_requested(self, username): 228 | requested = self.get_requested_unsafe(username) 229 | if requested == None: 230 | return None 231 | return json.loads(json.dumps(requested)) 232 | 233 | def add_get_requestor(self, requestor_json, userid): 234 | index = self.find_index(requestor_json["requestors"], "userid", userid) 235 | if index == -1: 236 | requestor = {"userid": userid, 237 | "requested": 0} 238 | self.create_list_item(requestor_json["requestors"], requestor) 239 | self.lock.acquire() 240 | requestor_json["requested"] +=1 241 | self.lock.release() 242 | index == len(requestor_json["requestors"]) - 1 243 | return requestor_json["requestors"][index] 244 | 245 | def remove_requestor(self, username): 246 | for thing in self.config["request_users"]: 247 | if thing["username"] == username: 248 | self.lock.acquire() 249 | self.config["request_users"].remove(thing) 250 | self.lock.release() 251 | 252 | def requested_add_request(self, username, requested_by_userid): 253 | requested = self.get_requested_unsafe(username) 254 | requestor = self.add_get_requestor(requested, str(requested_by_userid)) 255 | self.lock.acquire() 256 | requestor["requested"] += 1 257 | self.lock.release() 258 | 259 | # Language 260 | def get_text(self, text_key): 261 | file = open(self.language_file) 262 | lng = json.load(file) 263 | if self.has_key(lng, text_key): 264 | return lng[text_key] 265 | else: 266 | return "unknown, dm the dev or sth pls" 267 | 268 | def add_text(self, text_key, text): 269 | file = open(self.language_file) 270 | lng = json.load(file) 271 | with open(self.language_file, "w+") as language: 272 | lng[text_key] = text 273 | json.dump(lng, language, indent=4) 274 | 275 | #DELAY 276 | def reset_delay(self): 277 | self.delaylist = {} 278 | 279 | def capture_delay(self, delay, priority): 280 | if not self.has_key(self.delaylist, priority): 281 | self.delaylist[priority] = [] 282 | 283 | if len(self.delaylist[priority]) >= 20: 284 | self.delaylist[priority].remove(self.delaylist[priority][0]) 285 | 286 | self.delaylist[priority].append(delay) 287 | 288 | def get_delay(self, priority): 289 | if not self.has_key(self.delaylist, priority): 290 | self.delaylist[priority] = [] 291 | 292 | delay = 0 293 | i = 0 294 | for d in self.delaylist[priority]: 295 | delay += d 296 | i += 1 297 | if i > 0: 298 | delay = delay / i 299 | else: 300 | delay = 0 301 | return int(delay * 10) // 10 302 | 303 | class Uploader(object): 304 | def __init__(self, API, config, number, sessionpath): 305 | self.api = API 306 | self.cfg = config 307 | self.number = number 308 | self.sessionpath = sessionpath 309 | self.upload_worker = threading.Thread(target=self.upload_worker_func) 310 | self.running = False 311 | self.queue = [] 312 | 313 | self.sleep = [0,60] 314 | 315 | self.counter = 0 316 | self.errors = 0 317 | 318 | def start(self): 319 | self.running = True 320 | self.upload_worker.start() 321 | 322 | def stop(self): 323 | self.running = False 324 | 325 | 326 | def extract_priority(self, json): 327 | try: 328 | return int(json["priority"]) 329 | except KeyError: 330 | return 0 331 | 332 | def queue_contains(self, itemid): 333 | for item in self.queue: 334 | if item["item_id"] == itemid: 335 | return True 336 | return False 337 | 338 | def queue_contains_post(self, media_id, username): 339 | for item in self.queue: 340 | if item["username"] == username: 341 | try: 342 | if item["media_id"] == media_id: 343 | return True 344 | except Exception as e: 345 | pass 346 | return False 347 | 348 | 349 | def reload_api(self): 350 | uploaderpath = self.sessionpath 351 | 352 | if os.path.exists(uploaderpath): 353 | self.api = pickle.load(open(uploaderpath, "rb")) 354 | logging.info("Reloaded uploader {0}".format(self.sessionpath)) 355 | else: 356 | logging.warning("Failed to reload uploader") 357 | 358 | 359 | def send_media(self, url, itemid, mediatype, media_id, userid, username, download_from, sent, cut=False): 360 | user = self.cfg.get_user(userid) 361 | 362 | 363 | item = {"priority": user["priority"], 364 | "url": url, 365 | "item_id": itemid, 366 | "media_type": mediatype, 367 | "media_id": media_id, 368 | "cut": cut, 369 | "sent": sent, 370 | "userid": userid, 371 | "username": username, 372 | "download_from": download_from} 373 | 374 | self.queue.append(item) 375 | 376 | def upload_video(self, item, filename): 377 | full_path = str(Path("./videos/{f}.mp4".format(f=filename))) 378 | video = requests.get(item["url"]) 379 | open(full_path, "wb").write(video.content) 380 | if item["cut"] == True: 381 | new_path = str(Path("./videos/{f}_cut.mp4".format(f=filename))) 382 | ffmpeg_extract_subclip(full_path, 0, 59, targetname=new_path) 383 | os.remove(full_path) 384 | full_path = new_path 385 | xd = self.api.prepare_direct_video(item["userid"], full_path) 386 | try: 387 | self.api.send_direct_video(xd) 388 | except Exception as e: 389 | rnd = random.randint(1, 5) 390 | time.sleep(rnd) 391 | self.api.send_direct_video(xd) 392 | 393 | user = self.cfg.get_user(item["userid"]) 394 | if user["downloads"] == 0: 395 | self.api.sendMessage(str(item["userid"]), "This bot was developed by @instaagroup.\n Check us out or modify the code on GitHub!") 396 | self.counter += 1 397 | logging.info("Welcomed {u}!".format(u=item["username"])) 398 | cfg.user_add_download(item["userid"], item["username"], item["download_from"]) 399 | cfg.day_add_download() 400 | logging.info("{d} successfully downloaded a video from {u}".format(d=item["username"], u=item["download_from"])) 401 | 402 | logging.info("Timespan since sent video: {0}ms".format(str((time.time() * 1000 // 1) - item["sent"] // 1000))) 403 | self.cfg.capture_delay(int(time.time() - item["sent"] // 1000000), item["priority"]) 404 | if os.path.exists(full_path): 405 | os.remove(full_path) 406 | def upload_photo(self, item, filename): 407 | full_path = str(Path("./images/{f}.jpg".format(f=filename))) 408 | video = requests.get(item["url"]) 409 | open(full_path, "wb").write(video.content) 410 | xd = self.api.prepare_direct_image(item["userid"], full_path) 411 | try: 412 | self.api.send_direct_image(xd) 413 | except Exception as e: 414 | rnd = random.randint(1, 20) 415 | time.sleep(rnd) 416 | self.api.send_direct_image(xd) 417 | user = self.cfg.get_user(item["userid"]) 418 | if user["downloads"] == 0: 419 | self.api.sendMessage(str(item["userid"]), "This bot was developed by @instaagroup.\n Check us out or modify the code on GitHub!") 420 | self.counter += 1 421 | logging.info("Welcomed {u}!".format(u=item["username"])) 422 | cfg.user_add_download(item["userid"], item["username"], item["download_from"]) 423 | cfg.day_add_download() 424 | logging.info("{d} successfully downloaded a photo from {u}".format(d=item["username"], u=item["download_from"])) 425 | 426 | logging.info("Timespan since sent video: {0}ms".format(str((time.time() * 1000 // 1) - item["sent"] // 1000))) 427 | self.cfg.capture_delay(int(time.time() - item["sent"] // 1000000), item["priority"]) 428 | if os.path.exists(full_path): 429 | os.remove(full_path) 430 | 431 | def upload_worker_func(self): 432 | while self.running: 433 | if len(self.queue) == 0: 434 | time.sleep(1) 435 | continue 436 | 437 | self.queue.sort(key=self.extract_priority, reverse=True) 438 | 439 | item = None 440 | filename = None 441 | full_path = "" 442 | try: 443 | item = self.queue[0] 444 | if item["priority"] > 1: 445 | self.sleep = [5, 15] 446 | rnd = random.randint(self.sleep[0], self.sleep[1]) 447 | time.sleep(rnd) 448 | filename = str(int(round(time.time() * 10000))) 449 | if item["media_type"] == 2: 450 | self.upload_video(item, filename) 451 | elif item["media_type"] == 1: 452 | self.upload_photo(item, filename) 453 | 454 | self.sleep = [10, 30] 455 | self.queue.remove(item) 456 | except Exception as e: 457 | if os.path.exists(full_path): 458 | os.remove(full_path) 459 | logging.error("Error with {u} {er}".format(er=str(e), u=item["username"])) 460 | if not "few minutes" in str(e): 461 | self.queue.remove(item) 462 | self.reload_api() 463 | self.sleep = [30, 120] 464 | time.sleep(1) 465 | 466 | 467 | class InboxItem(object): 468 | def __init__(self, json): 469 | self.json = json 470 | self.item = json["items"][0] 471 | self.users = json["users"] 472 | self.is_group = json["is_group"] 473 | self.item_type = self.item["item_type"] 474 | self.author_id = self.item["user_id"] 475 | self.timestamp = self.item["timestamp"] 476 | 477 | 478 | def get_media(self): 479 | location = self.item[self.item_type] 480 | if self.item_type == "story_share": 481 | location = location["media"] 482 | elif self.item_type == "felix_share": 483 | location = location["video"] 484 | 485 | return location 486 | 487 | def get_media_type(self): 488 | if self.item_type != "media_share" and self.item_type != "story_share" and self.item_type != "felix_share" : 489 | return 0 490 | 491 | return self.get_media()["media_type"] 492 | 493 | def get_item_poster(self): 494 | type = self.get_media_type() 495 | if type == 0: 496 | return self.author_id 497 | name = "~unkown" 498 | if 0 < type < 3: 499 | name = self.get_media()["user"]["username"] 500 | if type == 8: 501 | name = self.item["media_share"]["user"]["username"] 502 | return name 503 | 504 | def get_video_url(self): 505 | url = self.get_media()["video_versions"][0]["url"] 506 | return url 507 | 508 | def get_image_url(self): 509 | url = self.get_media()["image_versions2"]["candidates"][0]["url"] 510 | return url 511 | 512 | def get_multipost_url(self, items, num): 513 | item = items[num - 1] 514 | if(item["type"] == 2): 515 | return item["url"] 516 | else: 517 | return "error" 518 | 519 | def get_multipost_length(self): 520 | return len(self.item["media_share"]["carousel_media"]) 521 | 522 | def get_multipost_json(self): 523 | jf = {} 524 | jf["author_id"] = self.author_id 525 | jf["download_from"] = self.get_item_poster() 526 | jf["items"] = [] 527 | for x in self.item["media_share"]["carousel_media"]: 528 | if(x["media_type"] == 2): 529 | jf["items"].append({"type": x["media_type"], 530 | "url": x["video_versions"][0], 531 | "duration": x["video_duration"]}) 532 | else: 533 | jf["items"].append({"type": x["media_type"], 534 | "url": x["image_versions2"][0]}) 535 | return jf 536 | 537 | 538 | class InboxHandler(object): 539 | def __init__(self, API, config, uploader, d_uploader): 540 | self.api = API 541 | self.cfg = config 542 | self.count = 0 543 | self.uploader_list = uploader 544 | self.uploader = self.uploader_list[0] 545 | 546 | self.admins = ["instaagroup", "rxc0.i", "dome271"] 547 | 548 | self.first = True 549 | 550 | def is_inbox_valid(self, json_inbox): 551 | millis = time.time() // 1000 552 | try: 553 | snapshot = json_inbox["snapshot_at_ms"] // 1000000 554 | except Exception: 555 | snapshot = 0 556 | 557 | return millis == snapshot 558 | 559 | def is_multipost_expected(self, userid): 560 | return os.path.exists(Path("./multi/{u}.json".format(u=str(userid)))) 561 | 562 | def run(self): 563 | while True: 564 | try: 565 | try: 566 | self.handle_inbox() 567 | time.sleep(15) 568 | except Exception as e: 569 | logging.error("Handle Inbox crashed: {0}".format(str(e))) 570 | time.sleep(10) 571 | except: 572 | self.cfg.save_config() 573 | time.sleep(10) 574 | for u in self.uploader_list: 575 | u.running = False 576 | logging.error("dead, oof") 577 | 578 | def get_uploader(self): 579 | upl = self.uploader_list[0] 580 | for u in self.uploader_list: 581 | if len(upl.queue) > len(u.queue): 582 | upl = u 583 | 584 | return upl 585 | 586 | def is_post_queued(self, media_id, username): 587 | total = 0 588 | for upl in self.uploader_list: 589 | if upl.queue_contains_post(media_id, username): 590 | return True 591 | return False 592 | 593 | def queue_count(self): 594 | total = 0 595 | for upl in self.uploader_list: 596 | q = len(upl.queue) 597 | print(str(q), end=" ") 598 | total += q 599 | print("Total {0}".format(total)) 600 | total = 0 601 | 602 | def queue_total(self): 603 | total = 0 604 | for upl in self.uploader_list: 605 | q = len(upl.queue) 606 | total += q 607 | return total 608 | 609 | #item handler 610 | def handle_video(self, username, item, same_queue=False, videojson = None, bypass = False): 611 | user = self.cfg.get_user(item.author_id) 612 | if bypass != True and user["latest_item_time"] == item.timestamp: 613 | return 614 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 615 | 616 | if not bypass and self.is_post_queued(item.get_media()["pk"], username): 617 | self.api.sendMessage(str(item.author_id), "That post is already in the queue.") 618 | return 619 | 620 | if bypass == False: 621 | self.do_delay_ad(username, item) 622 | 623 | if videojson == None: 624 | url = item.get_video_url() 625 | duration = item.get_media()["video_duration"] 626 | else: 627 | url = videojson["video_versions"][0]["url"] 628 | duration = videojson["video_duration"] 629 | 630 | if duration >= 70: 631 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("video_to_long")) 632 | return 633 | 634 | uploader = self.uploader 635 | 636 | if not same_queue: 637 | uploader = self.get_uploader() 638 | self.uploader = uploader 639 | 640 | uploader.send_media(url, item.item["item_id"], 2, item.get_media()["pk"], str(item.author_id), username, item.get_item_poster(), item.timestamp, cut = duration >= 60) 641 | logging.info("Added {u} to queue".format(u=username)) 642 | 643 | def handle_text(self, username, item, text = ""): 644 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 645 | return 646 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 647 | 648 | if text == "": 649 | try: 650 | text = item.item["text"] 651 | except: 652 | pass 653 | #ADMINCOMMANDS 654 | if username in self.admins: 655 | if text.startswith("!upgrade"): 656 | pusername = text.replace("!upgrade ", "") 657 | now = self.cfg.upgrade_priority(pusername) 658 | self.api.sendMessage(str(item.author_id), "{u} now has priority lvl {lv}".format(u=pusername, lv = now)) 659 | elif text.startswith("!downgrade"): 660 | pusername = text.replace("!downgrade ", "") 661 | now = self.cfg.downgrade_priority(pusername) 662 | self.api.sendMessage(str(item.author_id), "{u} now has priority lvl {lv}".format(u=pusername, lv = now)) 663 | elif text.startswith("!remove"): 664 | pusername = text.replace("!remove ", "") 665 | total = 0 666 | for upl in self.uploader_list: 667 | for i in upl.queue: 668 | if i["username"] == pusername: 669 | total += 1 670 | upl.queue.remove(i) 671 | self.api.sendMessage(str(item.author_id), "Removed {} queue items from that user!".format(total)) 672 | elif text.startswith("!reset"): 673 | self.cfg.reset_delay() 674 | self.api.sendMessage(str(item.author_id), "Resetted!") 675 | elif text.startswith("!most"): 676 | result = {} 677 | for u in self.uploader_list: 678 | for q in u.queue: 679 | if q["username"] not in result.keys(): 680 | result[q["username"]] = 1 681 | else: 682 | result[q["username"]] += 1 683 | xd = sorted(result.items(), key=lambda x: x[1], reverse=True) 684 | new = [] 685 | for i in range(0,10): 686 | new.append(xd[i]) 687 | self.api.sendMessage(str(item.author_id), json.dumps(new, indent=4)) 688 | if text == "!day": 689 | downloads = self.cfg.get_day()["downloads"] 690 | self.api.sendMessage(str(item.author_id), "{dl} downloads today!".format(dl = downloads)) 691 | elif text == "!delay": 692 | msg = "" 693 | for i in range(0, 100): 694 | d = self.cfg.get_delay(i) 695 | if d != 0: 696 | msg += "Priority Lv {lvl} - {delay}s \r\n".format(lvl=i, delay=d) 697 | self.api.sendMessage(str(item.author_id), msg) 698 | return 699 | 700 | def handle_link(self, username, item): 701 | if username in self.admins: 702 | self.handle_text(username, item, item.item["link"]["text"]) 703 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 704 | return 705 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 706 | 707 | 708 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("links_not_supported")) 709 | return 710 | 711 | def handle_image(self, username, item, same_queue=False, imagejson = None, bypass = False): 712 | user = self.cfg.get_user(item.author_id) 713 | if bypass != True and user["latest_item_time"] == item.timestamp: 714 | return 715 | 716 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 717 | 718 | 719 | if not bypass and self.is_post_queued(item.get_media()["pk"], username): 720 | self.api.sendMessage(str(item.author_id), "That post is already in the queue.") 721 | return 722 | 723 | if bypass == False: 724 | self.do_delay_ad(username, item) 725 | 726 | if imagejson == None: 727 | url = item.get_image_url() 728 | else: 729 | url = imagejson["image_versions2"]["candidates"][0]["url"] 730 | 731 | uploader = self.uploader 732 | 733 | if not same_queue: 734 | uploader = self.get_uploader() 735 | self.uploader = uploader 736 | 737 | uploader.send_media(url, item.item["item_id"], 1, item.get_media()["pk"], str(item.author_id), username, item.get_item_poster(), item.timestamp, cut = False) 738 | logging.info("Added {u} to queue".format(u=username)) 739 | 740 | def handle_placeholder(self, username, item): 741 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 742 | return 743 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 744 | if "Unavailable" in item.get_media()["title"]: 745 | msg = item.get_media()["message"] 746 | if "@" in msg: 747 | username_requested = "".join([i for i in msg.split() if i.startswith("@")][0])[1:] 748 | self.cfg.requested_add_request(username_requested, item.author_id) 749 | 750 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("requested")) 751 | return 752 | elif "deleted" in msg: 753 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("deleted")) 754 | else: 755 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("blocked")) 756 | return 757 | 758 | def handle_story(self, username, item): 759 | try: 760 | title = item.item["story_share"]["title"] 761 | msg = item.item["story_share"]["message"] 762 | reason = item.item["story_share"]["reason"] 763 | except : 764 | title = "nope" 765 | message = None 766 | 767 | if title != "nope": 768 | if reason != 4: 769 | return 770 | #Not following 771 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 772 | return 773 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 774 | username_requested = "".join([i for i in msg.split() if i.startswith("@")][0])[1:] 775 | self.cfg.requested_add_request(username_requested, item.author_id) 776 | self.api.sendMessage(str(item.author_id), self.cfg.get_text("requested")) 777 | return 778 | 779 | if item.get_media_type() == 2: 780 | self.handle_video(username, item) 781 | elif item.get_media_type() == 1: 782 | self.handle_image(username, item) 783 | 784 | def handle_media_share(self, username, item): 785 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 786 | return 787 | 788 | if item.get_media_type() == 2: 789 | self.handle_video(username, item) 790 | 791 | elif item.get_media_type() == 1: 792 | self.handle_image(username, item) 793 | 794 | elif item.get_media_type() == 8: 795 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 796 | return 797 | if self.queue_total() > 2000: 798 | self.api.sendMessage(str(item.author_id), "Slideposts are currently disabled due to heavy server load. Please come back later.") 799 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 800 | return 801 | for i in item.get_media()["carousel_media"]: 802 | if i["media_type"] == 2: 803 | self.handle_video(username, item, True, i, True) 804 | elif i["media_type"] == 1: 805 | try: 806 | self.handle_image(username, item, True, i, True) 807 | except Exception as e: 808 | print("skip") 809 | 810 | 811 | def handle_profilepic(self, username, item): 812 | if self.cfg.get_user(item.author_id)["latest_item_time"] == item.timestamp: 813 | return 814 | self.cfg.user_set_itemtime(item.author_id, username, item.timestamp) 815 | if item.item["profile"]["has_anonymous_profile_picture"]: 816 | self.api.sendMessage(str(item.author_id), "That profile picture is anonymous") 817 | url = item.item["profile"]["profile_pic_url"] 818 | self.uploader.send_media(url, item.item["item_id"], 1, str(item.author_id), username, item.item["profile"]["username"], item.timestamp, cut = False) 819 | logging.info("Added {u} to queue".format(u=username)) 820 | 821 | 822 | def do_delay_ad(self, username, item): 823 | user = self.cfg.get_user(item.author_id) 824 | priority = user["priority"] 825 | delay = self.cfg.get_delay(priority) 826 | print("user " + username + " " + str(delay)) 827 | if delay > 300: 828 | uprankdelay = self.cfg.get_delay(priority+1) 829 | if uprankdelay > 150: 830 | return 831 | self.api.sendMessage(str(item.author_id), "There are {q} people in the queue. Let an admin upgrade your priority".format(q=self.queue_total())) 832 | 833 | def handle_inbox(self): 834 | print("handle inbox") 835 | self.cfg.save_config() 836 | num = 20 837 | if self.first == True: 838 | num = 50 839 | self.first = False 840 | self.api.getv2Inbox(num) 841 | with open(Path("last.json"), "w+") as fp: 842 | json.dump(self.api.LastJson, fp) 843 | inbox = self.api.LastJson 844 | if not self.is_inbox_valid(inbox): 845 | logging.warning("Invalid inbox.. sleeping 10s") 846 | time.sleep(10) 847 | return 848 | 849 | for i in inbox["inbox"]["threads"]: 850 | try: 851 | username = i["users"][0]["username"] 852 | except : 853 | username = "@UNKNOWN@" 854 | 855 | item = InboxItem(i) 856 | if item.is_group: 857 | continue 858 | self.cfg.create_user(item.author_id, username) 859 | 860 | if item.item_type == "text": 861 | self.handle_text(username, item) 862 | 863 | elif item.item_type == "link": 864 | self.handle_link(username, item) 865 | 866 | elif item.item_type == "profile": 867 | self.handle_profilepic(username, item) 868 | 869 | elif item.item_type == "placeholder": 870 | self.handle_placeholder(username, item) 871 | 872 | elif item.item_type == "story_share": 873 | self.handle_story(username, item) 874 | 875 | elif item.item_type == "media_share": 876 | self.handle_media_share(username, item) 877 | 878 | if inbox["pending_requests_total"] == 0: 879 | time.sleep(1) 880 | self.queue_count() 881 | x = 0 882 | for upl in self.uploader_list: 883 | path = "uploader{0}_queue".format(str(x)) 884 | with open(path, "w+") as fp: 885 | json.dump(upl.queue, fp) 886 | x+=1 887 | return 888 | 889 | print("Now pending..") 890 | self.api.get_pending_inbox() 891 | inbox = self.api.LastJson 892 | for i in inbox["inbox"]["threads"]: 893 | try: 894 | username = i["users"][0]["username"] 895 | except : 896 | username = "@UNKNOWN@" 897 | 898 | item = InboxItem(i) 899 | self.api.approve_pending_thread(i["thread_id"]) 900 | self.cfg.create_user(item.author_id, username) 901 | 902 | if item.item_type == "text": 903 | self.handle_text(username, item) 904 | 905 | elif item.item_type == "link": 906 | self.handle_link(username, item) 907 | 908 | elif item.item_type == "placeholder": 909 | self.handle_placeholder(username, item) 910 | 911 | elif item.item_type == "story_share": 912 | self.handle_story(username, item) 913 | 914 | elif item.item_type == "media_share": 915 | self.handle_media_share(username, item) 916 | 917 | username = "USERNAME" 918 | password = "PASSWORD" 919 | 920 | cfg = Config(Path("config.json")) 921 | sessionpath = Path("sessions/{u}.session".format(u = username)) 922 | 923 | mainlogin = InstagramLogin(username, password, Path("./sessions")) 924 | api = mainlogin.api 925 | 926 | if not api.isLoggedIn: 927 | logging.error("Failed to login") 928 | exit() 929 | 930 | uploaders = [] 931 | for x in range(0, 2): 932 | uploaderpath = Path("sessions/" + username +"uploader_{0}.session".format(x)) 933 | queuepath = Path("uploader{0}_queue".format(x)) 934 | 935 | if os.path.exists(uploaderpath): 936 | uapi = pickle.load(open(uploaderpath, "rb")) 937 | if not uapi.isLoggedIn: 938 | uapi.login() 939 | else: 940 | uapi = InstagramAPI(username, password) 941 | uapi.login() 942 | pickle.dump(uapi, open(uploaderpath, "wb")) 943 | test_upl = Uploader(uapi, cfg, x, uploaderpath) 944 | 945 | if os.path.exists(queuepath): 946 | test_upl.queue = json.load(open(queuepath)) 947 | 948 | test_upl.start() 949 | uploaders.append(test_upl) 950 | 951 | 952 | inbox = InboxHandler(api, cfg, uploaders, []) 953 | inbox.run() 954 | 955 | 956 | -------------------------------------------------------------------------------- /Api.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | # -*- coding: utf-8 -*- 4 | 5 | import requests 6 | import random 7 | import json 8 | import hashlib 9 | import hmac 10 | import urllib 11 | import uuid 12 | import time 13 | import copy 14 | import math 15 | import sys 16 | import string 17 | from datetime import datetime 18 | import calendar 19 | import os 20 | from dVideo import dVideo 21 | from requests_toolbelt import MultipartEncoder 22 | import logging 23 | from pathlib import Path 24 | import pickle 25 | # Turn off InsecureRequestWarning 26 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 27 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 28 | 29 | logging.getLogger("urllib3").setLevel(logging.WARNING) 30 | logging.getLogger("requests").setLevel(logging.WARNING) 31 | 32 | try: 33 | from moviepy.editor import VideoFileClip 34 | except: 35 | print("Fail to import moviepy. Need only for Video upload.") 36 | 37 | # The urllib library was split into other modules from Python 2 to Python 3 38 | if sys.version_info.major == 3: 39 | import urllib.parse 40 | try: 41 | from ImageUtils import getImageSize 42 | except: 43 | # Issue 159, python3 import fix 44 | from .ImageUtils import getImageSize 45 | 46 | 47 | class InstagramAPI: 48 | API_URL = 'https://i.instagram.com/api/v1/' 49 | DEVICE_SETTINTS = {'manufacturer': 'Xiaomi', 50 | 'model': 'HM 1SW', 51 | 'android_version': 19, 52 | 'android_release': '4.4'} 53 | USER_AGENT = 'Instagram 89.0.0.21.101 Android ({android_version}/{android_release}; 320dpi; 720x1280; {manufacturer}; {model}; armani; qcom; en_US)'.format(**DEVICE_SETTINTS) 54 | IG_SIG_KEY = '4f8732eb9ba7d1c8e8897a75d6474d4eb3f5279137431b2aafb71fafe2abe178' 55 | EXPERIMENTS = 'ig_promote_reach_objective_fix_universe,ig_android_universe_video_production,ig_search_client_h1_2017_holdout,ig_android_live_follow_from_comments_universe,ig_android_carousel_non_square_creation,ig_android_live_analytics,ig_android_follow_all_dialog_confirmation_copy,ig_android_stories_server_coverframe,ig_android_video_captions_universe,ig_android_offline_location_feed,ig_android_direct_inbox_retry_seen_state,ig_android_ontact_invite_universe,ig_android_live_broadcast_blacklist,ig_android_insta_video_reconnect_viewers,ig_android_ad_async_ads_universe,ig_android_search_clear_layout_universe,ig_android_shopping_reporting,ig_android_stories_surface_universe,ig_android_verified_comments_universe,ig_android_preload_media_ahead_in_current_reel,android_instagram_prefetch_suggestions_universe,ig_android_reel_viewer_fetch_missing_reels_universe,ig_android_direct_search_share_sheet_universe,ig_android_business_promote_tooltip,ig_android_direct_blue_tab,ig_android_async_network_tweak_universe,ig_android_elevate_main_thread_priority_universe,ig_android_stories_gallery_nux,ig_android_instavideo_remove_nux_comments,ig_video_copyright_whitelist,ig_react_native_inline_insights_with_relay,ig_android_direct_thread_message_animation,ig_android_draw_rainbow_client_universe,ig_android_direct_link_style,ig_android_live_heart_enhancements_universe,ig_android_rtc_reshare,ig_android_preload_item_count_in_reel_viewer_buffer,ig_android_users_bootstrap_service,ig_android_auto_retry_post_mode,ig_android_shopping,ig_android_main_feed_seen_state_dont_send_info_on_tail_load,ig_fbns_preload_default,ig_android_gesture_dismiss_reel_viewer,ig_android_tool_tip,ig_android_ad_logger_funnel_logging_universe,ig_android_gallery_grid_column_count_universe,ig_android_business_new_ads_payment_universe,ig_android_direct_links,ig_android_audience_control,ig_android_live_encore_consumption_settings_universe,ig_perf_android_holdout,ig_android_cache_contact_import_list,ig_android_links_receivers,ig_android_ad_impression_backtest,ig_android_list_redesign,ig_android_stories_separate_overlay_creation,ig_android_stop_video_recording_fix_universe,ig_android_render_video_segmentation,ig_android_live_encore_reel_chaining_universe,ig_android_sync_on_background_enhanced_10_25,ig_android_immersive_viewer,ig_android_mqtt_skywalker,ig_fbns_push,ig_android_ad_watchmore_overlay_universe,ig_android_react_native_universe,ig_android_profile_tabs_redesign_universe,ig_android_live_consumption_abr,ig_android_story_viewer_social_context,ig_android_hide_post_in_feed,ig_android_video_loopcount_int,ig_android_enable_main_feed_reel_tray_preloading,ig_android_camera_upsell_dialog,ig_android_ad_watchbrowse_universe,ig_android_internal_research_settings,ig_android_search_people_tag_universe,ig_android_react_native_ota,ig_android_enable_concurrent_request,ig_android_react_native_stories_grid_view,ig_android_business_stories_inline_insights,ig_android_log_mediacodec_info,ig_android_direct_expiring_media_loading_errors,ig_video_use_sve_universe,ig_android_cold_start_feed_request,ig_android_enable_zero_rating,ig_android_reverse_audio,ig_android_branded_content_three_line_ui_universe,ig_android_live_encore_production_universe,ig_stories_music_sticker,ig_android_stories_teach_gallery_location,ig_android_http_stack_experiment_2017,ig_android_stories_device_tilt,ig_android_pending_request_search_bar,ig_android_fb_topsearch_sgp_fork_request,ig_android_seen_state_with_view_info,ig_android_animation_perf_reporter_timeout,ig_android_new_block_flow,ig_android_story_tray_title_play_all_v2,ig_android_direct_address_links,ig_android_stories_archive_universe,ig_android_save_collections_cover_photo,ig_android_live_webrtc_livewith_production,ig_android_sign_video_url,ig_android_stories_video_prefetch_kb,ig_android_stories_create_flow_favorites_tooltip,ig_android_live_stop_broadcast_on_404,ig_android_live_viewer_invite_universe,ig_android_promotion_feedback_channel,ig_android_render_iframe_interval,ig_android_accessibility_logging_universe,ig_android_camera_shortcut_universe,ig_android_use_one_cookie_store_per_user_override,ig_profile_holdout_2017_universe,ig_android_stories_server_brushes,ig_android_ad_media_url_logging_universe,ig_android_shopping_tag_nux_text_universe,ig_android_comments_single_reply_universe,ig_android_stories_video_loading_spinner_improvements,ig_android_collections_cache,ig_android_comment_api_spam_universe,ig_android_facebook_twitter_profile_photos,ig_android_shopping_tag_creation_universe,ig_story_camera_reverse_video_experiment,ig_android_direct_bump_selected_recipients,ig_android_ad_cta_haptic_feedback_universe,ig_android_vertical_share_sheet_experiment,ig_android_family_bridge_share,ig_android_search,ig_android_insta_video_consumption_titles,ig_android_stories_gallery_preview_button,ig_android_fb_auth_education,ig_android_camera_universe,ig_android_me_only_universe,ig_android_instavideo_audio_only_mode,ig_android_user_profile_chaining_icon,ig_android_live_video_reactions_consumption_universe,ig_android_stories_hashtag_text,ig_android_post_live_badge_universe,ig_android_swipe_fragment_container,ig_android_search_users_universe,ig_android_live_save_to_camera_roll_universe,ig_creation_growth_holdout,ig_android_sticker_region_tracking,ig_android_unified_inbox,ig_android_live_new_watch_time,ig_android_offline_main_feed_10_11,ig_import_biz_contact_to_page,ig_android_live_encore_consumption_universe,ig_android_experimental_filters,ig_android_search_client_matching_2,ig_android_react_native_inline_insights_v2,ig_android_business_conversion_value_prop_v2,ig_android_redirect_to_low_latency_universe,ig_android_ad_show_new_awr_universe,ig_family_bridges_holdout_universe,ig_android_background_explore_fetch,ig_android_following_follower_social_context,ig_android_video_keep_screen_on,ig_android_ad_leadgen_relay_modern,ig_android_profile_photo_as_media,ig_android_insta_video_consumption_infra,ig_android_ad_watchlead_universe,ig_android_direct_prefetch_direct_story_json,ig_android_shopping_react_native,ig_android_top_live_profile_pics_universe,ig_android_direct_phone_number_links,ig_android_stories_weblink_creation,ig_android_direct_search_new_thread_universe,ig_android_histogram_reporter,ig_android_direct_on_profile_universe,ig_android_network_cancellation,ig_android_background_reel_fetch,ig_android_react_native_insights,ig_android_insta_video_audio_encoder,ig_android_family_bridge_bookmarks,ig_android_data_usage_network_layer,ig_android_universal_instagram_deep_links,ig_android_dash_for_vod_universe,ig_android_modular_tab_discover_people_redesign,ig_android_mas_sticker_upsell_dialog_universe,ig_android_ad_add_per_event_counter_to_logging_event,ig_android_sticky_header_top_chrome_optimization,ig_android_rtl,ig_android_biz_conversion_page_pre_select,ig_android_promote_from_profile_button,ig_android_live_broadcaster_invite_universe,ig_android_share_spinner,ig_android_text_action,ig_android_own_reel_title_universe,ig_promotions_unit_in_insights_landing_page,ig_android_business_settings_header_univ,ig_android_save_longpress_tooltip,ig_android_constrain_image_size_universe,ig_android_business_new_graphql_endpoint_universe,ig_ranking_following,ig_android_stories_profile_camera_entry_point,ig_android_universe_reel_video_production,ig_android_power_metrics,ig_android_sfplt,ig_android_offline_hashtag_feed,ig_android_live_skin_smooth,ig_android_direct_inbox_search,ig_android_stories_posting_offline_ui,ig_android_sidecar_video_upload_universe,ig_android_promotion_manager_entry_point_universe,ig_android_direct_reply_audience_upgrade,ig_android_swipe_navigation_x_angle_universe,ig_android_offline_mode_holdout,ig_android_live_send_user_location,ig_android_direct_fetch_before_push_notif,ig_android_non_square_first,ig_android_insta_video_drawing,ig_android_swipeablefilters_universe,ig_android_live_notification_control_universe,ig_android_analytics_logger_running_background_universe,ig_android_save_all,ig_android_reel_viewer_data_buffer_size,ig_direct_quality_holdout_universe,ig_android_family_bridge_discover,ig_android_react_native_restart_after_error_universe,ig_android_startup_manager,ig_story_tray_peek_content_universe,ig_android_profile,ig_android_high_res_upload_2,ig_android_http_service_same_thread,ig_android_scroll_to_dismiss_keyboard,ig_android_remove_followers_universe,ig_android_skip_video_render,ig_android_story_timestamps,ig_android_live_viewer_comment_prompt_universe,ig_profile_holdout_universe,ig_android_react_native_insights_grid_view,ig_stories_selfie_sticker,ig_android_stories_reply_composer_redesign,ig_android_streamline_page_creation,ig_explore_netego,ig_android_ig4b_connect_fb_button_universe,ig_android_feed_util_rect_optimization,ig_android_rendering_controls,ig_android_os_version_blocking,ig_android_encoder_width_safe_multiple_16,ig_search_new_bootstrap_holdout_universe,ig_android_snippets_profile_nux,ig_android_e2e_optimization_universe,ig_android_comments_logging_universe,ig_shopping_insights,ig_android_save_collections,ig_android_live_see_fewer_videos_like_this_universe,ig_android_show_new_contact_import_dialog,ig_android_live_view_profile_from_comments_universe,ig_fbns_blocked,ig_formats_and_feedbacks_holdout_universe,ig_android_reduce_view_pager_buffer,ig_android_instavideo_periodic_notif,ig_search_user_auto_complete_cache_sync_ttl,ig_android_marauder_update_frequency,ig_android_suggest_password_reset_on_oneclick_login,ig_android_promotion_entry_from_ads_manager_universe,ig_android_live_special_codec_size_list,ig_android_enable_share_to_messenger,ig_android_background_main_feed_fetch,ig_android_live_video_reactions_creation_universe,ig_android_channels_home,ig_android_sidecar_gallery_universe,ig_android_upload_reliability_universe,ig_migrate_mediav2_universe,ig_android_insta_video_broadcaster_infra_perf,ig_android_business_conversion_social_context,android_ig_fbns_kill_switch,ig_android_live_webrtc_livewith_consumption,ig_android_destroy_swipe_fragment,ig_android_react_native_universe_kill_switch,ig_android_stories_book_universe,ig_android_all_videoplayback_persisting_sound,ig_android_draw_eraser_universe,ig_direct_search_new_bootstrap_holdout_universe,ig_android_cache_layer_bytes_threshold,ig_android_search_hash_tag_and_username_universe,ig_android_business_promotion,ig_android_direct_search_recipients_controller_universe,ig_android_ad_show_full_name_universe,ig_android_anrwatchdog,ig_android_qp_kill_switch,ig_android_2fac,ig_direct_bypass_group_size_limit_universe,ig_android_promote_simplified_flow,ig_android_share_to_whatsapp,ig_android_hide_bottom_nav_bar_on_discover_people,ig_fbns_dump_ids,ig_android_hands_free_before_reverse,ig_android_skywalker_live_event_start_end,ig_android_live_join_comment_ui_change,ig_android_direct_search_story_recipients_universe,ig_android_direct_full_size_gallery_upload,ig_android_ad_browser_gesture_control,ig_channel_server_experiments,ig_android_video_cover_frame_from_original_as_fallback,ig_android_ad_watchinstall_universe,ig_android_ad_viewability_logging_universe,ig_android_new_optic,ig_android_direct_visual_replies,ig_android_stories_search_reel_mentions_universe,ig_android_threaded_comments_universe,ig_android_mark_reel_seen_on_Swipe_forward,ig_internal_ui_for_lazy_loaded_modules_experiment,ig_fbns_shared,ig_android_capture_slowmo_mode,ig_android_live_viewers_list_search_bar,ig_android_video_single_surface,ig_android_offline_reel_feed,ig_android_video_download_logging,ig_android_last_edits,ig_android_exoplayer_4142,ig_android_post_live_viewer_count_privacy_universe,ig_android_activity_feed_click_state,ig_android_snippets_haptic_feedback,ig_android_gl_drawing_marks_after_undo_backing,ig_android_mark_seen_state_on_viewed_impression,ig_android_live_backgrounded_reminder_universe,ig_android_live_hide_viewer_nux_universe,ig_android_live_monotonic_pts,ig_android_search_top_search_surface_universe,ig_android_user_detail_endpoint,ig_android_location_media_count_exp_ig,ig_android_comment_tweaks_universe,ig_android_ad_watchmore_entry_point_universe,ig_android_top_live_notification_universe,ig_android_add_to_last_post,ig_save_insights,ig_android_live_enhanced_end_screen_universe,ig_android_ad_add_counter_to_logging_event,ig_android_blue_token_conversion_universe,ig_android_exoplayer_settings,ig_android_progressive_jpeg,ig_android_offline_story_stickers,ig_android_gqls_typing_indicator,ig_android_chaining_button_tooltip,ig_android_video_prefetch_for_connectivity_type,ig_android_use_exo_cache_for_progressive,ig_android_samsung_app_badging,ig_android_ad_holdout_watchandmore_universe,ig_android_offline_commenting,ig_direct_stories_recipient_picker_button,ig_insights_feedback_channel_universe,ig_android_insta_video_abr_resize,ig_android_insta_video_sound_always_on''' 56 | SIG_KEY_VERSION = '4' 57 | 58 | # username # Instagram username 59 | # password # Instagram password 60 | # debug # Debug 61 | # uuid # UUID 62 | # device_id # Device ID 63 | # username_id # Username ID 64 | # token # _csrftoken 65 | # isLoggedIn # Session status 66 | # rank_token # Rank token 67 | # IGDataPath # Data storage path 68 | 69 | def __init__(self, username, password, debug=False, IGDataPath=None): 70 | m = hashlib.md5() 71 | m.update(username.encode('utf-8') + password.encode('utf-8')) 72 | self.device_id = self.generateDeviceId(m.hexdigest()) 73 | self.setUser(username, password) 74 | self.isLoggedIn = False 75 | self.LastResponse = None 76 | self.PATH = "" 77 | self.s = requests.Session() 78 | 79 | def sendMessage(self, target_user, msgText): 80 | target_user = '[[{}]]'.format(','.join([target_user])) 81 | url = 'direct_v2/threads/broadcast/text/' 82 | data = { 83 | 'text': msgText, 84 | '_uuid': self.uuid, 85 | '_csrftoken': self.token, 86 | 'recipient_users': target_user, 87 | '_uid': self.username_id, 88 | 'action': 'send_item', 89 | 'client_context': self.generateUUID(True)} 90 | return self.SendRequest(url, data) 91 | 92 | def setUser(self, username, password): 93 | self.username = username 94 | self.password = password 95 | self.uuid = self.generateUUID(True) 96 | 97 | def setProxy(self, proxy=None): 98 | """ 99 | Set proxy for all requests:: 100 | 101 | Proxy format - user:password@ip:port 102 | """ 103 | print('Set proxy!') 104 | proxies = { 105 | 'http': 'http://' + proxy, 106 | 'https': 'http://' + proxy 107 | } 108 | self.s.proxies.update(proxies) 109 | 110 | def login(self, force=False): 111 | if (not self.isLoggedIn or force): 112 | if (self.SendRequest('si/fetch_headers/?challenge_type=signup&guid=' + self.generateUUID(False), None, True)): 113 | 114 | data = {'phone_id': self.generateUUID(True), 115 | '_csrftoken': self.LastResponse.cookies['csrftoken'], 116 | 'username': self.username, 117 | 'guid': self.uuid, 118 | 'device_id': self.device_id, 119 | 'password': self.password, 120 | 'login_attempt_count': '0'} 121 | 122 | if (self.SendRequest('accounts/login/', self.generateSignature(json.dumps(data)), True)): 123 | self.isLoggedIn = True 124 | self.username_id = self.LastJson["logged_in_user"]["pk"] 125 | self.rank_token = "%s_%s" % (self.username_id, self.uuid) 126 | self.token = self.LastResponse.cookies["csrftoken"] 127 | self.syncFeatures() 128 | self.autoCompleteUserList() 129 | self.timelineFeed() 130 | self.getv2Inbox() 131 | self.getRecentActivity() 132 | print("Login success!\n") 133 | return True 134 | 135 | def syncFeatures(self): 136 | data = json.dumps({'_uuid': self.uuid, 137 | '_uid': self.username_id, 138 | 'id': self.username_id, 139 | '_csrftoken': self.token, 140 | 'experiments': self.EXPERIMENTS}) 141 | return self.SendRequest('qe/sync/', self.generateSignature(data)) 142 | 143 | def autoCompleteUserList(self): 144 | return self.SendRequest('friendships/autocomplete_user_list/') 145 | 146 | def timelineFeed(self): 147 | return self.SendRequest('feed/timeline/') 148 | 149 | def megaphoneLog(self): 150 | return self.SendRequest('megaphone/log/') 151 | 152 | def expose(self): 153 | data = json.dumps({'_uuid': self.uuid, 154 | '_uid': self.username_id, 155 | 'id': self.username_id, 156 | '_csrftoken': self.token, 157 | 'experiment': 'ig_android_profile_contextual_feed'}) 158 | return self.SendRequest('qe/expose/', self.generateSignature(data)) 159 | 160 | def logout(self): 161 | logout = self.SendRequest('accounts/logout/') 162 | 163 | def uploadPhoto(self, photo, caption=None, upload_id=None, is_sidecar=None): 164 | if upload_id is None: 165 | upload_id = str(int(time.time() * 1000)) 166 | data = {'upload_id': upload_id, 167 | '_uuid': self.uuid, 168 | '_csrftoken': self.token, 169 | 'image_compression': '{"lib_name":"jt","lib_version":"1.3.0","quality":"87"}', 170 | 'photo': ('pending_media_%s.jpg' % upload_id, open(photo, 'rb'), 'application/octet-stream', {'Content-Transfer-Encoding': 'binary'})} 171 | if is_sidecar: 172 | data['is_sidecar'] = '1' 173 | m = MultipartEncoder(data, boundary=self.uuid) 174 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 175 | 'X-IG-Connection-Type': 'WIFI', 176 | 'Cookie2': '$Version=1', 177 | 'Accept-Language': 'en-US', 178 | 'Accept-Encoding': 'gzip, deflate', 179 | 'Content-type': m.content_type, 180 | 'Connection': 'close', 181 | 'User-Agent': self.USER_AGENT}) 182 | response = self.s.post(self.API_URL + "upload/photo/", data=m.to_string()) 183 | if response.status_code == 200: 184 | if self.configure(upload_id, photo, caption): 185 | self.expose() 186 | return False 187 | 188 | def uploadVideo(self, video, thumbnail, caption=None, upload_id=None, is_sidecar=None): 189 | if upload_id is None: 190 | upload_id = str(int(time.time() * 1000)) 191 | data = {'upload_id': upload_id, 192 | '_csrftoken': self.token, 193 | 'media_type': '2', 194 | '_uuid': self.uuid} 195 | if is_sidecar: 196 | data['is_sidecar'] = '1' 197 | m = MultipartEncoder(data, boundary=self.uuid) 198 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 199 | 'X-IG-Connection-Type': 'WIFI', 200 | 'Host': 'i.instagram.com', 201 | 'Cookie2': '$Version=1', 202 | 'Accept-Language': 'en-US', 203 | 'Accept-Encoding': 'gzip, deflate', 204 | 'Content-type': m.content_type, 205 | 'Connection': 'keep-alive', 206 | 'User-Agent': self.USER_AGENT}) 207 | response = self.s.post(self.API_URL + "upload/video/", data=m.to_string()) 208 | if response.status_code == 200: 209 | body = json.loads(response.text) 210 | upload_url = body['video_upload_urls'][3]['url'] 211 | upload_job = body['video_upload_urls'][3]['job'] 212 | 213 | videoData = open(video, 'rb').read() 214 | # solve issue #85 TypeError: slice indices must be integers or None or have an __index__ method 215 | request_size = int(math.floor(len(videoData) / 4)) 216 | lastRequestExtra = (len(videoData) - (request_size * 3)) 217 | 218 | headers = copy.deepcopy(self.s.headers) 219 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 220 | 'X-IG-Connection-Type': 'WIFI', 221 | 'Cookie2': '$Version=1', 222 | 'Accept-Language': 'en-US', 223 | 'Accept-Encoding': 'gzip, deflate', 224 | 'Content-type': 'application/octet-stream', 225 | 'Session-ID': upload_id, 226 | 'Connection': 'keep-alive', 227 | 'Content-Disposition': 'attachment; filename="video.mov"', 228 | 'job': upload_job, 229 | 'Host': 'upload.instagram.com', 230 | 'User-Agent': self.USER_AGENT}) 231 | for i in range(0, 4): 232 | start = i * request_size 233 | if i == 3: 234 | end = i * request_size + lastRequestExtra 235 | else: 236 | end = (i + 1) * request_size 237 | length = lastRequestExtra if i == 3 else request_size 238 | content_range = "bytes {start}-{end}/{lenVideo}".format(start=start, end=(end - 1), 239 | lenVideo=len(videoData)).encode('utf-8') 240 | 241 | self.s.headers.update({'Content-Length': str(end - start), 'Content-Range': content_range, }) 242 | response = self.s.post(upload_url, data=videoData[start:start + length]) 243 | self.s.headers = headers 244 | 245 | if response.status_code == 200: 246 | if self.configureVideo(upload_id, video, thumbnail, caption): 247 | self.expose() 248 | return False 249 | 250 | def uploadAlbum(self, media, caption=None, upload_id=None): 251 | if not media: 252 | raise Exception("List of media to upload can't be empty.") 253 | 254 | if len(media) < 2 or len(media) > 10: 255 | raise Exception('Instagram requires that albums contain 2-10 items. You tried to submit {}.'.format(len(media))) 256 | 257 | # Figure out the media file details for ALL media in the album. 258 | # NOTE: We do this first, since it validates whether the media files are 259 | # valid and lets us avoid wasting time uploading totally invalid albums! 260 | for idx, item in enumerate(media): 261 | if not item.get('file', '') or item.get('tipe', ''): 262 | raise Exception('Media at index "{}" does not have the required "file" and "type" keys.'.format(idx)) 263 | 264 | # $itemInternalMetadata = new InternalMetadata(); 265 | # If usertags are provided, verify that the entries are valid. 266 | if item.get('usertags', []): 267 | self.throwIfInvalidUsertags(item['usertags']) 268 | 269 | # Pre-process media details and throw if not allowed on Instagram. 270 | if item.get('type', '') == 'photo': 271 | # Determine the photo details. 272 | # $itemInternalMetadata->setPhotoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']); 273 | pass 274 | 275 | elif item.get('type', '') == 'video': 276 | # Determine the video details. 277 | # $itemInternalMetadata->setVideoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']); 278 | pass 279 | 280 | else: 281 | raise Exception('Unsupported album media type "{}".'.format(item['type'])) 282 | 283 | itemInternalMetadata = {} 284 | item['internalMetadata'] = itemInternalMetadata 285 | 286 | # Perform all media file uploads. 287 | for idx, item in enumerate(media): 288 | itemInternalMetadata = item['internalMetadata'] 289 | item_upload_id = self.generateUploadId() 290 | if item.get('type', '') == 'photo': 291 | self.uploadPhoto(item['file'], caption=caption, is_sidecar=True, upload_id=item_upload_id) 292 | # $itemInternalMetadata->setPhotoUploadResponse($this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata)); 293 | 294 | elif item.get('type', '') == 'video': 295 | # Attempt to upload the video data. 296 | self.uploadVideo(item['file'], item['thumbnail'], caption=caption, is_sidecar=True, upload_id=item_upload_id) 297 | # $itemInternalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_TIMELINE_ALBUM, $item['file'], $itemInternalMetadata); 298 | # Attempt to upload the thumbnail, associated with our video's ID. 299 | # $itemInternalMetadata->setPhotoUploadResponse($this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata)); 300 | pass 301 | item['internalMetadata']['upload_id'] = item_upload_id 302 | 303 | albumInternalMetadata = {} 304 | return self.configureTimelineAlbum(media, albumInternalMetadata, captionText=caption) 305 | 306 | def throwIfInvalidUsertags(self, usertags): 307 | for user_position in usertags: 308 | # Verify this usertag entry, ensuring that the entry is format 309 | # ['position'=>[0.0,1.0],'user_id'=>'123'] and nothing else. 310 | correct = True 311 | if isinstance(user_position, dict): 312 | position = user_position.get('position', None) 313 | user_id = user_position.get('user_id', None) 314 | 315 | if isinstance(position, list) and len(position) == 2: 316 | try: 317 | x = float(position[0]) 318 | y = float(position[1]) 319 | if x < 0.0 or x > 1.0: 320 | correct = False 321 | if y < 0.0 or y > 1.0: 322 | correct = False 323 | except: 324 | correct = False 325 | try: 326 | user_id = long(user_id) 327 | if user_id < 0: 328 | correct = False 329 | except: 330 | correct = False 331 | if not correct: 332 | raise Exception('Invalid user entry in usertags array.') 333 | 334 | def configureTimelineAlbum(self, media, albumInternalMetadata, captionText='', location=None): 335 | endpoint = 'media/configure_sidecar/' 336 | albumUploadId = self.generateUploadId() 337 | 338 | date = datetime.utcnow().isoformat() 339 | childrenMetadata = [] 340 | for item in media: 341 | itemInternalMetadata = item['internalMetadata'] 342 | uploadId = itemInternalMetadata.get('upload_id', self.generateUploadId()) 343 | if item.get('type', '') == 'photo': 344 | # Build this item's configuration. 345 | photoConfig = {'date_time_original': date, 346 | 'scene_type': 1, 347 | 'disable_comments': False, 348 | 'upload_id': uploadId, 349 | 'source_type': 0, 350 | 'scene_capture_type': 'standard', 351 | 'date_time_digitized': date, 352 | 'geotag_enabled': False, 353 | 'camera_position': 'back', 354 | 'edits': {'filter_strength': 1, 355 | 'filter_name': 'IGNormalFilter'} 356 | } 357 | # This usertag per-file EXTERNAL metadata is only supported for PHOTOS! 358 | if item.get('usertags', []): 359 | # NOTE: These usertags were validated in Timeline::uploadAlbum. 360 | photoConfig['usertags'] = json.dumps({'in': item['usertags']}) 361 | 362 | childrenMetadata.append(photoConfig) 363 | if item.get('type', '') == 'video': 364 | # Get all of the INTERNAL per-VIDEO metadata. 365 | videoDetails = itemInternalMetadata.get('video_details', {}) 366 | # Build this item's configuration. 367 | videoConfig = {'length': videoDetails.get('duration', 1.0), 368 | 'date_time_original': date, 369 | 'scene_type': 1, 370 | 'poster_frame_index': 0, 371 | 'trim_type': 0, 372 | 'disable_comments': False, 373 | 'upload_id': uploadId, 374 | 'source_type': 'library', 375 | 'geotag_enabled': False, 376 | 'edits': { 377 | 'length': videoDetails.get('duration', 1.0), 378 | 'cinema': 'unsupported', 379 | 'original_length': videoDetails.get('duration', 1.0), 380 | 'source_type': 'library', 381 | 'start_time': 0, 382 | 'camera_position': 'unknown', 383 | 'trim_type': 0} 384 | } 385 | 386 | childrenMetadata.append(videoConfig) 387 | # Build the request... 388 | data = {'_csrftoken': self.token, 389 | '_uid': self.username_id, 390 | '_uuid': self.uuid, 391 | 'client_sidecar_id': albumUploadId, 392 | 'caption': captionText, 393 | 'children_metadata': childrenMetadata} 394 | self.SendRequest(endpoint, self.generateSignature(json.dumps(data))) 395 | response = self.LastResponse 396 | if response.status_code == 200: 397 | self.LastResponse = response 398 | try: 399 | self.LastJson = json.loads(response.text) 400 | except: 401 | self.LastJson = {} 402 | return True 403 | else: 404 | print("Request return " + str(response.status_code) + " error!") 405 | time.sleep(20) 406 | # for debugging 407 | try: 408 | self.LastResponse = response 409 | self.LastJson = json.loads(response.text) 410 | except: 411 | pass 412 | return False 413 | 414 | def direct_share(self, media_id, recipients, text=None): 415 | if not isinstance(position, list): 416 | recipients = [str(recipients)] 417 | recipient_users = '"",""'.join(str(r) for r in recipients) 418 | endpoint = 'direct_v2/threads/broadcast/media_share/?media_type=photo' 419 | boundary = self.uuid 420 | bodies = [ 421 | { 422 | 'type': 'form-data', 423 | 'name': 'media_id', 424 | 'data': media_id, 425 | }, 426 | { 427 | 'type': 'form-data', 428 | 'name': 'recipient_users', 429 | 'data': '[["{}"]]'.format(recipient_users), 430 | }, 431 | { 432 | 'type': 'form-data', 433 | 'name': 'client_context', 434 | 'data': self.uuid, 435 | }, 436 | { 437 | 'type': 'form-data', 438 | 'name': 'thread', 439 | 'data': '["0"]', 440 | }, 441 | { 442 | 'type': 'form-data', 443 | 'name': 'text', 444 | 'data': text or '', 445 | }, 446 | ] 447 | data = self.buildBody(bodies, boundary) 448 | self.s.headers.update({'User-Agent': self.USER_AGENT, 449 | 'Proxy-Connection': 'keep-alive', 450 | 'Connection': 'keep-alive', 451 | 'Accept': '*/*', 452 | 'Content-Type': 'multipart/form-data; boundary={}'.format(boundary), 453 | 'Accept-Language': 'en-en'}) 454 | # self.SendRequest(endpoint,post=data) #overwrites 'Content-type' header and boundary is missed 455 | response = self.s.post(self.API_URL + endpoint, data=data) 456 | 457 | if response.status_code == 200: 458 | self.LastResponse = response 459 | self.LastJson = json.loads(response.text) 460 | return True 461 | else: 462 | print("Request return " + str(response.status_code) + " error!") 463 | time.sleep(20) 464 | # for debugging 465 | try: 466 | self.LastResponse = response 467 | self.LastJson = json.loads(response.text) 468 | except: 469 | pass 470 | return False 471 | 472 | def configureVideo(self, upload_id, video, thumbnail, caption=''): 473 | clip = VideoFileClip(video) 474 | self.uploadPhoto(photo=thumbnail, caption=caption, upload_id=upload_id) 475 | data = json.dumps({ 476 | 'upload_id': upload_id, 477 | 'source_type': 3, 478 | 'poster_frame_index': 0, 479 | 'length': 0.00, 480 | 'audio_muted': False, 481 | 'filter_type': 0, 482 | 'video_result': 'deprecated', 483 | 'clips': { 484 | 'length': clip.duration, 485 | 'source_type': '3', 486 | 'camera_position': 'back', 487 | }, 488 | 'extra': { 489 | 'source_width': clip.size[0], 490 | 'source_height': clip.size[1], 491 | }, 492 | 'device': self.DEVICE_SETTINTS, 493 | '_csrftoken': self.token, 494 | '_uuid': self.uuid, 495 | '_uid': self.username_id, 496 | 'caption': caption, 497 | }) 498 | return self.SendRequest('media/configure/?video=1', self.generateSignature(data)) 499 | 500 | def configure(self, upload_id, photo, caption=''): 501 | (w, h) = getImageSize(photo) 502 | data = json.dumps({'_csrftoken': self.token, 503 | 'media_folder': 'Instagram', 504 | 'source_type': 4, 505 | '_uid': self.username_id, 506 | '_uuid': self.uuid, 507 | 'caption': caption, 508 | 'upload_id': upload_id, 509 | 'device': self.DEVICE_SETTINTS, 510 | 'edits': { 511 | 'crop_original_size': [w * 1.0, h * 1.0], 512 | 'crop_center': [0.0, 0.0], 513 | 'crop_zoom': 1.0 514 | }, 515 | 'extra': { 516 | 'source_width': w, 517 | 'source_height': h 518 | }}) 519 | return self.SendRequest('media/configure/?', self.generateSignature(data)) 520 | 521 | def editMedia(self, mediaId, captionText=''): 522 | data = json.dumps({'_uuid': self.uuid, 523 | '_uid': self.username_id, 524 | '_csrftoken': self.token, 525 | 'caption_text': captionText}) 526 | return self.SendRequest('media/' + str(mediaId) + '/edit_media/', self.generateSignature(data)) 527 | 528 | def removeSelftag(self, mediaId): 529 | data = json.dumps({'_uuid': self.uuid, 530 | '_uid': self.username_id, 531 | '_csrftoken': self.token}) 532 | return self.SendRequest('media/' + str(mediaId) + '/remove/', self.generateSignature(data)) 533 | 534 | def mediaInfo(self, mediaId): 535 | data = json.dumps({'_uuid': self.uuid, 536 | '_uid': self.username_id, 537 | '_csrftoken': self.token, 538 | 'media_id': mediaId}) 539 | return self.SendRequest('media/' + str(mediaId) + '/info/', self.generateSignature(data)) 540 | 541 | def deleteMedia(self, mediaId): 542 | data = json.dumps({'_uuid': self.uuid, 543 | '_uid': self.username_id, 544 | '_csrftoken': self.token, 545 | 'media_id': mediaId}) 546 | return self.SendRequest('media/' + str(mediaId) + '/delete/', self.generateSignature(data)) 547 | 548 | def changePassword(self, newPassword): 549 | data = json.dumps({'_uuid': self.uuid, 550 | '_uid': self.username_id, 551 | '_csrftoken': self.token, 552 | 'old_password': self.password, 553 | 'new_password1': newPassword, 554 | 'new_password2': newPassword}) 555 | return self.SendRequest('accounts/change_password/', self.generateSignature(data)) 556 | 557 | def explore(self): 558 | return self.SendRequest('discover/explore/') 559 | 560 | def comment(self, mediaId, commentText): 561 | data = json.dumps({'_uuid': self.uuid, 562 | '_uid': self.username_id, 563 | '_csrftoken': self.token, 564 | 'comment_text': commentText}) 565 | return self.SendRequest('media/' + str(mediaId) + '/comment/', self.generateSignature(data)) 566 | 567 | def deleteComment(self, mediaId, commentId): 568 | data = json.dumps({'_uuid': self.uuid, 569 | '_uid': self.username_id, 570 | '_csrftoken': self.token}) 571 | return self.SendRequest('media/' + str(mediaId) + '/comment/' + str(commentId) + '/delete/', self.generateSignature(data)) 572 | 573 | def changeProfilePicture(self, photo): 574 | # TODO Instagram.php 705-775 575 | return False 576 | 577 | def removeProfilePicture(self): 578 | data = json.dumps({'_uuid': self.uuid, 579 | '_uid': self.username_id, 580 | '_csrftoken': self.token}) 581 | return self.SendRequest('accounts/remove_profile_picture/', self.generateSignature(data)) 582 | 583 | def setPrivateAccount(self): 584 | data = json.dumps({'_uuid': self.uuid, 585 | '_uid': self.username_id, 586 | '_csrftoken': self.token}) 587 | return self.SendRequest('accounts/set_private/', self.generateSignature(data)) 588 | 589 | def setPublicAccount(self): 590 | data = json.dumps({'_uuid': self.uuid, 591 | '_uid': self.username_id, 592 | '_csrftoken': self.token}) 593 | return self.SendRequest('accounts/set_public/', self.generateSignature(data)) 594 | 595 | def getProfileData(self): 596 | data = json.dumps({'_uuid': self.uuid, 597 | '_uid': self.username_id, 598 | '_csrftoken': self.token}) 599 | return self.SendRequest('accounts/current_user/?edit=true', self.generateSignature(data)) 600 | 601 | def editProfile(self, url, phone, first_name, biography, email, gender): 602 | data = json.dumps({'_uuid': self.uuid, 603 | '_uid': self.username_id, 604 | '_csrftoken': self.token, 605 | 'external_url': url, 606 | 'phone_number': phone, 607 | 'username': self.username, 608 | 'full_name': first_name, 609 | 'biography': biography, 610 | 'email': email, 611 | 'gender': gender}) 612 | return self.SendRequest('accounts/edit_profile/', self.generateSignature(data)) 613 | 614 | def getUsernameInfo(self, usernameId): 615 | return self.SendRequest('users/' + str(usernameId) + '/info/') 616 | 617 | def getSelfUsernameInfo(self): 618 | return self.getUsernameInfo(self.username_id) 619 | 620 | def getSelfSavedMedia(self): 621 | return self.SendRequest('feed/saved') 622 | 623 | def getRecentActivity(self): 624 | activity = self.SendRequest('news/inbox/?') 625 | return activity 626 | 627 | def getFollowingRecentActivity(self): 628 | activity = self.SendRequest('news/?') 629 | return activity 630 | 631 | def getv2Inbox(self, limit=50): 632 | inbox = self.SendRequest('direct_v2/inbox/?persistentBadging=true&use_unified_inbox=true&limit={}'.format(limit)) 633 | return inbox 634 | 635 | def getv2Threads(self, thread, cursor=None): 636 | endpoint = 'direct_v2/threads/{0}'.format(thread) 637 | if cursor is not None: 638 | endpoint += '?cursor={0}'.format(cursor) 639 | inbox = self.SendRequest(endpoint) 640 | return inbox 641 | 642 | def getUserTags(self, usernameId): 643 | tags = self.SendRequest('usertags/' + str(usernameId) + '/feed/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 644 | return tags 645 | 646 | def getSelfUserTags(self): 647 | return self.getUserTags(self.username_id) 648 | 649 | def tagFeed(self, tag): 650 | userFeed = self.SendRequest('feed/tag/' + str(tag) + '/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 651 | return userFeed 652 | 653 | def getMediaLikers(self, mediaId): 654 | likers = self.SendRequest('media/' + str(mediaId) + '/likers/?') 655 | return likers 656 | 657 | def getGeoMedia(self, usernameId): 658 | locations = self.SendRequest('maps/user/' + str(usernameId) + '/') 659 | return locations 660 | 661 | def getSelfGeoMedia(self): 662 | return self.getGeoMedia(self.username_id) 663 | 664 | def fbUserSearch(self, query): 665 | query = self.SendRequest('fbsearch/topsearch/?context=blended&query=' + str(query) + '&rank_token=' + str(self.rank_token)) 666 | return query 667 | 668 | def searchUsers(self, query): 669 | query = self.SendRequest('users/search/?ig_sig_key_version=' + str(self.SIG_KEY_VERSION) + '&is_typeahead=true&query=' + str(query) + '&rank_token=' + str(self.rank_token)) 670 | return query 671 | 672 | def searchUsername(self, usernameName): 673 | query = self.SendRequest('users/' + str(usernameName) + '/usernameinfo/') 674 | return query 675 | 676 | def syncFromAdressBook(self, contacts): 677 | return self.SendRequest('address_book/link/?include=extra_display_name,thumbnails', "contacts=" + json.dumps(contacts)) 678 | 679 | def searchTags(self, query): 680 | query = self.SendRequest('tags/search/?is_typeahead=true&q=' + str(query) + '&rank_token=' + str(self.rank_token)) 681 | return query 682 | 683 | def getTimeline(self): 684 | query = self.SendRequest('feed/timeline/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 685 | return query 686 | 687 | def getUserFeed(self, usernameId, maxid='', minTimestamp=None): 688 | query = self.SendRequest('feed/user/%s/?max_id=%s&min_timestamp=%s&rank_token=%s&ranked_content=true' 689 | % (usernameId, maxid, minTimestamp, self.rank_token)) 690 | return query 691 | 692 | def getSelfUserFeed(self, maxid='', minTimestamp=None): 693 | return self.getUserFeed(self.username_id, maxid, minTimestamp) 694 | 695 | def getHashtagFeed(self, hashtagString, maxid=''): 696 | return self.SendRequest('feed/tag/' + hashtagString + '/?max_id=' + str(maxid) + '&rank_token=' + self.rank_token + '&ranked_content=true&') 697 | 698 | def searchLocation(self, query): 699 | locationFeed = self.SendRequest('fbsearch/places/?rank_token=' + str(self.rank_token) + '&query=' + str(query)) 700 | return locationFeed 701 | 702 | def getLocationFeed(self, locationId, maxid=''): 703 | return self.SendRequest('feed/location/' + str(locationId) + '/?max_id=' + maxid + '&rank_token=' + self.rank_token + '&ranked_content=true&') 704 | 705 | def getPopularFeed(self): 706 | popularFeed = self.SendRequest('feed/popular/?people_teaser_supported=1&rank_token=' + str(self.rank_token) + '&ranked_content=true&') 707 | return popularFeed 708 | 709 | def getUserFollowings(self, usernameId, maxid=''): 710 | url = 'friendships/' + str(usernameId) + '/following/?' 711 | query_string = {'ig_sig_key_version': self.SIG_KEY_VERSION, 712 | 'rank_token': self.rank_token} 713 | if maxid: 714 | query_string['max_id'] = maxid 715 | if sys.version_info.major == 3: 716 | url += urllib.parse.urlencode(query_string) 717 | else: 718 | url += urllib.urlencode(query_string) 719 | return self.SendRequest(url) 720 | 721 | def getSelfUsersFollowing(self): 722 | return self.getUserFollowings(self.username_id) 723 | 724 | def getUserFollowers(self, usernameId, maxid=''): 725 | if maxid == '': 726 | return self.SendRequest('friendships/' + str(usernameId) + '/followers/?rank_token=' + self.rank_token) 727 | else: 728 | return self.SendRequest('friendships/' + str(usernameId) + '/followers/?rank_token=' + self.rank_token + '&max_id=' + str(maxid)) 729 | 730 | def getSelfUserFollowers(self): 731 | return self.getUserFollowers(self.username_id) 732 | 733 | def like(self, mediaId): 734 | data = json.dumps({'_uuid': self.uuid, 735 | '_uid': self.username_id, 736 | '_csrftoken': self.token, 737 | 'media_id': mediaId}) 738 | return self.SendRequest('media/' + str(mediaId) + '/like/', self.generateSignature(data)) 739 | 740 | def unlike(self, mediaId): 741 | data = json.dumps({'_uuid': self.uuid, 742 | '_uid': self.username_id, 743 | '_csrftoken': self.token, 744 | 'media_id': mediaId}) 745 | return self.SendRequest('media/' + str(mediaId) + '/unlike/', self.generateSignature(data)) 746 | 747 | def getMediaComments(self, mediaId, max_id=''): 748 | return self.SendRequest('media/' + mediaId + '/comments/?max_id=' + max_id) 749 | 750 | def setNameAndPhone(self, name='', phone=''): 751 | data = json.dumps({'_uuid': self.uuid, 752 | '_uid': self.username_id, 753 | 'first_name': name, 754 | 'phone_number': phone, 755 | '_csrftoken': self.token}) 756 | return self.SendRequest('accounts/set_phone_and_name/', self.generateSignature(data)) 757 | 758 | def getDirectShare(self): 759 | return self.SendRequest('direct_share/inbox/?') 760 | 761 | def backup(self): 762 | # TODO Instagram.php 1470-1485 763 | return False 764 | 765 | def follow(self, userId): 766 | data = json.dumps({'_uuid': self.uuid, 767 | '_uid': self.username_id, 768 | 'user_id': userId, 769 | '_csrftoken': self.token}) 770 | return self.SendRequest('friendships/create/' + str(userId) + '/', self.generateSignature(data)) 771 | 772 | def unfollow(self, userId): 773 | data = json.dumps({'_uuid': self.uuid, 774 | '_uid': self.username_id, 775 | 'user_id': userId, 776 | '_csrftoken': self.token}) 777 | return self.SendRequest('friendships/destroy/' + str(userId) + '/', self.generateSignature(data)) 778 | 779 | def block(self, userId): 780 | data = json.dumps({'_uuid': self.uuid, 781 | '_uid': self.username_id, 782 | 'user_id': userId, 783 | '_csrftoken': self.token}) 784 | return self.SendRequest('friendships/block/' + str(userId) + '/', self.generateSignature(data)) 785 | 786 | def unblock(self, userId): 787 | data = json.dumps({'_uuid': self.uuid, 788 | '_uid': self.username_id, 789 | 'user_id': userId, 790 | '_csrftoken': self.token}) 791 | return self.SendRequest('friendships/unblock/' + str(userId) + '/', self.generateSignature(data)) 792 | 793 | def userFriendship(self, userId): 794 | data = json.dumps({'_uuid': self.uuid, 795 | '_uid': self.username_id, 796 | 'user_id': userId, 797 | '_csrftoken': self.token}) 798 | return self.SendRequest('friendships/show/' + str(userId) + '/', self.generateSignature(data)) 799 | 800 | def getLikedMedia(self, maxid=''): 801 | return self.SendRequest('feed/liked/?max_id=' + str(maxid)) 802 | 803 | def generateSignature(self, data, skip_quote=False): 804 | if not skip_quote: 805 | try: 806 | parsedData = urllib.parse.quote(data) 807 | except AttributeError: 808 | parsedData = urllib.quote(data) 809 | else: 810 | parsedData = data 811 | return 'ig_sig_key_version=' + self.SIG_KEY_VERSION + '&signed_body=' + hmac.new(self.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + parsedData 812 | 813 | def generateDeviceId(self, seed): 814 | volatile_seed = "12345" 815 | m = hashlib.md5() 816 | m.update(seed.encode('utf-8') + volatile_seed.encode('utf-8')) 817 | return 'android-' + m.hexdigest()[:16] 818 | 819 | def generateUUID(self, type): 820 | generated_uuid = str(uuid.uuid4()) 821 | if (type): 822 | return generated_uuid 823 | else: 824 | return generated_uuid.replace('-', '') 825 | 826 | def generateUploadId(self): 827 | return str(calendar.timegm(datetime.utcnow().utctimetuple())) 828 | 829 | def buildBody(self, bodies, boundary): 830 | body = "" 831 | for b in bodies: 832 | body += "--{boundary}\r\n".format(boundary=boundary) 833 | body += 'Content-Disposition: {b_type}; name="{b_name}"'.format(b_type=b['type'], b_name=b['name']) 834 | _filename = b.get('filename', None) 835 | _headers = b.get('headers', None) 836 | if _filename: 837 | _filename, ext = os.path.splitext(_filename) 838 | body += '; filename="pending_media_{uid}.{ext}'.format(uid=self.generateUploadId(), ext=ext) 839 | if _headers and isinstance(_headers, list): 840 | for h in _headers: 841 | body += '\r\n{header}'.format(header=h) 842 | body += '\r\n\r\n{data}\r\n'.format(data=b['data']) 843 | body += '--{boundary}--'.format(boundary=boundary) 844 | return body 845 | 846 | def SendRequest(self, endpoint, post=None, login=False): 847 | verify = False # don't show request warning 848 | 849 | if (not self.isLoggedIn and not login): 850 | raise Exception("Not logged in!\n") 851 | 852 | self.s.headers.update({'Connection': 'close', 853 | 'Accept': '*/*', 854 | 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 855 | 'Cookie2': '$Version=1', 856 | 'Accept-Language': 'en-US', 857 | 'User-Agent': self.USER_AGENT}) 858 | 859 | while True: 860 | try: 861 | if (post is not None): 862 | response = self.s.post(self.API_URL + endpoint, data=post, verify=verify) 863 | else: 864 | response = self.s.get(self.API_URL + endpoint, verify=verify) 865 | break 866 | except Exception as e: 867 | print('Except on SendRequest (wait 60 sec and resend): ' + str(e)) 868 | time.sleep(60) 869 | 870 | if response.status_code == 200: 871 | self.LastResponse = response 872 | self.LastJson = json.loads(response.text) 873 | return True 874 | else: 875 | print("Request return " + str(response.status_code) + " error!") 876 | time.sleep(20) 877 | # for debugging 878 | try: 879 | self.LastResponse = response 880 | self.LastJson = json.loads(response.text) 881 | print(self.LastJson) 882 | except: 883 | pass 884 | return False 885 | 886 | def getTotalFollowers(self, usernameId): 887 | followers = [] 888 | next_max_id = '' 889 | while 1: 890 | self.getUserFollowers(usernameId, next_max_id) 891 | temp = self.LastJson 892 | 893 | for item in temp["users"]: 894 | followers.append(item) 895 | 896 | if temp["big_list"] is False: 897 | return followers 898 | next_max_id = temp["next_max_id"] 899 | 900 | def getTotalFollowings(self, usernameId): 901 | followers = [] 902 | next_max_id = '' 903 | while True: 904 | self.getUserFollowings(usernameId, next_max_id) 905 | temp = self.LastJson 906 | 907 | for item in temp["users"]: 908 | followers.append(item) 909 | 910 | if temp["big_list"] is False: 911 | return followers 912 | next_max_id = temp["next_max_id"] 913 | 914 | def getTotalUserFeed(self, usernameId, minTimestamp=None): 915 | user_feed = [] 916 | next_max_id = '' 917 | while True: 918 | self.getUserFeed(usernameId, next_max_id, minTimestamp) 919 | temp = self.LastJson 920 | for item in temp["items"]: 921 | user_feed.append(item) 922 | if temp["more_available"] is False: 923 | return user_feed 924 | next_max_id = temp["next_max_id"] 925 | 926 | def getTotalSelfUserFeed(self, minTimestamp=None): 927 | return self.getTotalUserFeed(self.username_id, minTimestamp) 928 | 929 | def getTotalSelfFollowers(self): 930 | return self.getTotalFollowers(self.username_id) 931 | 932 | def getTotalSelfFollowings(self): 933 | return self.getTotalFollowings(self.username_id) 934 | 935 | def getTotalLikedMedia(self, scan_rate=1): 936 | next_id = '' 937 | liked_items = [] 938 | for x in range(0, scan_rate): 939 | temp = self.getLikedMedia(next_id) 940 | temp = self.LastJson 941 | try: 942 | next_id = temp["next_max_id"] 943 | for item in temp["items"]: 944 | liked_items.append(item) 945 | except KeyError as e: 946 | break 947 | return liked_items 948 | 949 | 950 | 951 | ## RXC ## 952 | def getRetryContext(self): 953 | return json.dumps({'num_step_auto_retry': 0, 954 | 'num_reupload': 0, 955 | 'num_step_manual_retry':0}) 956 | 957 | def bytes_from_file(filename, chunksize=8192): 958 | with open(filename, "rb") as f: 959 | while True: 960 | chunk = f.read(chunksize) 961 | if chunk: 962 | for b in chunk: 963 | yield b 964 | else: 965 | break 966 | 967 | def UpId(self): 968 | currmil = int(round(time.time() * 10000)) 969 | ctime = str(currmil) 970 | return ctime 971 | 972 | def prepare_direct_video(self, recipients, filepath): 973 | baseheaders = copy.deepcopy(self.s.headers) 974 | try: 975 | uploadId = self.UpId() 976 | 977 | #Initial Request 978 | 979 | hashCode = str(hash(filepath) % 1000000000) 980 | waterfallId = self.generateUUID(True) 981 | videoEntityName = uploadId + "_0_" + hashCode 982 | 983 | uri = "https://i.instagram.com/rupload_igvideo/" + videoEntityName 984 | retryContext = self.getRetryContext() 985 | 986 | uploadParams = json.dumps({'upload_media_height' : "0", # we could provide that 987 | 'direct_v2': "1", 988 | 'upload_media_width': "0", # we could provide that 989 | 'upload_media_duration_ms': "0", 990 | 'upload_id': uploadId, 991 | 'retry_context': retryContext, 992 | 'media_type': "2"}) 993 | 994 | 995 | self.s.headers.update({'Accept-Language': 'en-US', 996 | 'X-IG-Capabilities': '3Q4=', 997 | 'X-IG-Connection-Type': 'WIFI', 998 | 'User-Agent': self.USER_AGENT, 999 | 'X-IG-App-ID': '567067343352427', 1000 | 'X_FB_VIDEO_WATERFALL_ID': waterfallId, 1001 | 'X-Instagram-Rupload-Params': uploadParams, 1002 | 'Host': 'i.instagram.com', 1003 | 'Connection': 'keep-alive' 1004 | }) 1005 | 1006 | response = self.s.get(uri) 1007 | if response.status_code != 200: 1008 | print("Request return " + str(response.status_code) + " error!") 1009 | self.s.headers = baseheaders 1010 | time.sleep(20) 1011 | raise Exception('Handshake error') 1012 | return 1013 | 1014 | # Video Upload 1015 | videobytes = open(filepath, 'rb').read() 1016 | entitytype = 'video/mp4' 1017 | 1018 | self.s.headers.update({'Accept-Language': 'en-US', 1019 | 'X-IG-Capabilities': '3Q4=', 1020 | 'X-IG-Connection-Type': 'WIFI', 1021 | 'User-Agent': self.USER_AGENT, 1022 | 'X-IG-App-ID': '567067343352427', 1023 | 'X-Entity-Type': 'video/mp4', 1024 | 'Offset': '0', 1025 | 'X-Instagram-Rupload-Params': uploadParams, 1026 | 'X-Entity-Name': entitytype, 1027 | 'X-Entity-Length': str(len(videobytes)), 1028 | 'X_FB_VIDEO_WATERFALL_ID': waterfallId, 1029 | 'Host': 'i.instagram.com', 1030 | 'Connection': 'keep-alive', 1031 | 'Expect': '100-continue' 1032 | }) 1033 | 1034 | 1035 | response = self.s.post(uri, data=videobytes) 1036 | if response.status_code != 200: 1037 | print("Request return " + str(response.status_code) + " error!") 1038 | self.s.headers = baseheaders 1039 | raise Exception('Upload error') 1040 | return 1041 | 1042 | 1043 | nice = dVideo(copy.deepcopy(self.s.headers), uploadId, recipients) 1044 | self.s.headers = baseheaders 1045 | return nice 1046 | except Exception as e: 1047 | try: 1048 | self.s.headers = baseheaders 1049 | except : 1050 | pass 1051 | raise e 1052 | 1053 | 1054 | 1055 | def send_direct_video(self, dVideo): 1056 | baseheaders = copy.deepcopy(self.s.headers) 1057 | try: 1058 | self.s.headers = dVideo.header 1059 | confuri="https://i.instagram.com/api/v1/direct_v2/threads/broadcast/configure_video/" 1060 | 1061 | content = "" 1062 | content += "action=send_item" 1063 | content += "&client_context=" + str(self.generateUUID(True)) 1064 | content += "&_csrftoken=" + self.token 1065 | content += "&video_result=" 1066 | content += "&_uuid=" + self.uuid 1067 | content += "&upload_id=" + dVideo.upload_id 1068 | content += "&recipient_users=%5B%5B" + dVideo.recipient + "%5D%5D" 1069 | 1070 | self.s.headers.update({'Accept-Language': 'en-US', 1071 | 'X-IG-Capabilities': '3Q4=', 1072 | 'X-IG-Connection-Type': 'WIFI', 1073 | 'User-Agent': self.USER_AGENT, 1074 | 'X-IG-App-ID': '567067343352427', 1075 | 'retry_context': self.getRetryContext(), 1076 | 'Content-Type': 'application/x-www-form-urlencoded', 1077 | 'Host': 'i.instagram.com', 1078 | 'Content-Length': str(len(content)), 1079 | 'Expect': '100-continue'}) 1080 | 1081 | response = self.s.post(confuri, data=content) 1082 | if response.status_code != 200: 1083 | tried = 0 1084 | while response.status_code == 202 and not tried > 10: 1085 | print("Still Transcoding..") 1086 | time.sleep(2) 1087 | response = self.s.post(confuri, data=content) 1088 | tried+=1 1089 | if response.status_code != 200: 1090 | print("Request return " + str(response.status_code) + " error!") 1091 | self.s.headers = baseheaders 1092 | raise Exception('Unable to configure video' + response.text) 1093 | return 1094 | except : 1095 | raise Exception('Unable to configure video' + response.text) 1096 | 1097 | self.s.headers = baseheaders 1098 | 1099 | def is_user_following(self, username): 1100 | return True 1101 | try: 1102 | url = "https://www.instagram.com/{}/?__a=1".format(username) 1103 | response = self.s.get(url) 1104 | if response.status_code != 200: 1105 | return True 1106 | 1107 | data = response.json() 1108 | return data['graphql']['user']['follows_viewer'] 1109 | except Exception: 1110 | return True 1111 | 1112 | 1113 | def get_pending_inbox(self): 1114 | url = ( 1115 | "direct_v2/pending_inbox/?persistentBadging=true" "&use_unified_inbox=true" 1116 | ) 1117 | return self.SendRequest(url) 1118 | 1119 | 1120 | def default_data(self): 1121 | return {"_uuid": self.uuid, "_uid": self.username_id, "_csrftoken": self.token} 1122 | def json_data(self, data=None): 1123 | """Adds the default_data to data and dumps it to a json.""" 1124 | if data is None: 1125 | data = {} 1126 | data.update(self.default_data()) 1127 | return json.dumps(data) 1128 | 1129 | def approve_pending_thread(self, thread_id): 1130 | data = self.json_data({"_uuid": self.uuid, "_csrftoken": self.token}) 1131 | url = "https://i.instagram.com/api/v1/direct_v2/threads/{}/approve/".format(thread_id) 1132 | r = self.s.post(url, data=data) 1133 | self.LastResponse = r 1134 | if r.status_code == 200: 1135 | self.LastJson = json.loads(r.content) 1136 | 1137 | return r.status_code == 200 1138 | 1139 | #image 1140 | def prepare_direct_image(self, recipients, filepath): 1141 | baseheaders = copy.deepcopy(self.s.headers) 1142 | try: 1143 | uploadId = self.UpId() 1144 | 1145 | #Initial Request 1146 | 1147 | hashCode = str(hash(filepath) % 1000000000) 1148 | waterfallId = self.generateUUID(True) 1149 | videoEntityName = uploadId + "_0_" + hashCode 1150 | 1151 | uri = "https://i.instagram.com/rupload_igphoto/" + videoEntityName 1152 | retryContext = self.getRetryContext() 1153 | 1154 | uploadParams = json.dumps({'upload_media_height' : "0", # we could provide that 1155 | 'direct_v2': "1", 1156 | 'upload_media_width': "0", # we could provide that 1157 | 'upload_media_duration_ms': "0", 1158 | 'upload_id': uploadId, 1159 | 'retry_context': retryContext, 1160 | 'media_type': "1"}) 1161 | 1162 | 1163 | self.s.headers.update({'Accept-Language': 'en-US', 1164 | 'X-IG-Capabilities': '3Q4=', 1165 | 'X-IG-Connection-Type': 'WIFI', 1166 | 'User-Agent': self.USER_AGENT, 1167 | 'X-IG-App-ID': '567067343352427', 1168 | 'X_FB_VIDEO_WATERFALL_ID': waterfallId, 1169 | 'X-Instagram-Rupload-Params': uploadParams, 1170 | 'Host': 'i.instagram.com', 1171 | 'Connection': 'keep-alive' 1172 | }) 1173 | 1174 | response = self.s.get(uri) 1175 | if response.status_code != 200: 1176 | print("Request return " + str(response.status_code) + " error!") 1177 | self.s.headers = baseheaders 1178 | raise Exception('Handshake error') 1179 | return 1180 | 1181 | # Video Upload 1182 | videobytes = open(filepath, 'rb').read() 1183 | entitytype = 'image/jpeg' 1184 | 1185 | self.s.headers.update({'Accept-Language': 'en-US', 1186 | 'X-IG-Capabilities': '3Q4=', 1187 | 'X-IG-Connection-Type': 'WIFI', 1188 | 'User-Agent': self.USER_AGENT, 1189 | 'X-IG-App-ID': '567067343352427', 1190 | 'X-Entity-Type': 'image/jpeg', 1191 | 'Offset': '0', 1192 | 'X-Instagram-Rupload-Params': uploadParams, 1193 | 'X-Entity-Name': entitytype, 1194 | 'X-Entity-Length': str(len(videobytes)), 1195 | 'X_FB_VIDEO_WATERFALL_ID': waterfallId, 1196 | 'Host': 'i.instagram.com', 1197 | 'Connection': 'keep-alive', 1198 | 'Expect': '100-continue' 1199 | }) 1200 | 1201 | 1202 | response = self.s.post(uri, data=videobytes) 1203 | if response.status_code != 200: 1204 | print("Request return " + str(response.status_code) + " error!") 1205 | self.s.headers = baseheaders 1206 | raise Exception('Upload error') 1207 | return 1208 | 1209 | 1210 | nice = dVideo(copy.deepcopy(self.s.headers), uploadId, recipients) 1211 | self.s.headers = baseheaders 1212 | return nice 1213 | except Exception as e: 1214 | try: 1215 | self.s.headers = baseheaders 1216 | except : 1217 | pass 1218 | raise e 1219 | 1220 | 1221 | 1222 | def send_direct_image(self, dVideo): 1223 | baseheaders = copy.deepcopy(self.s.headers) 1224 | try: 1225 | self.s.headers = dVideo.header 1226 | confuri="https://i.instagram.com/api/v1/direct_v2/threads/broadcast/configure_photo/" 1227 | 1228 | content = "" 1229 | content += "action=send_item" 1230 | content += "&client_context=" + str(self.generateUUID(True)) 1231 | content += "&_csrftoken=" + self.token 1232 | content += "&video_result=" 1233 | content += "&_uuid=" + self.uuid 1234 | content += "&upload_id=" + dVideo.upload_id 1235 | content += "&recipient_users=%5B%5B" + dVideo.recipient + "%5D%5D" 1236 | 1237 | self.s.headers.update({'Accept-Language': 'en-US', 1238 | 'X-IG-Capabilities': '3Q4=', 1239 | 'X-IG-Connection-Type': 'WIFI', 1240 | 'User-Agent': self.USER_AGENT, 1241 | 'X-IG-App-ID': '567067343352427', 1242 | 'retry_context': self.getRetryContext(), 1243 | 'Content-Type': 'application/x-www-form-urlencoded', 1244 | 'Host': 'i.instagram.com', 1245 | 'Content-Length': str(len(content)), 1246 | 'Expect': '100-continue'}) 1247 | 1248 | response = self.s.post(confuri, data=content) 1249 | if response.status_code != 200: 1250 | tried = 0 1251 | while response.status_code == 202 and not tried > 10: 1252 | print("Still Transcoding..") 1253 | time.sleep(5) 1254 | response = self.s.post(confuri, data=content) 1255 | tried+=1 1256 | if response.status_code != 200: 1257 | print("Request return " + str(response.status_code) + " error!") 1258 | self.s.headers = baseheaders 1259 | raise Exception('Unable to configure video' + response.text) 1260 | return 1261 | except : 1262 | raise Exception('Unable to configure video' + response.text) 1263 | 1264 | self.s.headers = baseheaders 1265 | 1266 | def get_id_from_username(self, username): 1267 | if self.searchUsername(username): 1268 | return self.LastJson["user"]["pk"] 1269 | return None 1270 | 1271 | 1272 | class InstagramLogin(object): 1273 | def __init__(self,username, password, folder = Path("./")): 1274 | self.username = username 1275 | self.password = password 1276 | self.path = Path(str(folder) + "/" + username + ".session") 1277 | 1278 | if not os.path.exists(str(folder)): 1279 | os.mkdir(folder) 1280 | 1281 | if not os.path.exists(self.path): 1282 | self.api = InstagramAPI(self.username, self.password) 1283 | self.api.login() 1284 | pickle.dump(self.api, open(self.path, "wb")) 1285 | else: 1286 | self.api = pickle.load(open(self.path, "rb")) 1287 | if not self.api.isLoggedIn: 1288 | print("logging in " + username) 1289 | self.api.login() 1290 | if self.api.isLoggedIn: 1291 | pickle.dump(self.api, open(self.path, "wb")) --------------------------------------------------------------------------------