def album_configure( self, childs: List, caption: str, usertags: List[Usertag] = [], location: Location = None, ) -> Dict: """ Post Configure Album Parameters ---------- childs: List List of media/resources of an album caption: str Media caption usertags: List[Usertag], optional List of users to be tagged on this upload, default is empty list. location: Location, optional Location tag for this upload, default is None Returns ------- Dict A dictionary of response from the call """ upload_id = str(int(time.time() * 1000)) if usertags: usertags = [ {"user_id": tag.user.pk, "position": [tag.x, tag.y]} for tag in usertags ] childs[0]["usertags"] = dumps({"in": usertags}) data = { "timezone_offset": "10800", "source_type": "4", "creation_logger_session_id": self.client_session_id, "location": self.location_build(location), "caption": caption, "client_sidecar_id": upload_id, "upload_id": upload_id, # "location": self.build_location(name, lat, lng, address), "suggested_venue_position": -1, "device": self.device, "is_suggested_venue": False, "children_metadata": [ { "source_type": "4", "timezone_offset": "10800", "device": dumps(self.device), **child, } for child in childs ], } return self.private_request( "media/configure_sidecar/", self.with_default_data(data) )
def direct_send(self, text: str, user_ids: List[int] = [], thread_ids: List[int] = []) -> DirectMessage: """ Send a direct message to list of users or threads Parameters ---------- text: str String to be posted on the thread user_ids: List[int] List of unique identifier of Users id thread_ids: List[int] List of unique identifier of Direct Message thread id Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" assert (user_ids or thread_ids) and not ( user_ids and thread_ids), "Specify user_ids or thread_ids, but not both" method = "text" token = self.generate_mutation_token() kwargs = { "action": "send_item", "is_shh_mode": "0", "send_attribution": "direct_thread", "client_context": token, "mutation_token": token, "nav_chain": "1qT:feed_timeline:1,1qT:feed_timeline:2,1qT:feed_timeline:3,7Az:direct_inbox:4,7Az:direct_inbox:5,5rG:direct_thread:7", "offline_threading_id": token, } if "http" in text: method = "link" kwargs["link_text"] = text kwargs["link_urls"] = dumps(re.findall(r"(https?://[^\s]+)", text)) else: kwargs["text"] = text if thread_ids: kwargs["thread_ids"] = dumps([int(tid) for tid in thread_ids]) if user_ids: kwargs["recipient_users"] = dumps([[int(uid) for uid in user_ids]]) result = self.private_request(f"direct_v2/threads/broadcast/{method}/", data=self.with_default_data(kwargs), with_signature=False) return extract_direct_message(result["payload"])
def direct_story_share(self, story_id: str, user_ids: List[int] = [], thread_ids: List[int] = []) -> DirectMessage: """ Share a story to list of users Parameters ---------- story_id: str Unique Story ID user_ids: List[int] List of unique identifier of Users id thread_ids: List[int] List of unique identifier of Users id Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" assert (user_ids or thread_ids) and not ( user_ids and thread_ids), "Specify user_ids or thread_ids, but not both" story_id = self.media_id(story_id) story_pk = self.media_pk(story_id) token = self.generate_mutation_token() data = { "action": "send_item", "is_shh_mode": "0", "send_attribution": "reel_feed_timeline", "client_context": token, "mutation_token": token, "nav_chain": "1qT:feed_timeline:1,ReelViewerFragment:reel_feed_timeline:4,DirectShareSheetFragment:direct_reshare_sheet:5", "reel_id": story_pk, "containermodule": "reel_feed_timeline", "story_media_id": story_id, "offline_threading_id": token, } if user_ids: data["recipient_users"] = dumps([[int(uid) for uid in user_ids]]) if thread_ids: data["thread_ids"] = dumps([int(tid) for tid in thread_ids]) result = self.private_request( "direct_v2/threads/broadcast/story_share/", # params={'story_type': 'video'}, data=self.with_default_data(data), with_signature=False, ) return extract_direct_message(result["payload"])
def direct_send_photo(self, filepath: str, user_ids: List[int] = [], thread_ids: List[int] = []) -> DirectMessage: """ Send a direct photo to list of users or threads Parameters ---------- filepath: str Path to photo that will be posted on the thread user_ids: List[int] List of unique identifier of Users thread thread_ids: List[int] List of unique identifier of Direct Message thread Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" method = "configure_photo" kwargs = {} if user_ids: kwargs["recipient_users"] = dumps([[int(uid) for uid in user_ids]]) if thread_ids: kwargs["thread_ids"] = dumps([int(tid) for tid in thread_ids]) path = Path(filepath) upload_id = str(int(time.time() * 1000)) upload_id, width, height = self.photo_rupload(path, upload_id) kwargs['upload_id'] = upload_id kwargs['content_type'] = 'photo' data = { "client_context": self.generate_uuid(), "action": "send_item", **kwargs } result = self.private_request( "direct_v2/threads/broadcast/%s/" % method, data=self.with_default_data(data), with_signature=False, ) return extract_direct_message(result["payload"])
def highlight_create( self, title: str, story_ids: List[str], cover_story_id: str = "", crop_rect: List[float] = [0.0, 0.21830457, 1.0, 0.78094524]) -> Highlight: """ Create highlight Parameters ---------- title: str Title story_ids: List[str] List of story ids cover_story_id: str User story as cover, default is first of story_ids Returns ------- Highlight An object of Highlight type """ if not cover_story_id: cover_story_id = story_ids[0] data = { "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES), "source": "self_profile", "creation_id": str(int(time.time())), "_uid": str(self.user_id), "_uuid": self.uuid, "cover": dumps({ "media_id": self.media_id(cover_story_id), "crop_rect": dumps(crop_rect) }), "title": title, "media_ids": dumps([self.media_id(sid) for sid in story_ids]) } result = self.private_request("highlights/create_reel/", data=data) return extract_highlight_v1(result['reel'])
def bloks_change_password(self, password: str, challenge_context: dict) -> bool: """ Change password for challenge Parameters ---------- passwrd: str New password Returns ------- bool """ assert self.bloks_versioning_id, "Client.bloks_versioning_id is empty (hash is expected)" enc_password = self.password_encrypt(password) data = { "bk_client_context": dumps({ "bloks_version": self.bloks_versioning_id, "styles_id": "instagram" }), "challenge_context": challenge_context, "bloks_versioning_id": self.bloks_versioning_id, "enc_new_password1": enc_password, "enc_new_password2": enc_password, } return self.bloks_action( "com.instagram.challenge.navigation.take_challenge", data)
def direct_thread_by_participants(self, user_ids: List[int]) -> DirectThread: """ Get direct thread by participants Parameters ---------- user_ids: List[int] List of unique identifier of Users id Returns ------- DirectThread An object of DirectThread """ recipient_users = dumps([int(uid) for uid in user_ids]) result = self.private_request("direct_v2/threads/get_by_participants/", params={ "recipient_users": recipient_users, "seq_id": 2580572, "limit": 20 }) if 'thread' not in result: raise DirectThreadNotFound( f'Thread not found by recipient_users={recipient_users}', user_ids=user_ids, **self.last_json) return extract_direct_thread(result['thread'])
def direct_send(self, text: str, user_ids: List[int] = [], thread_ids: List[int] = []) -> DirectMessage: """ Send a direct message to list of users or threads Parameters ---------- text: str String to be posted on the thread user_ids: List[int] List of unique identifier of Users thread thread_ids: List[int] List of unique identifier of Direct Message thread Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" method = "text" kwargs = {} if "http" in text: method = "link" kwargs["link_text"] = text kwargs["link_urls"] = dumps(re.findall(r"(https?://[^\s]+)", text)) else: kwargs["text"] = text if thread_ids: kwargs["thread_ids"] = dumps([int(tid) for tid in thread_ids]) if user_ids: kwargs["recipient_users"] = dumps([[int(uid) for uid in user_ids]]) data = { "client_context": self.generate_uuid(), "action": "send_item", **kwargs } result = self.private_request( "direct_v2/threads/broadcast/%s/" % method, data=self.with_default_data(data), with_signature=False, ) return extract_direct_message(result["payload"])
def hashtag_medias_v1_chunk( self, name: str, max_amount: int = 27, tab_key: str = "", max_id: str = None ) -> Tuple[List[Media], str]: """ Get chunk of medias for a hashtag and max_id (cursor) by Private Mobile API Parameters ---------- name: str Name of the hashtag max_amount: int, optional Maximum number of media to return, default is 27 tab_key: str, optional Tab Key, default value is "" max_id: str Max ID, default value is None Returns ------- Tuple[List[Media], str] List of objects of Media and max_id """ assert tab_key in ("top", "recent"), \ 'You must specify one of the options for "tab_key" ("top" or "recent")' data = { "supported_tabs": dumps([tab_key]), # 'lat': 59.8626416, # 'lng': 30.5126682, "include_persistent": "true", "rank_token": self.rank_token, "count": 10000, } medias = [] while True: result = self.private_request( f"tags/{name}/sections/", params={"max_id": max_id} if max_id else {}, data=self.with_default_data(data), ) for section in result["sections"]: layout_content = section.get("layout_content") or {} nodes = layout_content.get("medias") or [] for node in nodes: if max_amount and len(medias) >= max_amount: break media = extract_media_v1(node["media"]) # check contains hashtag in caption if f"#{name}" not in media.caption_text: continue medias.append(media) if not result["more_available"]: break if max_amount and len(medias) >= max_amount: break max_id = result["next_max_id"] return medias, max_id
def authorization(self) -> str: """Build authorization header Example: Bearer IGT:2:eaW9u.....aWQiOiI0NzM5= """ if self.authorization_data: b64part = base64.b64encode( dumps(self.authorization_data).encode() ).decode() return f'Bearer IGT:2:{b64part}' return ''
def hashtag_medias_v1(self, name: str, amount: int = 27, tab_key: str = "") -> List[Media]: """ Get medias for a hashtag Parameters ---------- name: str Name of the hashtag amount: int, optional Maximum number of media to return, default is 27 tab_key: str, optional Tab Key, default value is "" Returns ------- List[Media] List of objects of Media """ data = { "supported_tabs": dumps([tab_key]), # 'lat': 59.8626416, # 'lng': 30.5126682, "include_persistent": "true", "rank_token": self.rank_token, } max_id = None medias = [] while True: result = self.private_request( f"tags/{name}/sections/", params={"max_id": max_id} if max_id else {}, data=self.with_default_data(data), ) for section in result["sections"]: layout_content = section.get("layout_content") or {} nodes = layout_content.get("medias") or [] for node in nodes: if amount and len(medias) >= amount: break media = extract_media_v1(node["media"]) # check contains hashtag in caption if f"#{name}" not in media.caption_text: continue medias.append(media) if not result["more_available"]: break if amount and len(medias) >= amount: break max_id = result["next_max_id"] if amount: medias = medias[:amount] return medias
def highlight_edit(self, highlight_pk: str, title: str = "", cover: Dict = {}, added_media_ids: List[str] = [], removed_media_ids: List[str] = []): data = { "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES), "source": "self_profile", "_uid": str(self.user_id), "_uuid": self.uuid, "added_media_ids": dumps(added_media_ids), "removed_media_ids": dumps(removed_media_ids) } if title: data["title"] = title if cover: data["cover"] = dumps(cover) result = self.private_request( f"highlights/highlight:{highlight_pk}/edit_reel/", data=data) return extract_highlight_v1(result['reel'])
def account_set_biography(self, biography: str) -> bool: """ Set biography with entities (markup) Parameters ---------- biography: str Biography raw text Returns ------- bool A boolean value """ data = {"logged_in_uids": dumps([str(self.user_id)]), "raw_text": biography} result = self.private_request( "accounts/set_biography/", self.with_default_data(data) ) return result["status"] == "ok"
def direct_media_share(self, media_id: str, user_ids: List[int]) -> DirectMessage: """ Share a media to list of users Parameters ---------- media_id: str Unique Media ID user_ids: List[int] List of unique identifier of Users id Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" media_id = self.media_id(media_id) recipient_users = dumps([[int(uid) for uid in user_ids]]) token = random.randint(6800011111111111111, 6800099999999999999) data = { 'recipient_users': recipient_users, 'action': 'send_item', 'is_shh_mode': 0, 'send_attribution': 'feed_timeline', 'client_context': token, 'media_id': media_id, 'mutation_token': token, 'nav_chain': '1VL:feed_timeline:1,1VL:feed_timeline:2,1VL:feed_timeline:5,DirectShareSheetFragment:direct_reshare_sheet:6', 'offline_threading_id': token } result = self.private_request( "direct_v2/threads/broadcast/media_share/", # params={'media_type': 'video'}, data=self.with_default_data(data), with_signature=False, ) return extract_direct_message(result["payload"])
def video_configure( self, upload_id: str, width: int, height: int, duration: int, thumbnail: Path, caption: str, usertags: List[Usertag] = [], location: Location = None, links: List[StoryLink] = [], ) -> Dict: """ Post Configure Video (send caption, thumbnail and more to Instagram) Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels duration: int Duration of the video in seconds thumbnail: str Path to thumbnail for video. When None, then thumbnail is generate automatically caption: str Media caption usertags: List[Usertag], optional List of users to be tagged on this upload, default is empty list. location: Location, optional Location tag for this upload, default is None links: List[StoryLink] URLs for Swipe Up Returns ------- Dict A dictionary of response from the call """ self.photo_rupload(Path(thumbnail), upload_id) usertags = [{ "user_id": tag.user.pk, "position": [tag.x, tag.y] } for tag in usertags] data = { "multi_sharing": "1", "creation_logger_session_id": self.client_session_id, "upload_id": upload_id, "source_type": "4", "location": self.location_build(location), "poster_frame_index": 0, "length": duration, "audio_muted": False, "usertags": dumps({"in": usertags}), "filter_type": "0", "date_time_original": time.strftime("%Y%m%dT%H%M%S.000Z", time.localtime()), "timezone_offset": "10800", "clips": [{ "length": duration, "source_type": "4" }], "extra": { "source_width": width, "source_height": height }, "device": self.device, "caption": caption, } return self.private_request("media/configure/?video=1", self.with_default_data(data))
def photo_configure_to_story( self, upload_id: str, width: int, height: int, caption: str, mentions: List[StoryMention] = [], location: Location = None, links: List[StoryLink] = [], ) -> Dict: """ Post configure photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. location: Location, optional Location tag for this upload, default is None links: List[StoryLink] URLs for Swipe Up Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) data = { "text_metadata": '[{"font_size":40.0,"scale":1.0,"width":611.0,"height":169.0,"x":0.51414347,"y":0.8487708,"rotation":0.0}]', "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES), "has_original_sound": "1", "camera_session_id": self.client_session_id, "scene_capture_type": "", "timezone_offset": "10800", "client_shared_at": str(timestamp - 5), # 5 seconds ago "story_sticker_ids": "time_sticker_digital", "media_folder": "Camera", "configure_mode": "1", "source_type": "4", "creation_surface": "camera", "imported_taken_at": (timestamp - 3 * 24 * 3600), # 3 days ago "caption": caption, "capture_type": "normal", "rich_text_format_types": '["default"]', "upload_id": upload_id, "client_timestamp": str(timestamp), "device": self.device, "implicit_location": { "media_location": {"lat": 44.64972222222222, "lng": 33.541666666666664} }, "edits": { "crop_original_size": [width * 1.0, height * 1.0], "crop_center": [0.0, 0.0], "crop_zoom": 1.0, }, "extra": {"source_width": width, "source_height": height}, } if links: links = [link.dict() for link in links] data["story_cta"] = dumps([{"links": links}]) if mentions: mentions = [ { "x": 0.5002546, "y": 0.8583542, "z": 0, "width": 0.4712963, "height": 0.0703125, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", } for mention in mentions ] data["tap_models"] = data["reel_mentions"] = json.dumps(mentions) return self.private_request( "media/configure_to_story/", self.with_default_data(data) )
def _send_private_request( self, endpoint, data=None, params=None, login=False, with_signature=True, headers=None, extra_sig=None, ): self.last_response = None self.last_json = last_json = {} # for Sentry context in traceback self.private.headers.update(self.base_headers) if headers: self.private.headers.update(headers) if not login: time.sleep(self.request_timeout) if self.user_id and login: raise Exception(f"User already login ({self.user_id})") try: if not endpoint.startswith('/'): endpoint = f"/v1/{endpoint}" api_url = f"https://{config.API_DOMAIN}/api{endpoint}" if data: # POST # Client.direct_answer raw dict # data = json.dumps(data) if with_signature: # Client.direct_answer doesn't need a signature data = generate_signature(dumps(data)) if extra_sig: data += "&".join(extra_sig) response = self.private.post(api_url, data=data, params=params) else: # GET response = self.private.get(api_url, params=params) self.logger.debug("private_request %s: %s (%s)", response.status_code, response.url, response.text) self.request_log(response) self.last_response = response response.raise_for_status() # last_json - for Sentry context in traceback self.last_json = last_json = response.json() self.logger.debug("last_json %s", last_json) except JSONDecodeError as e: self.logger.error( "Status %s: JSONDecodeError in private_request (user_id=%s, endpoint=%s) >>> %s", response.status_code, self.user_id, endpoint, response.text, ) raise ClientJSONDecodeError( "JSONDecodeError {0!s} while opening {1!s}".format( e, response.url), response=response, ) except requests.HTTPError as e: try: self.last_json = last_json = response.json() except JSONDecodeError: pass message = last_json.get("message", "") if e.response.status_code == 403: if message == "login_required": raise LoginRequired(response=e.response, **last_json) raise ClientForbiddenError(e, response=e.response, **last_json) elif e.response.status_code == 400: error_type = last_json.get("error_type") if message == "challenge_required": raise ChallengeRequired(**last_json) elif message == "feedback_required": raise FeedbackRequired(**dict( last_json, message="%s: %s" % (message, last_json.get("feedback_message")), )) elif error_type == "sentry_block": raise SentryBlock(**last_json) elif error_type == "rate_limit_error": raise RateLimitError(**last_json) elif error_type == "bad_password": raise BadPassword(**last_json) elif error_type == "two_factor_required": if not last_json['message']: last_json[ 'message'] = "Two-factor authentication required" raise TwoFactorRequired(**last_json) elif "Please wait a few minutes before you try again" in message: raise PleaseWaitFewMinutes(e, response=e.response, **last_json) elif "VideoTooLongException" in message: raise VideoTooLongException(e, response=e.response, **last_json) elif error_type or message: raise UnknownError(**last_json) # TODO: Handle last_json with {'message': 'counter get error', 'status': 'fail'} self.logger.exception(e) self.logger.warning( "Status 400: %s", message or "Empty response message. Maybe enabled Two-factor auth?") raise ClientBadRequestError(e, response=e.response, **last_json) elif e.response.status_code == 429: self.logger.warning("Status 429: Too many requests") if "Please wait a few minutes before you try again" in message: raise PleaseWaitFewMinutes(e, response=e.response, **last_json) raise ClientThrottledError(e, response=e.response, **last_json) elif e.response.status_code == 404: self.logger.warning("Status 404: Endpoint %s does not exists", endpoint) raise ClientNotFoundError(e, response=e.response, **last_json) elif e.response.status_code == 408: self.logger.warning("Status 408: Request Timeout") raise ClientRequestTimeout(e, response=e.response, **last_json) raise ClientError(e, response=e.response, **last_json) except requests.ConnectionError as e: raise ClientConnectionError( "{e.__class__.__name__} {e}".format(e=e)) if last_json.get("status") == "fail": raise ClientError(response=response, **last_json) elif "error_title" in last_json: """Example: { 'error_title': 'bad image input extra:{}', <------------- 'media': { 'device_timestamp': '1588184737203', 'upload_id': '1588184737203' }, 'message': 'media_needs_reupload', <------------- 'status': 'ok' <------------- }""" raise ClientError(response=response, **last_json) return last_json
def video_configure_to_story( self, upload_id: str, width: int, height: int, duration: int, thumbnail: Path, caption: str, mentions: List[StoryMention] = [], location: Location = None, links: List[StoryLink] = [], ) -> Dict: """ Story Configure for Photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels duration: int Duration of the video in seconds thumbnail: str Path to thumbnail for video. When None, then thumbnail is generate automatically caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. location: Location, optional Location tag for this upload, default is None links: List[StoryLink] URLs for Swipe Up Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) data = { "supported_capabilities_new": dumps(config.SUPPORTED_CAPABILITIES), "has_original_sound": "1", # Segment mode (when file is too big): # "allow_multi_configures": "1", # "segmented_video_group_id": str(uuid4()), # "multi_upload_session_id": str(uuid4()), # "segmented_video_count": "4", # "4" # SEGMENT MODE # "segmented_video_index": "0", # 0,1,2,3 # SEGMENT MODE # "is_multi_upload": "1", # SEGMENT MODE # "is_segmented_video": "1", # SEGMENT MODE "filter_type": "0", "camera_session_id": self.client_session_id, "timezone_offset": "10800", "client_timestamp": str(timestamp), "client_shared_at": str(timestamp - 7), # 7 seconds ago "imported_taken_at": str(timestamp - 5 * 24 * 3600), # 5 days ago "date_time_original": time.strftime("%Y%m%dT%H%M%S.000Z", time.localtime()), "media_folder": "Camera", "configure_mode": "1", "source_type": "4", "video_result": "", "creation_surface": "camera", "caption": caption, "capture_type": "normal", "rich_text_format_types": '["strong"]', # default, typewriter "upload_id": upload_id, # Facebook Sharing Part: # "xpost_surface": "auto_xpost", # "share_to_fb_destination_type": "USER", # "share_to_fb_destination_id":"832928543", # "share_to_facebook":"1", # "fb_access_token":"EAABwzLixnjYBACVgqBfLyDuPWs6RN2sTZC........cnNkjHCH2", # "attempt_id": str(uuid4()), "device": self.device, "length": duration, "implicit_location": { "media_location": { "lat": 0.0, "lng": 0.0 } }, "clips": [{ "length": duration, "source_type": "4" }], "extra": { "source_width": width, "source_height": height }, "audio_muted": False, "poster_frame_index": 0, } if links: links = [link.dict() for link in links] data["story_cta"] = dumps([{"links": links}]) if mentions: reel_mentions = [] text_metadata = [] for mention in mentions: reel_mentions.append({ "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", }) text_metadata.append({ "font_size": 40.0, "scale": 1.2798771, "width": 1017.50226, "height": 216.29922, "x": mention.x, "y": mention.y, "rotation": 0.0, }) data["text_metadata"] = dumps(text_metadata) data["tap_models"] = data["reel_mentions"] = dumps(reel_mentions) return self.private_request("media/configure_to_story/?video=1", self.with_default_data(data))
def photo_configure_to_story( self, upload_id: str, width: int, height: int, caption: str, mentions: List[StoryMention] = [], locations: List[StoryLocation] = [], links: List[StoryLink] = [], hashtags: List[StoryHashtag] = [], stickers: List[StorySticker] = [], extra_data: Dict[str, str] = {}, ) -> Dict: """ Post configure photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. locations: List[StoryLocation], optional List of locations to be tagged on this upload, default is empty list. links: List[StoryLink] URLs for Swipe Up hashtags: List[StoryHashtag], optional List of hashtags to be tagged on this upload, default is empty list. stickers: List[StorySticker], optional List of stickers to be tagged on this upload, default is empty list. extra_data: List[str, str], optional Dict of extra data, if you need to add your params, like {"share_to_facebook": 1}. Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) story_sticker_ids = [] data = { "text_metadata": '[{"font_size":40.0,"scale":1.0,"width":611.0,"height":169.0,"x":0.51414347,"y":0.8487708,"rotation":0.0}]', "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES), "has_original_sound": "1", "camera_session_id": self.client_session_id, "scene_capture_type": "", "timezone_offset": "10800", "client_shared_at": str(timestamp - 5), # 5 seconds ago "story_sticker_ids": "", "media_folder": "Camera", "configure_mode": "1", "source_type": "4", "creation_surface": "camera", "imported_taken_at": (timestamp - 3 * 24 * 3600), # 3 days ago "caption": caption, "capture_type": "normal", "rich_text_format_types": '["default"]', "upload_id": upload_id, "client_timestamp": str(timestamp), "device": self.device, "edits": { "crop_original_size": [width * 1.0, height * 1.0], "crop_center": [0.0, 0.0], "crop_zoom": 1.0, }, "extra": { "source_width": width, "source_height": height }, } data.update(extra_data) if links: links = [link.dict() for link in links] data["story_cta"] = dumps([{"links": links}]) tap_models = [] static_models = [] if mentions: reel_mentions = [{ "x": 0.5002546, "y": 0.8583542, "z": 0, "width": 0.4712963, "height": 0.0703125, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", } for mention in mentions] data["reel_mentions"] = json.dumps(reel_mentions) tap_models.extend(reel_mentions) if hashtags: story_sticker_ids.append("hashtag_sticker") for mention in hashtags: item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "hashtag", "tag_name": mention.hashtag.name, "is_sticker": True, "tap_state": 0, "tap_state_str_id": "hashtag_sticker_gradient" } tap_models.append(item) if locations: story_sticker_ids.append("location_sticker") for mention in locations: mention.location = self.location_complete(mention.location) item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "location", "location_id": str(mention.location.pk), "is_sticker": True, "tap_state": 0, "tap_state_str_id": "location_sticker_vibrant" } tap_models.append(item) if stickers: for sticker in stickers: str_id = sticker.id # "gif_Igjf05J559JWuef4N5" static_models.append({ "x": sticker.x, "y": sticker.y, "z": sticker.z, "width": sticker.width, "height": sticker.height, "rotation": sticker.rotation, "str_id": str_id, "sticker_type": sticker.type, }) story_sticker_ids.append(str_id) if sticker.type == "gif": data["has_animated_sticker"] = "1" data["tap_models"] = dumps(tap_models) data["static_models"] = dumps(static_models) data["story_sticker_ids"] = dumps(story_sticker_ids) return self.private_request("media/configure_to_story/", self.with_default_data(data))
def photo_configure_to_story( self, upload_id: str, width: int, height: int, caption: str, mentions: List[StoryMention] = [], locations: List[StoryLocation] = [], links: List[StoryLink] = [], hashtags: List[StoryHashtag] = [], stickers: List[StorySticker] = [], medias: List[StoryMedia] = [], extra_data: Dict[str, str] = {}, ) -> Dict: """ Post configure photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. locations: List[StoryLocation], optional List of locations to be tagged on this upload, default is empty list. links: List[StoryLink] URLs for Swipe Up hashtags: List[StoryHashtag], optional List of hashtags to be tagged on this upload, default is empty list. stickers: List[StorySticker], optional List of stickers to be tagged on this upload, default is empty list. medias: List[StoryMedia], optional List of medias to be tagged on this upload, default is empty list. extra_data: Dict[str, str], optional Dict of extra data, if you need to add your params, like {"share_to_facebook": 1}. Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) mentions = mentions.copy() locations = locations.copy() links = links.copy() hashtags = hashtags.copy() stickers = stickers.copy() medias = medias.copy() story_sticker_ids = [] data = { "text_metadata": '[{"font_size":40.0,"scale":1.0,"width":611.0,"height":169.0,"x":0.51414347,"y":0.8487708,"rotation":0.0}]', # REMOVEIT "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES), "has_original_sound": "1", "camera_session_id": self.client_session_id, "scene_capture_type": "", "timezone_offset": str(self.timezone_offset), "client_shared_at": str(timestamp - 5), # 5 seconds ago "story_sticker_ids": "", "media_folder": "Camera", "configure_mode": "1", "source_type": "4", "creation_surface": "camera", "imported_taken_at": (timestamp - 3 * 24 * 3600), # 3 days ago "capture_type": "normal", "rich_text_format_types": '["default"]', # REMOVEIT "upload_id": upload_id, "client_timestamp": str(timestamp), "device": self.device, "_uid": self.user_id, "_uuid": self.uuid, "device_id": self.android_device_id, "composition_id": self.generate_uuid(), "app_attribution_android_namespace": "", "media_transformation_info": dumps({ "width": str(width), "height": str(height), "x_transform": "0", "y_transform": "0", "zoom": "1.0", "rotation": "0.0", "background_coverage": "0.0" }), "original_media_type": "photo", "camera_entry_point": str(random.randint(25, 164)), # e.g. 25 "edits": { "crop_original_size": [width * 1.0, height * 1.0], # "crop_center": [0.0, 0.0], # "crop_zoom": 1.0, 'filter_type': 0, 'filter_strength': 1.0, }, "extra": {"source_width": width, "source_height": height}, } if caption: data["caption"] = caption data.update(extra_data) tap_models = [] static_models = [] if mentions: for mention in mentions: reel_mentions = [ { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", } ] data["reel_mentions"] = json.dumps(reel_mentions) tap_models.extend(reel_mentions) if hashtags: story_sticker_ids.append("hashtag_sticker") for mention in hashtags: item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "hashtag", "tag_name": mention.hashtag.name, "is_sticker": True, "tap_state": 0, "tap_state_str_id": "hashtag_sticker_gradient" } tap_models.append(item) if locations: story_sticker_ids.append("location_sticker") for mention in locations: mention.location = self.location_complete(mention.location) item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "location", "location_id": str(mention.location.pk), "is_sticker": True, "tap_state": 0, "tap_state_str_id": "location_sticker_vibrant" } tap_models.append(item) if links: # instagram allow one link now link = links[0] self.private_request("media/validate_reel_url/", { "url": str(link.webUri), "_uid": str(self.user_id), "_uuid": str(self.uuid), }) stickers.append( StorySticker( type="story_link", x=link.x, y=link.y, z=link.z, width=link.width, height=link.height, rotation=link.rotation, extra=dict( link_type="web", url=str(link.webUri), tap_state_str_id="link_sticker_default" ) ) ) story_sticker_ids.append("link_sticker_default") if stickers: for sticker in stickers: sticker_extra = sticker.extra or {} if sticker.id: sticker_extra["str_id"] = sticker.id story_sticker_ids.append(sticker.id) tap_models.append({ "x": sticker.x, "y": sticker.y, "z": sticker.z, "width": sticker.width, "height": sticker.height, "rotation": sticker.rotation, "type": sticker.type, "is_sticker": True, "selected_index": 0, "tap_state": 0, **sticker_extra }) if sticker.type == "gif": data["has_animated_sticker"] = "1" if medias: for feed_media in medias: assert feed_media.media_pk, 'Required StoryMedia.media_pk' # if not feed_media.user_id: # user = self.media_user(feed_media.media_pk) # feed_media.user_id = user.pk item = { 'x': feed_media.x, 'y': feed_media.y, 'z': feed_media.z, 'width': feed_media.width, 'height': feed_media.height, 'rotation': feed_media.rotation, 'type': 'feed_media', 'media_id': str(feed_media.media_pk), 'media_owner_id': str(feed_media.user_id or ""), 'product_type': 'feed', 'is_sticker': True, 'tap_state': 0, 'tap_state_str_id': 'feed_post_sticker_square' } tap_models.append(item) data["reshared_media_id"] = str(feed_media.media_pk) if tap_models: data["tap_models"] = dumps(tap_models) if static_models: data["static_models"] = dumps(static_models) if story_sticker_ids: data["story_sticker_ids"] = story_sticker_ids[0] return self.private_request("media/configure_to_story/", self.with_default_data(data))
def video_configure_to_story( self, upload_id: str, width: int, height: int, duration: int, thumbnail: Path, caption: str, mentions: List[StoryMention] = [], locations: List[StoryLocation] = [], links: List[StoryLink] = [], hashtags: List[StoryHashtag] = [], stickers: List[StorySticker] = [], medias: List[StoryMedia] = [], thread_ids: List[int] = [], extra_data: Dict[str, str] = {}, ) -> Dict: """ Story Configure for Photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels duration: int Duration of the video in seconds thumbnail: str Path to thumbnail for video. When None, then thumbnail is generate automatically caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. locations: List[StoryLocation], optional List of locations to be tagged on this upload, default is empty list. links: List[StoryLink] URLs for Swipe Up hashtags: List[StoryHashtag], optional List of hashtags to be tagged on this upload, default is empty list. stickers: List[StorySticker], optional List of stickers to be tagged on this upload, default is empty list. medias: List[StoryMedia], optional List of medias to be tagged on this upload, default is empty list. thread_ids: List[int], optional List of Direct Message Thread ID (to send a story to a thread) extra_data: Dict[str, str], optional Dict of extra data, if you need to add your params, like {"share_to_facebook": 1}. Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) mentions = mentions.copy() locations = locations.copy() links = links.copy() hashtags = hashtags.copy() stickers = stickers.copy() medias = medias.copy() thread_ids = thread_ids.copy() story_sticker_ids = [] data = { # USE extra_data TO EXTEND THE SETTINGS OF THE LOADED STORY, USE FOR EXAMPLE THE PROPERTIES SPECIFIED IN THE COMMENT: # --------------------------------- # When send to DIRECT: # "allow_multi_configures": "1", # "client_context":"6823316152962778207", <-- token = random.randint(6800011111111111111, 6800099999999999999) from direct.py # "is_shh_mode":"0", # "mutation_token":"6824688191453546273", # "nav_chain":"1qT:feed_timeline:1,1qT:feed_timeline:7,ReelViewerFragment:reel_feed_timeline:21,5HT:attribution_quick_camera_fragment:22,4ji:reel_composer_preview:23,8wg:direct_story_audience_picker:24,4ij:reel_composer_camera:25,ReelViewerFragment:reel_feed_timeline:26", # "recipient_users":"[]", # "send_attribution":"direct_story_audience_picker", # "thread_ids":"[\"340282366841710300949128149448121770626\"]", <-- send story to direct # "view_mode": "replayable", # --------------------------------- # Optional (markup for caption field) when tagging: # "story_captions":"[{\"text\":\"@user1+\\n\\n@user2+\",\"position_data\":{\"x\":0.5,\"y\":0.5,\"height\":272.0,\"width\":670.0,\"rotation\":0.0},\"scale\":1.0,\"font_size\":24.0,\"format_type\":\"classic_v2\",\"effects\":[\"disabled\"],\"colors\":[\"#ffffff\"],\"alignment\":\"center\",\"animation\":\"\"}]", # --------------------------------- # SEGMENT MODE (when file is too big): # "allow_multi_configures": "1", # "segmented_video_group_id": str(uuid4()), # "multi_upload_session_id": str(uuid4()), # "segmented_video_count": "4", # "4" # SEGMENT MODE # "segmented_video_index": "0", # 0,1,2,3 # SEGMENT MODE # "is_multi_upload": "1", # SEGMENT MODE # "is_segmented_video": "1", # SEGMENT MODE # --------------------------------- # COMMON properties: "_uid": str(self.user_id), "supported_capabilities_new": dumps(config.SUPPORTED_CAPABILITIES), "has_original_sound": "1", "filter_type": "0", "camera_session_id": self.client_session_id, "camera_entry_point": str(random.randint(35, 164)), "composition_id": self.generate_uuid(), # "camera_make": self.device_settings.get("manufacturer", "Xiaomi"), # "camera_model": self.device_settings.get("model", "MI+5s"), "timezone_offset": str(self.timezone_offset), "client_timestamp": str(timestamp), "client_shared_at": str(timestamp - 7), # 7 seconds ago # "imported_taken_at": str(timestamp - 5 * 24 * 3600), # 5 days ago "date_time_original": date_time_original(time.localtime()), # "date_time_digitalized": date_time_original(time.localtime()), # "story_sticker_ids": "", # "media_folder": "Camera", "configure_mode": "1", # "configure_mode": "2", <- when direct "source_type": "3", # "3" "video_result": "", "creation_surface": "camera", # "software": config.SOFTWARE.format(**self.device_settings), # "caption": caption, "capture_type": "normal", # "rich_text_format_types": '["classic_v2"]', # default, typewriter "upload_id": upload_id, # "scene_capture_type": "standard", # "scene_type": "", "original_media_type": "video", "camera_position": "back", # Facebook Sharing Part: # "xpost_surface": "auto_xpost", # "share_to_fb_destination_type": "USER", # "share_to_fb_destination_id":"832928543", # "share_to_facebook":"1", # "fb_access_token":"EAABwzLixnjYBACVgqBfLyDuPWs6RN2sTZC........cnNkjHCH2", # "attempt_id": str(uuid4()), "device": self.device, "length": duration, "clips": [{"length": duration, "source_type": "3", "camera_position": "back"}], # "edits": { # "filter_type": 0, # "filter_strength": 1.0, # "crop_original_size": [width, height], # # "crop_center": [0, 0], # # "crop_zoom": 1 # }, "media_transformation_info": dumps({ "width": str(width), "height": str(height), "x_transform": "0", "y_transform": "0", "zoom": "1.0", "rotation": "0.0", "background_coverage": "0.0" }), "extra": {"source_width": width, "source_height": height}, "audio_muted": False, "poster_frame_index": 0, # "app_attribution_android_namespace": "", } data.update(extra_data) tap_models = [] static_models = [] if mentions: reel_mentions = [] text_metadata = [] for mention in mentions: reel_mentions.append( { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", "tap_state": 0, "tap_state_str_id": "mention_text", } ) text_metadata.append( { "font_size": 24.0, "scale": 1.0, "width": 366.0, "height": 102.0, "x": mention.x, "y": mention.y, "rotation": 0.0, } ) data["text_metadata"] = dumps(text_metadata) # data["reel_mentions"] = dumps(reel_mentions) tap_models.extend(reel_mentions) if hashtags: story_sticker_ids.append("hashtag_sticker") for mention in hashtags: item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "hashtag", "tag_name": mention.hashtag.name, "is_sticker": True, "tap_state": 0, "tap_state_str_id": "hashtag_sticker_gradient" } tap_models.append(item) if locations: story_sticker_ids.append("location_sticker") for mention in locations: mention.location = self.location_complete(mention.location) item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "location", "location_id": str(mention.location.pk), "is_sticker": True, "tap_state": 0, "tap_state_str_id": "location_sticker_vibrant" } tap_models.append(item) if links: # instagram allow one link now link = links[0] self.private_request("media/validate_reel_url/", { "url": str(link.webUri), "_uid": str(self.user_id), "_uuid": str(self.uuid), }) stickers.append( StorySticker( type="story_link", x=link.x, y=link.y, z=link.z, width=link.width, height=link.height, rotation=link.rotation, extra=dict( link_type="web", url=str(link.webUri), tap_state_str_id="link_sticker_default" ) ) ) story_sticker_ids.append("link_sticker_default") if stickers: for sticker in stickers: sticker_extra = sticker.extra or {} if sticker.id: sticker_extra["str_id"] = sticker.id story_sticker_ids.append(sticker.id) tap_models.append({ "x": round(sticker.x, 7), "y": round(sticker.y, 7), "z": sticker.z, "width": round(sticker.width, 7), "height": round(sticker.height, 7), "rotation": sticker.rotation, "type": sticker.type, "is_sticker": True, "selected_index": 0, "tap_state": 0, **sticker_extra }) if sticker.type == "gif": data["has_animated_sticker"] = "1" if medias: for feed_media in medias: assert feed_media.media_pk, "Required StoryMedia.media_pk" # if not feed_media.user_id: # user = self.media_user(feed_media.media_pk) # feed_media.user_id = user.pk item = { "x": feed_media.x, "y": feed_media.y, "z": feed_media.z, "width": feed_media.width, "height": feed_media.height, "rotation": feed_media.rotation, "type": "feed_media", "media_id": str(feed_media.media_pk), "media_owner_id": str(feed_media.user_id or ""), "product_type": "feed", "is_sticker": True, "tap_state": 0, "tap_state_str_id": "feed_post_sticker_square" } tap_models.append(item) data["reshared_media_id"] = str(feed_media.media_pk) if thread_ids: # Send to direct thread token = self.generate_mutation_token() data.update({ "configure_mode": "2", "allow_multi_configures": "1", "client_context": token, "is_shh_mode": "0", "mutation_token": token, "nav_chain": "1qT:feed_timeline:1,1qT:feed_timeline:7,ReelViewerFragment:reel_feed_timeline:21,5HT:attribution_quick_camera_fragment:22,4ji:reel_composer_preview:23,8wg:direct_story_audience_picker:24,4ij:reel_composer_camera:25,ReelViewerFragment:reel_feed_timeline:26", "recipient_users": "[]", "send_attribution": "direct_story_audience_picker", "thread_ids": dumps([str(tid) for tid in thread_ids]), "view_mode": "replayable" }) if tap_models: data["tap_models"] = dumps(tap_models) if static_models: data["static_models"] = dumps(static_models) if story_sticker_ids: data["story_sticker_ids"] = story_sticker_ids[0] return self.private_request("media/configure_to_story/?video=1", self.with_default_data(data))
def video_rupload( self, path: Path, thumbnail: Path = None, to_album: bool = False, to_story: bool = False, ) -> tuple: """ Upload video to Instagram Parameters ---------- path: Path Path to the media thumbnail: str Path to thumbnail for video. When None, then thumbnail is generate automatically to_album: bool, optional to_story: bool, optional Returns ------- tuple (Upload ID for the media, width, height) """ assert isinstance( path, Path), f"Path must been Path, now {path} ({type(path)})" upload_id = str(int(time.time() * 1000)) width, height, duration, thumbnail = analyze_video(path, thumbnail) waterfall_id = str(uuid4()) # upload_name example: '1576102477530_0_7823256191' upload_name = "{upload_id}_0_{rand}".format(upload_id=upload_id, rand=random.randint( 1000000000, 9999999999)) rupload_params = { "retry_context": '{"num_step_auto_retry":0,"num_reupload":0,"num_step_manual_retry":0}', "media_type": "2", "xsharing_user_ids": dumps([self.user_id]), "upload_id": upload_id, "upload_media_duration_ms": str(int(duration * 1000)), "upload_media_width": str(width), "upload_media_height": str(height), # "1138" for Mi5s } if to_album: rupload_params["is_sidecar"] = "1" if to_story: rupload_params = { "extract_cover_frame": "1", "content_tags": "has-overlay", "for_album": "1", **rupload_params, } headers = { "Accept-Encoding": "gzip, deflate", "X-Instagram-Rupload-Params": dumps(rupload_params), "X_FB_VIDEO_WATERFALL_ID": waterfall_id, # "X_FB_VIDEO_WATERFALL_ID": "88732215909430_55CF262450C9_Mixed_0", # ALBUM # "X_FB_VIDEO_WATERFALL_ID": "1594919079102", # VIDEO } if to_album: headers = { "Segment-Start-Offset": "0", "Segment-Type": "3", **headers } response = self.private.get( "https://{domain}/rupload_igvideo/{name}".format( domain=config.API_DOMAIN, name=upload_name), headers=headers, ) self.request_log(response) if response.status_code != 200: raise VideoNotUpload(response.text, response=response, **self.last_json) video_data = open(path, "rb").read() video_len = str(len(video_data)) headers = { "Offset": "0", "X-Entity-Name": upload_name, "X-Entity-Length": video_len, "Content-Type": "application/octet-stream", "Content-Length": video_len, "X-Entity-Type": "video/mp4", **headers, } response = self.private.post( "https://{domain}/rupload_igvideo/{name}".format( domain=config.API_DOMAIN, name=upload_name), data=video_data, headers=headers, ) self.request_log(response) if response.status_code != 200: raise VideoNotUpload(response.text, response=response, **self.last_json) return upload_id, width, height, duration, Path(thumbnail)
def direct_send_file(self, path: Path, user_ids: List[int] = [], thread_ids: List[int] = [], content_type: str = 'photo') -> DirectMessage: """ Send a direct file to list of users or threads Parameters ---------- path: Path Path to file that will be posted on the thread user_ids: List[int] List of unique identifier of Users id thread_ids: List[int] List of unique identifier of Direct Message thread id Returns ------- DirectMessage An object of DirectMessage """ assert self.user_id, "Login required" assert (user_ids or thread_ids) and not ( user_ids and thread_ids), "Specify user_ids or thread_ids, but not both" method = f"configure_{content_type}" token = self.generate_mutation_token() nav_chains = [ "6xQ:direct_media_picker_photos_fragment:1,5rG:direct_thread:2,5ME:direct_quick_camera_fragment:3,5ME:direct_quick_camera_fragment:4,4ju:reel_composer_preview:5,5rG:direct_thread:6,5rG:direct_thread:7,6xQ:direct_media_picker_photos_fragment:8,5rG:direct_thread:9", "1qT:feed_timeline:1,7Az:direct_inbox:2,7Az:direct_inbox:3,5rG:direct_thread:4,6xQ:direct_media_picker_photos_fragment:5,5rG:direct_thread:6,5rG:direct_thread:7,6xQ:direct_media_picker_photos_fragment:8,5rG:direct_thread:9", ] kwargs = {} data = { "action": "send_item", "is_shh_mode": "0", "send_attribution": "direct_thread", "client_context": token, "mutation_token": token, "nav_chain": random.choices(nav_chains), "offline_threading_id": token, } if content_type == "video": data["video_result"] = "" kwargs["to_direct"] = True if content_type == "photo": data["send_attribution"] = "inbox" data["allow_full_aspect_ratio"] = "true" if user_ids: data["recipient_users"] = dumps([[int(uid) for uid in user_ids]]) if thread_ids: data["thread_ids"] = dumps([int(tid) for tid in thread_ids]) path = Path(path) upload_id = str(int(time.time() * 1000)) upload_id, width, height = getattr(self, f'{content_type}_rupload')( path, upload_id, **kwargs)[:3] data['upload_id'] = upload_id # data['content_type'] = content_type result = self.private_request( f"direct_v2/threads/broadcast/{method}/", data=self.with_default_data(data), with_signature=False, ) return extract_direct_message(result["payload"])
def album_upload( self, paths: List[Path], caption: str, usertags: List[Usertag] = [], location: Location = None, configure_timeout: int = 3, configure_handler=None, configure_exception=None, to_story=False, ) -> Media: """ Upload album to feed Parameters ---------- paths: List[Path] List of paths for media to upload caption: str Media caption usertags: List[Usertag], optional List of users to be tagged on this upload, default is empty list. location: Location, optional Location tag for this upload, default is none configure_timeout: int Timeout between attempt to configure media (set caption, etc), default is 3 configure_handler Configure handler method, default is None configure_exception Configure exception class, default is None to_story: bool Currently not used, default is False Returns ------- Media An object of Media class """ children = [] for path in paths: path = Path(path) if path.suffix == ".jpg": upload_id, width, height = self.photo_rupload(path, to_album=True) children.append( { "upload_id": upload_id, "edits": dumps( { "crop_original_size": [width, height], "crop_center": [0.0, -0.0], "crop_zoom": 1.0, } ), "extra": dumps( {"source_width": width, "source_height": height} ), "scene_capture_type": "", "scene_type": None, } ) elif path.suffix == ".mp4": upload_id, width, height, duration, thumbnail = self.video_rupload( path, to_album=True ) children.append( { "upload_id": upload_id, "clips": dumps([{"length": duration, "source_type": "4"}]), "extra": dumps( {"source_width": width, "source_height": height} ), "length": duration, "poster_frame_index": "0", "filter_type": "0", "video_result": "", "date_time_original": time.strftime( "%Y%m%dT%H%M%S.000Z", time.localtime() ), "audio_muted": "false", } ) self.photo_rupload(thumbnail, upload_id) else: raise AlbumUnknownFormat() for attempt in range(20): self.logger.debug(f"Attempt #{attempt} to configure Album: {paths}") time.sleep(configure_timeout) try: configured = (configure_handler or self.album_configure)( children, caption, usertags, location ) except Exception as e: if "Transcode not finished yet" in str(e): """ Response 202 status: {"message": "Transcode not finished yet.", "status": "fail"} """ time.sleep(10) continue raise e else: if configured: media = configured.get("media") self.expose() return extract_media_v1(media) raise (configure_exception or AlbumConfigureError)( response=self.last_response, **self.last_json )
def video_configure_to_story( self, upload_id: str, width: int, height: int, duration: int, thumbnail: Path, caption: str, mentions: List[StoryMention] = [], locations: List[StoryLocation] = [], links: List[StoryLink] = [], hashtags: List[StoryHashtag] = [], stickers: List[StorySticker] = [], extra_data: Dict[str, str] = {}, ) -> Dict: """ Story Configure for Photo Parameters ---------- upload_id: str Unique upload_id width: int Width of the video in pixels height: int Height of the video in pixels duration: int Duration of the video in seconds thumbnail: str Path to thumbnail for video. When None, then thumbnail is generate automatically caption: str Media caption mentions: List[StoryMention], optional List of mentions to be tagged on this upload, default is empty list. locations: List[StoryLocation], optional List of locations to be tagged on this upload, default is empty list. links: List[StoryLink] URLs for Swipe Up hashtags: List[StoryHashtag], optional List of hashtags to be tagged on this upload, default is empty list. stickers: List[StorySticker], optional List of stickers to be tagged on this upload, default is empty list. extra_data: List[str, str], optional Dict of extra data, if you need to add your params, like {"share_to_facebook": 1}. Returns ------- Dict A dictionary of response from the call """ timestamp = int(time.time()) story_sticker_ids = [] data = { "supported_capabilities_new": dumps(config.SUPPORTED_CAPABILITIES), # "has_original_sound": "1", # Segment mode (when file is too big): # "allow_multi_configures": "1", # "segmented_video_group_id": str(uuid4()), # "multi_upload_session_id": str(uuid4()), # "segmented_video_count": "4", # "4" # SEGMENT MODE # "segmented_video_index": "0", # 0,1,2,3 # SEGMENT MODE # "is_multi_upload": "1", # SEGMENT MODE # "is_segmented_video": "1", # SEGMENT MODE "filter_type": "0", "camera_session_id": self.client_session_id, "timezone_offset": "10800", "client_timestamp": str(timestamp), "client_shared_at": str(timestamp - 7), # 7 seconds ago "imported_taken_at": str(timestamp - 5 * 24 * 3600), # 5 days ago "date_time_original": time.strftime("%Y%m%dT%H%M%S.000Z", time.localtime()), "story_sticker_ids": "", "media_folder": "Camera", "configure_mode": "1", "source_type": "4", "video_result": "", "creation_surface": "camera", "caption": caption, "capture_type": "normal", "rich_text_format_types": '["strong"]', # default, typewriter "upload_id": upload_id, "scene_capture_type": "", # "original_media_type": "photo" / "video", # Facebook Sharing Part: # "xpost_surface": "auto_xpost", # "share_to_fb_destination_type": "USER", # "share_to_fb_destination_id":"832928543", # "share_to_facebook":"1", # "fb_access_token":"EAABwzLixnjYBACVgqBfLyDuPWs6RN2sTZC........cnNkjHCH2", # "attempt_id": str(uuid4()), "device": self.device, "length": duration, "clips": [{ "length": duration, "source_type": "4" }], # "edits": { # "crop_original_size": [ # 960, # 960 # ], # "crop_center": [ # 0, # 0 # ], # "crop_zoom": 1 # }, "extra": { "source_width": width, "source_height": height }, "audio_muted": False, "poster_frame_index": 0, } data.update(extra_data) if links: links = [link.dict() for link in links] data["story_cta"] = dumps([{"links": links}]) tap_models = [] static_models = [] if mentions: reel_mentions = [] text_metadata = [] for mention in mentions: reel_mentions.append({ "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "mention", "user_id": str(mention.user.pk), "is_sticker": False, "display_type": "mention_username", }) text_metadata.append({ "font_size": 40.0, "scale": 1.2798771, "width": 1017.50226, "height": 216.29922, "x": mention.x, "y": mention.y, "rotation": 0.0, }) data["text_metadata"] = dumps(text_metadata) data["reel_mentions"] = dumps(reel_mentions) tap_models.extend(reel_mentions) if hashtags: story_sticker_ids.append("hashtag_sticker") for mention in hashtags: item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "hashtag", "tag_name": mention.hashtag.name, "is_sticker": True, "tap_state": 0, "tap_state_str_id": "hashtag_sticker_gradient" } tap_models.append(item) if locations: story_sticker_ids.append("location_sticker") for mention in locations: mention.location = self.location_complete(mention.location) item = { "x": mention.x, "y": mention.y, "z": 0, "width": mention.width, "height": mention.height, "rotation": 0.0, "type": "location", "location_id": str(mention.location.pk), "is_sticker": True, "tap_state": 0, "tap_state_str_id": "location_sticker_vibrant" } tap_models.append(item) if stickers: for sticker in stickers: str_id = sticker.id # "gif_Igjf05J559JWuef4N5" static_models.append({ "x": sticker.x, "y": sticker.y, "z": sticker.z, "width": sticker.width, "height": sticker.height, "rotation": sticker.rotation, "str_id": str_id, "sticker_type": sticker.type, }) story_sticker_ids.append(str_id) if sticker.type == "gif": data["has_animated_sticker"] = "1" data["tap_models"] = dumps(tap_models) data["static_models"] = dumps(static_models) data["story_sticker_ids"] = dumps(story_sticker_ids) return self.private_request("media/configure_to_story/?video=1", self.with_default_data(data))