Example #1
0
    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)
        )
Example #2
0
    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"])
Example #3
0
    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"])
Example #4
0
    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"])
Example #5
0
    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'])
Example #6
0
    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)
Example #7
0
    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'])
Example #8
0
    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"])
Example #9
0
    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
Example #10
0
 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 ''
Example #11
0
    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
Example #12
0
 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'])
Example #13
0
    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"
Example #14
0
    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"])
Example #15
0
    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))
Example #16
0
    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)
        )
Example #17
0
 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
Example #18
0
    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))
Example #19
0
    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))
Example #20
0
    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))
Example #21
0
    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))
Example #22
0
    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)
Example #23
0
    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"])
Example #24
0
    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
        )
Example #25
0
    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))