From 1a69623cfb2a82f350ca62c6dbb5322dd0146950 Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 31 Dec 2021 16:25:46 +0100 Subject: [PATCH 1/9] Added parameters to STOP method in ItsAGramLive --- ItsAGramLive/ItsAGramLive.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index 1373b8c..9e9c76f 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -545,8 +545,7 @@ def add_post_live_to_igtv(self, description, title): return True return False - def stop(self): - print('Save Live replay to IGTV ? ') + def stop(self, save_to_igtv: bool = None, title = 'Live video', description = 'Live Instagram video.'): save = input('command> ') if save == 'y': title = input("Title: ") From 593b1e19fd6418fdc96176baeb1167697d3edfa5 Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 31 Dec 2021 16:26:39 +0100 Subject: [PATCH 2/9] Added condition in STOP method where if no parameters then it asks the user what to do --- ItsAGramLive/ItsAGramLive.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index 9e9c76f..f900df7 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -546,10 +546,17 @@ def add_post_live_to_igtv(self, description, title): return False def stop(self, save_to_igtv: bool = None, title = 'Live video', description = 'Live Instagram video.'): - save = input('command> ') - if save == 'y': - title = input("Title: ") - description = input("Description: ") + final_save = save_to_igtv + + if final_save is None: + print('Save Live replay to IGTV ? ') + save_to_igtv = input('command> ') + if save_to_igtv == 'y': + final_save = 1 + title = input("Title: ") + description = input("Description: ") + + if final_save: self.add_post_live_to_igtv(description, title) print('Exiting...') From 3a787bcad6fea043a47ba1808a80b2f0e24be6cc Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 31 Dec 2021 16:55:11 +0100 Subject: [PATCH 3/9] Added a logger ( default level = INFO ) and replaced print() methods by logger.LEVEL() methods --- ItsAGramLive/ItsAGramLive.py | 94 ++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index f900df7..b15349a 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -9,6 +9,7 @@ import tempfile import pyperclip import requests +import logging # Turn off InsecureRequestWarning from requests.packages.urllib3.exceptions import InsecureRequestWarning from PIL import Image @@ -60,7 +61,7 @@ class ItsAGramLive: IG_SIG_KEY = '4f8732eb9ba7d1c8e8897a75d6474d4eb3f5279137431b2aafb71fafe2abe178' SIG_KEY_VERSION = '4' - def __init__(self, username='', password=''): + def __init__(self, username='', password='', logging_level='INFO'): if bool(username) is False and bool(password) is False: parser = argparse.ArgumentParser(add_help=True) @@ -88,6 +89,9 @@ def __init__(self, username='', password=''): 'User-Agent': self.USER_AGENT, } + logging.basicConfig(level=logging_level) + logging.getLogger("urllib3").setLevel(logging_level) + def set_user(self, username, password): self.username = username self.password = password @@ -127,6 +131,7 @@ def get_code_challenge_required(self, path, choice=0): self.send_request(path, self.generate_signature(json.dumps(data)), True) def login(self, force=False): + logging.info('Logging in.') if not self.isLoggedIn or force: if self.send_request(endpoint='si/fetch_headers/?challenge_type=signup&guid=' + self.generate_UUID(False), login=True): @@ -142,7 +147,7 @@ def login(self, force=False): if self.send_request('accounts/login/', post=self.generate_signature(json.dumps(data)), login=True): if "error_type" in self.LastJson: if self.LastJson['error_type'] == 'bad_password': - print(self.LastJson['message']) + logging.error(self.LastJson['message']) return False if "two_factor_required" not in self.LastJson: @@ -150,6 +155,7 @@ def login(self, force=False): self.username_id = self.LastJson["logged_in_user"]["pk"] self.rank_token = "%s_%s" % (self.username_id, self.uuid) self.token = self.LastResponse.cookies["csrftoken"] + logging.info('Logged in.') return True else: if self.two_factor(): @@ -157,8 +163,9 @@ def login(self, force=False): self.username_id = self.LastJson["logged_in_user"]["pk"] self.rank_token = "%s_%s" % (self.username_id, self.uuid) self.token = self.LastResponse.cookies["csrftoken"] + logging.info('Logged in.') return True - + logging.error('Error while logging in.') return False def two_factor(self): @@ -201,7 +208,7 @@ def send_request(self, endpoint, post=None, login=False, headers: dict = {}): break except Exception as e: - print('* Except on SendRequest (wait 60 sec and resend): {}'.format(str(e))) + logging.warning('* Except on SendRequest (wait 60 sec and resend): {}'.format(str(e))) time.sleep(60) if self.LastResponse.status_code == 200: @@ -209,7 +216,7 @@ def send_request(self, endpoint, post=None, login=False, headers: dict = {}): elif 'two_factor_required' in self.LastJson and self.LastResponse.status_code == 400: # even the status code isn't 200 return True if the 2FA is required if self.LastJson['two_factor_required']: - print("Two factor required") + logging.info("Two factor required") return True elif 'message' in self.LastJson and self.LastResponse.status_code == 400 and self.LastJson['message'] == 'challenge_required': path = self.LastJson['challenge']['api_path'][1:] @@ -222,8 +229,8 @@ def send_request(self, endpoint, post=None, login=False, headers: dict = {}): error_message = " - " if "message" in self.LastJson: error_message = self.LastJson['message'] - print('* ERROR({}): {}'.format(self.LastResponse.status_code, error_message)) - print(self.LastResponse) + logging.error('* ERROR({}): {}'.format(self.LastResponse.status_code, error_message)) + logging.error(self.LastResponse) return False def set_proxy(self, proxy=None): @@ -243,27 +250,27 @@ def generate_signature(self, data, skip_quote=False): self.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + parsed_data def start(self): - print("Let's do it!") + logging.info("Starting, let's do it !") if not self.login(): - print("Error {}".format(self.LastResponse.status_code)) - print(json.loads(self.LastResponse.text).get("message")) + logging.error("Error {}".format(self.LastResponse.status_code)) + logging.error(json.loads(self.LastResponse.text).get("message")) else: - print("You'r logged in") + logging.info("You're logged in.") if self.create_broadcast(): - print("Broadcast ID: {}") - print("* Broadcast ID: {}".format(self.broadcast_id)) - print("* Server URL: {}".format(self.stream_server)) - print("* Server Key: {}".format(self.stream_key)) + logging.info("Broadcast ID: {}") + logging.info("* Broadcast ID: {}".format(self.broadcast_id)) + logging.info("* Server URL: {}".format(self.stream_server)) + logging.info("* Server Key: {}".format(self.stream_key)) try: pyperclip.copy(self.stream_key) - print("The stream key was automatically copied to your clipboard") + logging.info("The stream key was automatically copied to your clipboard") except pyperclip.PyperclipException as headless_error: - print("Could not find a copy/paste mechanism for your system") + logging.error("Could not find a copy/paste mechanism for your system") pass - print("Press Enter after your setting your streaming software.") + logging.info("Press Enter after your setting your streaming software.") if self.start_broadcast(): self.is_running = True @@ -285,7 +292,7 @@ def start(self): elif cmd == 'viewers': users, ids = self.get_viewer_list() - print(users) + logging.info(users) elif cmd == 'comments': self.get_comments() @@ -295,7 +302,7 @@ def start(self): if to_send: self.pin_comment(to_send) else: - print('usage: chat ') + logging.info('usage: chat ') elif cmd[:5] == 'unpin': self.unpin_comment() @@ -305,13 +312,13 @@ def start(self): if to_send: self.send_comment(to_send) else: - print('usage: chat ') + logging.info('usage: chat ') elif cmd == 'wave': users, ids = self.get_viewer_list() for i in range(len(users)): - print(f'{i + 1}. {users[i]}') - print('Type number according to user e.g 1.') + logging.info(f'{i + 1}. {users[i]}') + logging.info('Type number according to user e.g 1.') while True: cmd = input('number> ') @@ -322,10 +329,10 @@ def start(self): self.wave(ids[user_id]) break except: - print('Please type number e.g 1') + logging.error('Please type number e.g 1') else: - print( + logging.info( 'Available commands:\n\t ' '"stop"\n\t ' '"mute comments"\n\t ' @@ -360,17 +367,17 @@ def live_info(self): if self.send_request("live/{}/info/".format(self.broadcast_id)): viewer_count = self.LastJson['viewer_count'] - print("[*]Broadcast ID: {}".format(self.broadcast_id)) - print("[*]Server URL: {}".format(self.stream_server)) - print("[*]Stream Key: {}".format(self.stream_key)) - print("[*]Viewer Count: {}".format(viewer_count)) - print("[*]Status: {}".format(self.LastJson['broadcast_status'])) + logging.info("[*]Broadcast ID: {}".format(self.broadcast_id)) + logging.info("[*]Server URL: {}".format(self.stream_server)) + logging.info("[*]Stream Key: {}".format(self.stream_key)) + logging.info("[*]Viewer Count: {}".format(viewer_count)) + logging.info("[*]Status: {}".format(self.LastJson['broadcast_status'])) def mute_comments(self): data = json.dumps({'_uuid': self.uuid, '_uid': self.username_id, '_csrftoken': self.token}) if self.send_request(endpoint='live/{}/mute_comment/'.format(self.broadcast_id), post=self.generate_signature(data)): - print("Comments muted") + logging.info("Comments muted") return True return False @@ -379,7 +386,7 @@ def unmute_comment(self): data = json.dumps({'_uuid': self.uuid, '_uid': self.username_id, '_csrftoken': self.token}) if self.send_request(endpoint='live/{}/unmute_comment/'.format(self.broadcast_id), post=self.generate_signature(data)): - print("Comments un-muted") + logging.info("Comments un-muted") return True return False @@ -398,6 +405,7 @@ def send_comment(self, msg): return False def create_broadcast(self): + logging.info("Creating broadcast...") data = json.dumps({'_uuid': self.uuid, '_uid': self.username_id, 'preview_height': self.previewHeight, @@ -416,21 +424,25 @@ def create_broadcast(self): self.stream_server = upload_url[0] self.stream_key = "{}{}".format(str(self.broadcast_id), upload_url[1]) + logging.info("Broadcast created.") return True else: - + logging.error("Error while creating broadcast.") return False def start_broadcast(self): + logging.info("Starting broadcast...") data = json.dumps({'_uuid': self.uuid, '_uid': self.username_id, 'should_send_notifications': 1, '_csrftoken': self.token}) if self.send_request(endpoint='live/' + str(self.broadcast_id) + '/start/', post=self.generate_signature(data)): + logging.info("Broadcast started.") return True else: + logging.error("Error while starting broadcast.") return False def end_broadcast(self): @@ -541,7 +553,7 @@ def add_post_live_to_igtv(self, description, title): } if self.send_request(endpoint='media/configure_to_igtv/', post=self.generate_signature(data), headers=h): - print('Live Posted to Story!') + logging.info('Live Posted to Story!') return True return False @@ -549,7 +561,7 @@ def stop(self, save_to_igtv: bool = None, title = 'Live video', description = 'L final_save = save_to_igtv if final_save is None: - print('Save Live replay to IGTV ? ') + logging.info('Save Live replay to IGTV ? ') save_to_igtv = input('command> ') if save_to_igtv == 'y': final_save = 1 @@ -559,18 +571,18 @@ def stop(self, save_to_igtv: bool = None, title = 'Live video', description = 'L if final_save: self.add_post_live_to_igtv(description, title) - print('Exiting...') + logging.info('Ending broadcast...') self.end_broadcast() self.is_running = False - print('Bye bye') + logging.info('Bye bye') def get_comments(self): if self.send_request("live/{}/get_comment/".format(self.broadcast_id)): if 'comments' in self.LastJson: for comment in self.LastJson['comments']: - print(f"{comment['user']['username']} has posted a new comment: {comment['text']}") + logging.info(f"{comment['user']['username']} has posted a new comment: {comment['text']}") else: - print("There is no comments.") + logging.info("There is no comments.") def pin_comment(self, to_send): if self.send_comment(msg=to_send): @@ -587,7 +599,7 @@ def pin_comment(self, to_send): }) if self.send_request(endpoint='live/{}/pin_comment/'.format(self.broadcast_id), post=self.generate_signature(data)): - print('Comment pinned!') + logging.info('Comment pinned!') return True return False @@ -601,6 +613,6 @@ def unpin_comment(self): }) if self.send_request(endpoint='live/{}/unpin_comment/'.format(self.broadcast_id), post=self.generate_signature(data)): - print('Comment unpinned!') + logging.info('Comment unpinned!') return True return False From fea0eb9a023f1c37c027a3c5fef0fb2afe5b093a Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 31 Dec 2021 17:07:20 +0100 Subject: [PATCH 4/9] Added try/except while parsing args to prevent SystemExit to make the whole application crash --- ItsAGramLive/ItsAGramLive.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index b15349a..7ca940b 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -62,13 +62,19 @@ class ItsAGramLive: SIG_KEY_VERSION = '4' def __init__(self, username='', password='', logging_level='INFO'): + logging.basicConfig(level=logging_level) + logging.getLogger("urllib3").setLevel(logging_level) if bool(username) is False and bool(password) is False: parser = argparse.ArgumentParser(add_help=True) parser.add_argument("-u", "--username", type=str, help="username", required=True) parser.add_argument("-p", "--password", type=str, help="password", required=True) parser.add_argument("-proxy", type=str, help="Proxy format - user:password@ip:port", default=None) - args = parser.parse_args() + try: + args = parser.parse_args() + except SystemExit: + logging.fatal('Error while parsing arguments. Did you provide your Username & Password ?') + raise Exception('Credentials not provided') username = args.username password = args.password @@ -89,8 +95,6 @@ def __init__(self, username='', password='', logging_level='INFO'): 'User-Agent': self.USER_AGENT, } - logging.basicConfig(level=logging_level) - logging.getLogger("urllib3").setLevel(logging_level) def set_user(self, username, password): self.username = username From 16aa39e3b89248b753ff443da46b67d0f6ad189d Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 4 Feb 2022 16:37:37 +0100 Subject: [PATCH 5/9] Removed PIL usage --- ItsAGramLive/ItsAGramLive.py | 40 ------------------------------------ 1 file changed, 40 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index 7ca940b..2409867 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -12,7 +12,6 @@ import logging # Turn off InsecureRequestWarning from requests.packages.urllib3.exceptions import InsecureRequestWarning -from PIL import Image requests.packages.urllib3.disable_warnings(InsecureRequestWarning) @@ -460,45 +459,6 @@ def get_post_live_thumbnails(self): if self.send_request(endpoint="live/{}/get_post_live_thumbnails/".format(self.broadcast_id)): return self.LastJson.get("thumbnails")[int(len(self.LastJson.get("thumbnails")) / 2)] - def upload_live_thumbnails(self): - im1 = Image.open(requests.get(self.get_post_live_thumbnails(), stream=True).raw) - size = 1080, 1920 - im1 = im1.resize(size, Image.ANTIALIAS) - upload_id = str(int(time.time() * 1000)) - link = os.path.join(tempfile.gettempdir(), "{}.jpg".format(upload_id)) - im1.save(link, "JPEG", quality=100) - - upload_idforurl = "{}_0_{}".format(upload_id, str(hash(os.path.basename(link)))) - - rupload_params = { - "retry_context": '{"num_step_auto_retry":0,"num_reupload":0,"num_step_manual_retry":0}', - "media_type": "1", - "broadcast_id": self.broadcast_id, - "is_post_live_igtv":"1", - "xsharing_user_ids": "[]", - "upload_id": upload_id, - "image_compression": json.dumps( - {"lib_name": "moz", "lib_version": "3.1.m", "quality": "80"} - ), - } - - h = { - "Accept-Encoding": "gzip", - "X-Instagram-Rupload-Params": json.dumps(rupload_params), - "X-Entity-Type": "image/jpeg", - "Offset": "0", - "X-Entity-Name": upload_idforurl, - "X-Entity-Length": str(os.path.getsize(link)), - "Content-Type": "application/octet-stream", - "Content-Length": str(os.path.getsize(link)), - "Accept-Encoding": "gzip", - } - - data = open(link, 'rb').read() - - if self.send_request(endpoint="../../rupload_igphoto/{}".format(upload_idforurl), post=data, headers=h): - if self.LastJson.get('status') == 'ok': - return self.LastJson.get('upload_id') def add_post_live_to_igtv(self, description, title): self.end_broadcast() From cd5c7425e37f593e8d2ef2179d521c9ddafb5474 Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 4 Feb 2022 16:47:23 +0100 Subject: [PATCH 6/9] Removed Pillow from dependencies --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eec7b92..5dc1a4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ pyperclip>=1.8.0 requests>=2.23.0 -Pillow=8.1.1 From 1022287ede50d136eb5381c09b6fa646600bca19 Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Tue, 8 Feb 2022 17:59:22 +0100 Subject: [PATCH 7/9] Challenge now send an email automatically & returns the error --- ItsAGramLive/ItsAGramLive.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index 2409867..c4bc26a 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -223,10 +223,11 @@ def send_request(self, endpoint, post=None, login=False, headers: dict = {}): return True elif 'message' in self.LastJson and self.LastResponse.status_code == 400 and self.LastJson['message'] == 'challenge_required': path = self.LastJson['challenge']['api_path'][1:] - choice = int(input('Choose a challenge mode (0 - SMS, 1 - Email): ')) - self.get_code_challenge_required(path, choice) - code = input('Enter the code: ') - self.set_code_challenge_required(path, code) + # choice = int(input('Choose a challenge mode (0 - SMS, 1 - Email): ')) + self.get_code_challenge_required(path, 1) + return self.LastJson['message'] + # code = input('Enter the code: ') + # self.set_code_challenge_required(path, code) # if message is 'Pre-allocated media not Found.' else: error_message = " - " From 7ab67fffa8493295f0f62d763c7f31c569f1c610 Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Tue, 8 Feb 2022 18:21:10 +0100 Subject: [PATCH 8/9] Challenge_required now raises an Exception("challenge_required") --- ItsAGramLive/ItsAGramLive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index c4bc26a..ec798c3 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -225,7 +225,7 @@ def send_request(self, endpoint, post=None, login=False, headers: dict = {}): path = self.LastJson['challenge']['api_path'][1:] # choice = int(input('Choose a challenge mode (0 - SMS, 1 - Email): ')) self.get_code_challenge_required(path, 1) - return self.LastJson['message'] + raise Exception("challenge_required") # code = input('Enter the code: ') # self.set_code_challenge_required(path, code) # if message is 'Pre-allocated media not Found.' From 24e7177569729c9aa5cad0e90cffc8cfd095346c Mon Sep 17 00:00:00 2001 From: Dylan Cattelan Date: Fri, 11 Feb 2022 19:03:57 +0100 Subject: [PATCH 9/9] Added exception when bad password + return instance of instagram in create_broadcast --- ItsAGramLive/ItsAGramLive.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ItsAGramLive/ItsAGramLive.py b/ItsAGramLive/ItsAGramLive.py index ec798c3..54fbc73 100644 --- a/ItsAGramLive/ItsAGramLive.py +++ b/ItsAGramLive/ItsAGramLive.py @@ -168,8 +168,7 @@ def login(self, force=False): self.token = self.LastResponse.cookies["csrftoken"] logging.info('Logged in.') return True - logging.error('Error while logging in.') - return False + raise Exception("bad_password") def two_factor(self): # verification_method': 0 works for sms and TOTP. why? ¯\_ಠ_ಠ_/¯ @@ -429,7 +428,7 @@ def create_broadcast(self): self.stream_key = "{}{}".format(str(self.broadcast_id), upload_url[1]) logging.info("Broadcast created.") - return True + return self else: logging.error("Error while creating broadcast.")