text: import json
  import shutil
  from copy import deepcopy
  from pathlib import Path
  from typing import List
  from urllib.parse import urlparse
  
  import requests
  
  from instagrapi import config
  from instagrapi.exceptions import ClientNotFoundError, StoryNotFound, UserNotFound
  from instagrapi.extractors import (
      extract_story_gql,
      extract_story_v1,
      extract_user_short,
  )
  from instagrapi.types import Story, UserShort
  
  
  class StoryMixin:
      _stories_cache = {}  # pk -> object
  
      def story_pk_from_url(self, url: str) -> int:
          """
          Get Story (media) PK from URL
  
          Parameters
          ----------
          url: str
              URL of the story
  
          Returns
          -------
          int
              Media PK
  
          Examples
          --------
          https://www.instagram.com/stories/dhbastards/2581281926631793076/ -> 2581281926631793076
          """
          path = urlparse(url).path
          parts = [p for p in path.split("/") if p and p.isdigit()]
          return int(parts[0])
  
      # def story_info_gql(self, story_pk: int):
      #     # GQL havent video_url :-(
      #     return self.media_info_gql(self, int(story_pk))
  
      def story_info_v1(self, story_pk: int) -> Story:
          """
          Get Story by pk or id
  
          Parameters
          ----------
          story_pk: int
              Unique identifier of the story
  
          Returns
          -------
          Story
              An object of Story type
          """
          story_id = self.media_id(story_pk)
          story_pk, user_id = story_id.split("_")
          stories = self.user_stories_v1(user_id)
          story_pk = int(story_pk)
          for story in stories:
              self._stories_cache[story.pk] = story
          if story_pk in self._stories_cache:
              return deepcopy(self._stories_cache[story_pk])
          raise StoryNotFound(story_pk=story_pk, user_id=user_id)
  
      def story_info(self, story_pk: int, use_cache: bool = True) -> Story:
          """
          Get Story by pk or id
  
          Parameters
          ----------
          story_pk: int
              Unique identifier of the story
          use_cache: bool, optional
              Whether or not to use information from cache, default value is True
  
          Returns
          -------
          Story
              An object of Story type
          """
          if not use_cache or story_pk not in self._stories_cache:
              story = self.story_info_v1(story_pk)
              self._stories_cache[story_pk] = story
          return deepcopy(self._stories_cache[story_pk])
  
      def story_delete(self, story_pk: int) -> bool:
          """
          Delete story
  
          Parameters
          ----------
          story_pk: int
              Unique identifier of the story
  
          Returns
          -------
          bool
              A boolean value
          """
          assert self.user_id, "Login required"
          media_id = self.media_id(story_pk)
          self._stories_cache.pop(self.media_pk(media_id), None)
          return self.media_delete(media_id)
  
      def users_stories_gql(self, user_ids: List[int]) -> List[UserShort]:
          """
          Get a user's stories (Public API)
  
          Parameters
          ----------
          user_ids: List[int]
  
          Returns
          -------
          List[UserShort]
              A list of objects of UserShort for each user_id
          """
          self.inject_sessionid_to_public()
  
          def _userid_chunks():
              assert user_ids is not None
              user_ids_per_query = 50
              for i in range(0, len(user_ids), user_ids_per_query):
                  yield user_ids[i:i + user_ids_per_query]
  
          stories_un = {}
          for userid_chunk in _userid_chunks():
              res = self.public_graphql_request(
                  query_hash="303a4ae99711322310f25250d988f3b7",
                  variables={"reel_ids": userid_chunk, "precomposed_overlay": False}
              )
              stories_un.update(res)
          users = []
          for media in stories_un['reels_media']:
              user = extract_user_short(media['owner'])
              user.stories = [
                  extract_story_gql(m)
                  for m in media['items']
              ]
              users.append(user)
          return users
  
      def user_stories_gql(self, user_id: int, amount: int = None) -> List[UserShort]:
          """
          Get a user's stories (Public API)
  
          Parameters
          ----------
          user_id: int
          amount: int, optional
              Maximum number of story to return, default is all
  
          Returns
          -------
          List[UserShort]
              A list of objects of UserShort for each user_id
          """
          user = self.users_stories_gql([user_id])[0]
          stories = deepcopy(user.stories)
          if amount:
              stories = stories[:amount]
          return stories
  
      def user_stories_v1(self, user_id: int, amount: int = None) -> List[Story]:
          """
          Get a user's stories (Private API)
  
          Parameters
          ----------
          user_id: int
          amount: int, optional
              Maximum number of story to return, default is all
  
          Returns
          -------
          List[Story]
              A list of objects of Story
          """
          params = {
              "supported_capabilities_new": json.dumps(config.SUPPORTED_CAPABILITIES)
          }
          user_id = int(user_id)
          reel = self.private_request(f"feed/user/{user_id}/story/", params=params).get("reel") or {}
          stories = []
          for item in reel.get("items", []):
              stories.append(extract_story_v1(item))
          if amount:
              stories = stories[:int(amount)]
          return stories
  
      def user_stories(self, user_id: int, amount: int = None) -> List[Story]:
          """
          Get a user's stories
  
          Parameters
          ----------
          user_id: int
          amount: int, optional
              Maximum number of story to return, default is all
  
          Returns
          -------
          List[Story]
              A list of objects of STory
          """
          try:
              return self.user_stories_gql(user_id, amount)
          except ClientNotFoundError as e:
              raise UserNotFound(e, user_id=user_id, **self.last_json)
          except IndexError:
              return []
          except Exception:
              return self.user_stories_v1(user_id, amount)
  
      def story_seen(self, story_pks: List[int], skipped_story_pks: List[int] = []):
          """
          Mark a story as seen
  
          Parameters
          ----------
          story_pk: int
  
          Returns
          -------
          bool
              A boolean value
          """
          return self.media_seen(
              [self.media_id(mid) for mid in story_pks],
              [self.media_id(mid) for mid in skipped_story_pks]
          )
  
      def story_download(self, story_pk: int, filename: str = "", folder: Path = "") -> Path:
          """
          Download story media by media_type
  
          Parameters
          ----------
          story_pk: int
  
          Returns
          -------
          Path
              Path for the file downloaded
          """
          story_pk = int(story_pk)
          story = self.story_info(story_pk)
          url = story.thumbnail_url if story.media_type == 1 else story.video_url
          return self.story_download_by_url(url, filename, folder)
  
      def story_download_by_url(self, url: str, filename: str = "", folder: Path = "") -> Path:
          """
          Download story media using URL
  
          Parameters
          ----------
          url: str
              URL for a media
          filename: str, optional
              Filename for the media
          folder: Path, optional
              Directory in which you want to download the album, default is "" and will download the files to working
                  directory
  
          Returns
          -------
          Path
              Path for the file downloaded
          """
          fname = urlparse(url).path.rsplit("/", 1)[1]
          filename = "%s.%s" % (filename, fname.rsplit(".", 1)[1]) if filename else fname
          path = Path(folder) / filename
          response = requests.get(url, stream=True)
          response.raise_for_status()
          with open(path, "wb") as f:
              response.raw.decode_content = True
              shutil.copyfileobj(response.raw, f)
          return path.resolve()
  