From b0c9e3f1a518fe20679189210b03b0cf2ab51580 Mon Sep 17 00:00:00 2001 From: Gary Host Date: Wed, 21 Oct 2020 19:53:44 +0200 Subject: [PATCH 1/6] Added upload_photo method --- instagram_private_api/endpoints/upload.py | 102 +++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index c173a45a..950020ee 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -3,6 +3,9 @@ from random import randint import re import warnings +from urllib.parse import urlparse +from os.path import splitext +import uuid from ..compat import ( compat_urllib_error, compat_urllib_request, @@ -377,6 +380,103 @@ def configure_video_to_reel(self, upload_id, size, duration, thumbnail_data): ClientCompatPatch.media(res.get('media'), drop_incompat_keys=self.drop_incompat_keys) return res + def is_image(self, url): + image_formats = [".jpeg", ".jpg"] + parsed = urlparse(url) + root, ext = splitext(parsed.path) + if ext in image_formats: + return ext + return False + + def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, is_sidecar=False, **kwargs) -> bool: + """ + Upload an image and post it. + + :param img: io.BufferedReader + :param size: tuple of (width, height) + :param caption: + :param location: a dict of venue/location information, from :meth:`location_search` + or :meth:`location_fb_search` + :param disable_comments: bool to disable comments + :return bool: + """ + is_img = self.is_image(img.name) + + if is_img: + image_props = { + 'caption': '', + 'edits': { + 'crop_center': [0.0, 0.0], + 'crop_original_size': size, + 'crop_zoom': 1.0 + }, + 'entity_type': f'image/{is_img[1:]}', + 'extra': {'source_height': size[1], 'source_width': size[0]}, + 'image_path': img.name, + 'location': None, + 'media_folder': 'Pictures', + 'multi_sharing': '-1', + 'scene_capture_type': '', + 'size': size, + 'source_type': 3, + 'suggested_venue_position': -1, + 'timezone_offset': str(time.localtime().tm_gmtoff), + 'upload_id': str(time.time()).split('.')[0], + 'x_fb_waterfall_id': str(uuid.uuid4()) + } + + image_props['entity_name'] = f'{image_props["upload_id"]}_0_{randint(1000000000, 9999999999)}' + + with open(img.name, 'rb') as f: + f.seek(0, 2) + image_props['entity_length'] = f.tell() + + image_props.pop('image_path') + + headers = { + 'x-fb-photo-waterfall-id': str(image_props['x_fb_waterfall_id']), + 'x-entity-length': str(image_props['entity_length']), + 'x-entity-name': image_props['entity_name'], + 'x-instagram-rupload-params': json.dumps({ + "upload_id": image_props['upload_id'], + "media_type": 1, + "retry_context": json.dumps({ + "num_reupload": 0, + "num_step_auto_retry": 0, + "num_step_manual_retry": 0 + }), + "xsharing_user_ids": "[]", + "image_compression": json.dumps({ + "lib_name": "moz", + "lib_version": "3.1.m", + "quality": "80" + }), + }), + 'x-entity-type': image_props['entity_type'], + 'offset': '0', + 'scene_capture_type': 'standard', + 'creation_logger_session_id': str(uuid.uuid4()) + } + + endpoint = f'https://i.instagram.com/rupload_igphoto/{headers["x-entity-name"]}' + + with open(img.name, 'rb') as f: + + req = compat_urllib_request.Request( + endpoint, data=f.read(), headers=headers + ) + + res = self.opener.open(req, timeout=self.timeout) + post_response = self._read_response(res) + + self.configure(image_props['upload_id'], size, caption=caption) + + return True + + return False + else: + raise ValueError('Incompatible image format.') + def post_photo(self, photo_data, size, caption='', upload_id=None, to_reel=False, **kwargs): """ Upload a photo. @@ -791,4 +891,4 @@ def post_album(self, medias, caption='', location=None, **kwargs): res = self._call_api(endpoint, params=params) if self.auto_patch and res.get('media'): ClientCompatPatch.media(res.get('media'), drop_incompat_keys=self.drop_incompat_keys) - return res + return res \ No newline at end of file From 4d6f3cbf8bed0ff24ba6880299c5c2b015cf314c Mon Sep 17 00:00:00 2001 From: Gary Host Date: Wed, 21 Oct 2020 20:06:03 +0200 Subject: [PATCH 2/6] Fix flake8 --- instagram_private_api/endpoints/upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index 950020ee..7ac11eb8 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -387,7 +387,7 @@ def is_image(self, url): if ext in image_formats: return ext return False - + def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, is_sidecar=False, **kwargs) -> bool: """ Upload an image and post it. @@ -467,7 +467,7 @@ def upload_image(self, img, size, quality=80, caption='', location=None, disable ) res = self.opener.open(req, timeout=self.timeout) - post_response = self._read_response(res) + self._read_response(res) self.configure(image_props['upload_id'], size, caption=caption) From 985a3d3d89721821d8ff98b5d015801995815619 Mon Sep 17 00:00:00 2001 From: Gary Host Date: Wed, 21 Oct 2020 21:52:49 +0200 Subject: [PATCH 3/6] Added upload story feature --- instagram_private_api/endpoints/upload.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index 7ac11eb8..1e7a56c6 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -388,16 +388,16 @@ def is_image(self, url): return ext return False - def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, is_sidecar=False, **kwargs) -> bool: + def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, story=False, **kwargs) -> bool: """ Upload an image and post it. - :param img: io.BufferedReader :param size: tuple of (width, height) :param caption: :param location: a dict of venue/location information, from :meth:`location_search` or :meth:`location_fb_search` :param disable_comments: bool to disable comments + :param story: bool to upload a story instead of a post :return bool: """ is_img = self.is_image(img.name) @@ -467,9 +467,12 @@ def upload_image(self, img, size, quality=80, caption='', location=None, disable ) res = self.opener.open(req, timeout=self.timeout) - self._read_response(res) + post_response = self._read_response(res) - self.configure(image_props['upload_id'], size, caption=caption) + if story: + self.configure_to_reel(image_props['upload_id'], size) + else: + self.configure(image_props['upload_id'], size, caption=caption) return True From 8a4a6d99def34e405fd6e7cc58f9733b8ba8aaa9 Mon Sep 17 00:00:00 2001 From: Gary Host Date: Sun, 25 Oct 2020 20:09:36 +0100 Subject: [PATCH 4/6] Fix flake8 --- instagram_private_api/endpoints/upload.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index 1e7a56c6..1ba44404 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -388,7 +388,11 @@ def is_image(self, url): return ext return False - def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, story=False, **kwargs) -> bool: + def upload_image(self, img, size, quality=80, caption='', + location=None, + disable_comments=False, + story=False, + **kwargs) -> bool: """ Upload an image and post it. :param img: io.BufferedReader @@ -467,7 +471,7 @@ def upload_image(self, img, size, quality=80, caption='', location=None, disable ) res = self.opener.open(req, timeout=self.timeout) - post_response = self._read_response(res) + self._read_response(res) if story: self.configure_to_reel(image_props['upload_id'], size) @@ -894,4 +898,4 @@ def post_album(self, medias, caption='', location=None, **kwargs): res = self._call_api(endpoint, params=params) if self.auto_patch and res.get('media'): ClientCompatPatch.media(res.get('media'), drop_incompat_keys=self.drop_incompat_keys) - return res \ No newline at end of file + return res From aa01c1f38401d60ad2e3d941893e1da065ede07f Mon Sep 17 00:00:00 2001 From: Gary Host Date: Sun, 25 Oct 2020 20:19:56 +0100 Subject: [PATCH 5/6] Fix build and unsecure randomness --- instagram_private_api/endpoints/upload.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index 1ba44404..0118f5af 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -1,6 +1,6 @@ import json import time -from random import randint +from secrets import choice import re import warnings from urllib.parse import urlparse @@ -302,8 +302,8 @@ def configure_to_reel(self, upload_id, size): params = { 'source_type': '4', 'upload_id': upload_id, - 'story_media_creation_date': str(int(time.time()) - randint(11, 20)), - 'client_shared_at': str(int(time.time()) - randint(3, 10)), + 'story_media_creation_date': str(int(time.time()) - choice(range(11, 20))), + 'client_shared_at': str(int(time.time()) - choice(range(3, 10))), 'client_timestamp': str(int(time.time())), 'configure_mode': 1, # 1 - REEL_SHARE, 2 - DIRECT_STORY_SHARE 'device': { @@ -348,8 +348,8 @@ def configure_video_to_reel(self, upload_id, size, duration, thumbnail_data): params = { 'source_type': '4', 'upload_id': upload_id, - 'story_media_creation_date': str(int(time.time()) - randint(11, 20)), - 'client_shared_at': str(int(time.time()) - randint(3, 10)), + 'story_media_creation_date': str(int(time.time()) - choice(range(11, 20))), + 'client_shared_at': str(int(time.time()) - choice(range(3, 10))), 'client_timestamp': str(int(time.time())), 'configure_mode': 1, # 1 - REEL_SHARE, 2 - DIRECT_STORY_SHARE 'poster_frame_index': 0, @@ -383,7 +383,7 @@ def configure_video_to_reel(self, upload_id, size, duration, thumbnail_data): def is_image(self, url): image_formats = [".jpeg", ".jpg"] parsed = urlparse(url) - root, ext = splitext(parsed.path) + ext = splitext(parsed.path)[1] if ext in image_formats: return ext return False @@ -408,7 +408,7 @@ def upload_image(self, img, size, quality=80, caption='', if is_img: image_props = { - 'caption': '', + 'caption': caption, 'edits': { 'crop_center': [0.0, 0.0], 'crop_original_size': size, @@ -429,7 +429,7 @@ def upload_image(self, img, size, quality=80, caption='', 'x_fb_waterfall_id': str(uuid.uuid4()) } - image_props['entity_name'] = f'{image_props["upload_id"]}_0_{randint(1000000000, 9999999999)}' + image_props['entity_name'] = f'{image_props["upload_id"]}_0_{choice(range(1000000000, 9999999999))}' with open(img.name, 'rb') as f: f.seek(0, 2) @@ -476,7 +476,8 @@ def upload_image(self, img, size, quality=80, caption='', if story: self.configure_to_reel(image_props['upload_id'], size) else: - self.configure(image_props['upload_id'], size, caption=caption) + self.configure(image_props['upload_id'], size, caption=caption, + location=location, disable_comments=disable_comments) return True From e61d2f9ab26eb22b8ed5e002715846ad56575bfb Mon Sep 17 00:00:00 2001 From: Gary Host Date: Sun, 25 Oct 2020 20:42:55 +0100 Subject: [PATCH 6/6] Added function to utils --- instagram_private_api/endpoints/upload.py | 14 ++------------ instagram_private_api/utils.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/instagram_private_api/endpoints/upload.py b/instagram_private_api/endpoints/upload.py index 0118f5af..6cd8863b 100644 --- a/instagram_private_api/endpoints/upload.py +++ b/instagram_private_api/endpoints/upload.py @@ -3,8 +3,6 @@ from secrets import choice import re import warnings -from urllib.parse import urlparse -from os.path import splitext import uuid from ..compat import ( @@ -15,7 +13,7 @@ from ..http import MultipartFormDataEncoder from ..utils import ( max_chunk_count_generator, max_chunk_size_generator, - get_file_size + get_file_size, is_image_jpg ) from ..compatpatch import ClientCompatPatch from .common import ClientDeprecationWarning @@ -380,14 +378,6 @@ def configure_video_to_reel(self, upload_id, size, duration, thumbnail_data): ClientCompatPatch.media(res.get('media'), drop_incompat_keys=self.drop_incompat_keys) return res - def is_image(self, url): - image_formats = [".jpeg", ".jpg"] - parsed = urlparse(url) - ext = splitext(parsed.path)[1] - if ext in image_formats: - return ext - return False - def upload_image(self, img, size, quality=80, caption='', location=None, disable_comments=False, @@ -404,7 +394,7 @@ def upload_image(self, img, size, quality=80, caption='', :param story: bool to upload a story instead of a post :return bool: """ - is_img = self.is_image(img.name) + is_img = is_image_jpg(img.name) if is_img: image_props = { diff --git a/instagram_private_api/utils.py b/instagram_private_api/utils.py index 8623a1ec..0a478e74 100644 --- a/instagram_private_api/utils.py +++ b/instagram_private_api/utils.py @@ -6,7 +6,7 @@ import os from datetime import datetime import re - +from .compat import compat_urllib_parse_urlparse VALID_UUID_RE = r'^[a-f\d]{8}\-[a-f\d]{4}\-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$' @@ -263,3 +263,18 @@ def expand_code(cls, short_code): :return: """ return cls._decode(short_code) + + +def is_image_jpg(url): + """ + Check if an image has JPG extension + + :param url: URL or filename + :return: str or bool + """ + image_formats = [".jpeg", ".jpg"] + parsed = compat_urllib_parse_urlparse(url) + ext = os.path.splitext(parsed.path)[1] + if ext in image_formats: + return ext + return False