instagram-archiver 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of instagram-archiver might be problematic. Click here for more details.

@@ -0,0 +1,172 @@
1
+ """Typing helpers."""
2
+ # ruff: noqa: D101
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal, NotRequired, TypedDict
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Sequence
9
+
10
+ __all__ = ('BrowserName', 'CarouselMedia', 'Comments', 'Edge', 'HasID', 'HighlightsTray',
11
+ 'MediaInfo', 'MediaInfoItem', 'MediaInfoItemImageVersions2Candidate', 'UserInfo',
12
+ 'WebProfileInfo', 'WebProfileInfoData', 'XDTAPIV1FeedUserTimelineGraphQLConnection',
13
+ 'XDTAPIV1FeedUserTimelineGraphQLConnectionContainer', 'XDTMediaDict')
14
+
15
+
16
+ class MediaInfoItemVideoVersion(TypedDict):
17
+ height: int
18
+ url: str
19
+ width: int
20
+
21
+
22
+ class MediaInfoItemImageVersions2Candidate(TypedDict):
23
+ height: int
24
+ """Height of the image."""
25
+ url: str
26
+ """URL of the image."""
27
+ width: int
28
+ """Width of the image."""
29
+
30
+
31
+ class HighlightItem(TypedDict):
32
+ id: str
33
+ """Identifier."""
34
+
35
+
36
+ class HighlightsTray(TypedDict):
37
+ tray: Sequence[HighlightItem]
38
+ """Highlights tray items."""
39
+
40
+
41
+ class PageInfo(TypedDict):
42
+ end_cursor: str
43
+ """End cursor for pagination."""
44
+ has_next_page: bool
45
+ """Whether there are more pages."""
46
+
47
+
48
+ class EdgeOwnerToTimelineMedia(TypedDict):
49
+ edges: Sequence[Edge]
50
+ page_info: PageInfo
51
+ """Pagination information."""
52
+
53
+
54
+ class UserInfo(TypedDict):
55
+ """User information."""
56
+ edge_owner_to_timeline_media: EdgeOwnerToTimelineMedia
57
+ """Timeline media edge."""
58
+ id: str
59
+ """User ID."""
60
+ profile_pic_url_hd: str
61
+ """Profile picture URL."""
62
+
63
+
64
+ class MediaInfoItemImageVersions2(TypedDict):
65
+ candidates: Sequence[MediaInfoItemImageVersions2Candidate]
66
+ """Image versions."""
67
+
68
+
69
+ class CarouselMedia(TypedDict):
70
+ image_versions2: MediaInfoItemImageVersions2
71
+ """Image versions."""
72
+ id: str
73
+ """Identifier."""
74
+
75
+
76
+ class HasID(TypedDict):
77
+ """Dictionary with an ``id`` field."""
78
+ id: str
79
+ """Identifier."""
80
+
81
+
82
+ class MediaInfoItem(TypedDict):
83
+ """Media information item."""
84
+ carousel_media: NotRequired[Sequence[CarouselMedia] | None]
85
+ """Carousel media items."""
86
+ image_versions2: MediaInfoItemImageVersions2
87
+ """Image versions."""
88
+ id: str
89
+ """Identifier."""
90
+ taken_at: int
91
+ """Timestamp when the media was taken"""
92
+ user: HasID
93
+ """User who posted the media."""
94
+ video_dash_manifest: NotRequired[str | None]
95
+ """URL of the video dash manifest."""
96
+ video_duration: float
97
+ """Duration of the video in seconds."""
98
+ video_versions: Sequence[MediaInfoItemVideoVersion]
99
+ """Video versions."""
100
+
101
+
102
+ class Comments(TypedDict):
103
+ """Comments container."""
104
+ can_view_more_preview_comments: bool
105
+ """Whether more preview comments can be viewed."""
106
+ comments: Sequence[HasID]
107
+ """List of comments."""
108
+ next_min_id: str
109
+ """Next minimum ID for pagination."""
110
+
111
+
112
+ class MediaInfo(TypedDict):
113
+ """Media information."""
114
+ items: Sequence[MediaInfoItem]
115
+ """List of media items."""
116
+
117
+
118
+ class Owner(TypedDict):
119
+ id: str
120
+ """Owner ID."""
121
+ username: str
122
+ """Owner username."""
123
+
124
+
125
+ class XDTMediaDict(TypedDict):
126
+ __typename: Literal['XDTMediaDict']
127
+ """Type name."""
128
+ code: str
129
+ """Short code."""
130
+ id: str
131
+ """Media ID."""
132
+ owner: Owner
133
+ """Owner information."""
134
+ pk: str
135
+ """Primary key. Also carousel ID."""
136
+ video_dash_manifest: NotRequired[str | None]
137
+ """Video dash manifest URL, if available."""
138
+
139
+
140
+ class Edge(TypedDict):
141
+ """Edge of a graph."""
142
+ node: XDTMediaDict
143
+ """Node at this edge."""
144
+
145
+
146
+ class XDTAPIV1FeedUserTimelineGraphQLConnection(TypedDict):
147
+ edges: Sequence[Edge]
148
+ """Edges of the graph."""
149
+ page_info: PageInfo
150
+ """Pagination information."""
151
+
152
+
153
+ class XDTAPIV1FeedUserTimelineGraphQLConnectionContainer(TypedDict):
154
+ """Container for :py:class:`XDTAPIV1FeedUserTimelineGraphQLConnection`."""
155
+ xdt_api__v1__feed__user_timeline_graphql_connection: XDTAPIV1FeedUserTimelineGraphQLConnection
156
+ """User timeline data."""
157
+
158
+
159
+ class WebProfileInfoData(TypedDict):
160
+ user: UserInfo
161
+ """User information."""
162
+
163
+
164
+ class WebProfileInfo(TypedDict):
165
+ """Profile information container."""
166
+ data: WebProfileInfoData
167
+ """Profile data."""
168
+
169
+
170
+ BrowserName = Literal['brave', 'chrome', 'chromium', 'edge', 'firefox', 'opera', 'safari',
171
+ 'vivaldi']
172
+ """Possible browser choices to get cookies from."""
@@ -1,60 +1,112 @@
1
- from contextlib import contextmanager
2
- from os import chdir as os_chdir, getcwd
3
- from os.path import isfile
1
+ """Utility functions."""
2
+ from __future__ import annotations
3
+
4
4
  from pathlib import Path
5
- from types import FrameType
6
- from typing import Generic, Iterator, Literal, TypeVar
5
+ from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar, override
7
6
  import json
8
7
  import logging
9
- import sys
8
+ import logging.config
10
9
 
11
- from loguru import logger
12
10
  import click
13
11
 
14
- __all__ = ('UnknownMimetypeError', 'YoutubeDLLogger', 'chdir', 'get_extension',
15
- 'json_dumps_formatted', 'setup_logging', 'write_if_new')
12
+ if TYPE_CHECKING:
13
+ from .typing import Edge
14
+
15
+ __all__ = ('JSONFormattedString', 'UnknownMimetypeError', 'get_extension', 'json_dumps_formatted',
16
+ 'setup_logging', 'write_if_new')
16
17
 
17
18
  T = TypeVar('T')
18
19
 
19
20
 
20
- class JSONFormattedString(Generic[T]): # pylint: disable=too-few-public-methods
21
- def __init__(self, formatted: str, original: T) -> None:
21
+ def setup_logging(*,
22
+ debug: bool = False,
23
+ force_color: bool = False,
24
+ no_color: bool = False) -> None: # pragma: no cover
25
+ """Set up logging configuration."""
26
+ logging.config.dictConfig({
27
+ 'version': 1,
28
+ 'disable_existing_loggers': True,
29
+ 'root': {
30
+ 'handlers': ('console',),
31
+ 'level': 'DEBUG' if debug else 'INFO',
32
+ },
33
+ 'formatters': {
34
+ 'default': {
35
+ '()': 'colorlog.ColoredFormatter',
36
+ 'force_color': force_color,
37
+ 'format':
38
+ '%(log_color)s%(levelname)-8s%(reset)s | %(light_green)s%(name)s%(reset)s:'
39
+ '%(light_red)s%(funcName)s%(reset)s:%(blue)s%(lineno)d%(reset)s - %(message)s',
40
+ 'no_color': no_color,
41
+ },
42
+ 'simple': {
43
+ 'format': '%(message)s',
44
+ },
45
+ },
46
+ 'handlers': {
47
+ 'console': {
48
+ 'class': 'colorlog.StreamHandler',
49
+ 'formatter': 'default' if debug else 'simple',
50
+ },
51
+ },
52
+ 'loggers': {
53
+ 'instagram_archiver': {
54
+ 'handlers': ('console',),
55
+ 'propagate': False,
56
+ },
57
+ 'urllib3': {
58
+ 'handlers': ('console',),
59
+ 'propagate': False,
60
+ }
61
+ },
62
+ })
63
+
64
+
65
+ class JSONFormattedString:
66
+ """Contains a formatted version of the JSON str and the original value."""
67
+ def __init__(self, formatted: str, original: Any) -> None:
22
68
  self.formatted = formatted
69
+ """Formatted JSON string."""
23
70
  self.original_value = original
71
+ """Original value."""
24
72
 
73
+ @override
25
74
  def __str__(self) -> str:
26
75
  return self.formatted
27
76
 
28
77
 
29
- def json_dumps_formatted(obj: T) -> JSONFormattedString[T]:
30
- """Returns a special object with the formatted version of the JSON str and the original."""
31
- return JSONFormattedString(json.dumps(obj, sort_keys=True, indent=2), obj)
78
+ def json_dumps_formatted(obj: Any) -> JSONFormattedString:
79
+ """
80
+ Return a special object with the formatted version of the JSON str and the original.
32
81
 
33
-
34
- @contextmanager
35
- def chdir(path: str | Path) -> Iterator[None]:
36
- """Context-managing ``chdir``. Changes to old path on exit."""
37
- old_path = getcwd()
38
- os_chdir(path)
39
- try:
40
- yield
41
- finally:
42
- chdir(old_path)
82
+ Parameters
83
+ ----------
84
+ obj : Any
85
+ The object to be formatted.
86
+ """
87
+ return JSONFormattedString(json.dumps(obj, sort_keys=True, indent=2), obj)
43
88
 
44
89
 
45
90
  def write_if_new(target: Path | str, content: str | bytes, mode: str = 'w') -> None:
46
91
  """Write a file only if it will be a new file."""
47
- if not isfile(target):
92
+ if not Path(target).is_file():
48
93
  with click.open_file(str(target), mode) as f:
49
94
  f.write(content)
50
95
 
51
96
 
52
97
  class UnknownMimetypeError(Exception):
53
- """Raised when an unknown mimetype is encountered in ``get_extension()``."""
98
+ """Raised when an unknown mimetype is encountered in :py:func:`~get_extension`."""
54
99
 
55
100
 
56
101
  def get_extension(mimetype: str) -> Literal['png', 'jpg']:
57
- """Gets the appropriate three-letter extension for a mimetype."""
102
+ """
103
+ Get the appropriate three-letter extension for a mimetype.
104
+
105
+ Raises
106
+ ------
107
+ UnknownMimetypeError
108
+ If the mimetype is not recognised.
109
+ """
58
110
  if mimetype == 'image/jpeg':
59
111
  return 'jpg'
60
112
  if mimetype == 'image/png':
@@ -62,55 +114,21 @@ def get_extension(mimetype: str) -> Literal['png', 'jpg']:
62
114
  raise UnknownMimetypeError(mimetype)
63
115
 
64
116
 
65
- class InterceptHandler(logging.Handler): # pragma: no cover
66
- """Intercept handler taken from Loguru's documentation."""
67
- def emit(self, record: logging.LogRecord) -> None:
68
- level: str | int
69
- # Get corresponding Loguru level if it exists
70
- try:
71
- level = logger.level(record.levelname).name
72
- except ValueError:
73
- level = record.levelno
74
- # Find caller from where originated the logged message
75
- frame: FrameType | None = logging.currentframe()
76
- depth = 2
77
- while frame and frame.f_code.co_filename == logging.__file__:
78
- frame = frame.f_back
79
- depth += 1
80
- logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
81
-
82
-
83
- def setup_log_intercept_handler() -> None: # pragma: no cover
84
- """Sets up Loguru to intercept records from the logging module."""
85
- logging.basicConfig(handlers=(InterceptHandler(),), level=0)
86
-
87
-
88
- def setup_logging(debug: bool | None = False) -> None:
89
- """Shared function to enable logging."""
90
- if debug: # pragma: no cover
91
- setup_log_intercept_handler()
92
- logger.enable('')
93
- else:
94
- logger.configure(handlers=(dict(
95
- format='<level>{message}</level>',
96
- level='INFO',
97
- sink=sys.stderr,
98
- ),))
99
-
100
-
101
- class YoutubeDLLogger:
102
- """Basic logger front-end to loguru for use with ``YoutubeDL``."""
103
- def debug(self, message: str) -> None:
104
- if message.startswith('[debug] '):
105
- logger.debug(message)
106
- else:
107
- logger.info(message)
108
-
109
- def info(self, message: str) -> None:
110
- pass
111
-
112
- def warning(self, message: str) -> None:
113
- logger.warning(message)
114
-
115
- def error(self, message: str) -> None:
116
- logger.error(message)
117
+ if TYPE_CHECKING:
118
+
119
+ class InstagramClientInterface(Protocol):
120
+ should_save_comments: bool
121
+
122
+ def save_comments(self, edge: Edge) -> None:
123
+ ...
124
+ else:
125
+ InstagramClientInterface = object
126
+
127
+
128
+ class SaveCommentsCheckDisabledMixin(InstagramClientInterface):
129
+ """Mixin to control saving comments."""
130
+ @override
131
+ def save_comments(self, edge: Edge) -> None:
132
+ if not self.should_save_comments:
133
+ return
134
+ super().save_comments(edge) # type: ignore[safe-super]
@@ -0,0 +1,18 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 instagram-archiver authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or
12
+ substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
18
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.3
2
+ Name: instagram-archiver
3
+ Version: 0.3.1
4
+ Summary: Save Instagram content you have access to.
5
+ License: MIT
6
+ Keywords: command line,instagram
7
+ Author: Andrew Udvare
8
+ Author-email: audvare@gmail.com
9
+ Requires-Python: >=3.12,<3.14
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: beautifulsoup4 (>=4.13.4,<5.0.0)
18
+ Requires-Dist: click (>=8.2.0,<9.0.0)
19
+ Requires-Dist: colorlog (>=6.9.0,<7.0.0)
20
+ Requires-Dist: html5lib (>=1.1,<2.0)
21
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
22
+ Requires-Dist: typing-extensions (>=4.13.1,<5.0.0)
23
+ Requires-Dist: yt-dlp-utils (>=0,<1)
24
+ Project-URL: Documentation, https://instagram-archiver.readthedocs.org
25
+ Project-URL: Homepage, https://tatsh.github.io/instagram-archiver/
26
+ Project-URL: Issues, https://github.com/Tatsh/instagram-archiver/issues
27
+ Project-URL: Repository, https://github.com/Tatsh/instagram-archiver
28
+ Description-Content-Type: text/markdown
29
+
30
+ # instagram-archiver
31
+
32
+ [![Python versions](https://img.shields.io/pypi/pyversions/instagram-archiver.svg?color=blue&logo=python&logoColor=white)](https://www.python.org/)
33
+ [![PyPI - Version](https://img.shields.io/pypi/v/instagram-archiver)](https://pypi.org/project/instagram-archiver/)
34
+ [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/instagram-archiver)](https://github.com/Tatsh/instagram-archiver/tags)
35
+ [![License](https://img.shields.io/github/license/Tatsh/instagram-archiver)](https://github.com/Tatsh/instagram-archiver/blob/master/LICENSE.txt)
36
+ [![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/instagram-archiver/v0.3.1/master)](https://github.com/Tatsh/instagram-archiver/compare/v0.3.1...master)
37
+ [![QA](https://github.com/Tatsh/instagram-archiver/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/instagram-archiver/actions/workflows/qa.yml)
38
+ [![Tests](https://github.com/Tatsh/instagram-archiver/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/instagram-archiver/actions/workflows/tests.yml)
39
+ [![Coverage Status](https://coveralls.io/repos/github/Tatsh/instagram-archiver/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/instagram-archiver?branch=master)
40
+ [![Documentation Status](https://readthedocs.org/projects/instagram-archiver/badge/?version=latest)](https://instagram-archiver.readthedocs.org/?badge=latest)
41
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
42
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
43
+ [![pydocstyle](https://img.shields.io/badge/pydocstyle-enabled-AD4CD3)](http://www.pydocstyle.org/en/stable/)
44
+ [![pytest](https://img.shields.io/badge/pytest-zz?logo=Pytest&labelColor=black&color=black)](https://docs.pytest.org/en/stable/)
45
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
46
+ [![Downloads](https://static.pepy.tech/badge/instagram-archiver/month)](https://pepy.tech/project/instagram-archiver)
47
+ [![Stargazers](https://img.shields.io/github/stars/Tatsh/instagram-archiver?logo=github&style=flat)](https://github.com/Tatsh/instagram-archiver/stargazers)
48
+
49
+ [![@Tatsh](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpublic.api.bsky.app%2Fxrpc%2Fapp.bsky.actor.getProfile%2F%3Factor%3Ddid%3Aplc%3Auq42idtvuccnmtl57nsucz72%26query%3D%24.followersCount%26style%3Dsocial%26logo%3Dbluesky%26label%3DFollow%2520%40Tatsh&query=%24.followersCount&style=social&logo=bluesky&label=Follow%20%40Tatsh)](https://bsky.app/profile/Tatsh.bsky.social)
50
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109370961877277568?domain=hostux.social&style=social)](https://hostux.social/@Tatsh)
51
+
52
+ Save Instagram content you have access to.
53
+
54
+ ## Installation
55
+
56
+ ### Poetry
57
+
58
+ ```shell
59
+ poetry add instagram-archiver
60
+ ```
61
+
62
+ ### Pip
63
+
64
+ ```shell
65
+ pip install instagram-archiver
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ```plain
71
+ Usage: instagram-archiver [OPTIONS] USERNAME
72
+
73
+ Archive a profile's posts.
74
+
75
+ Options:
76
+ -o, --output-dir DIRECTORY Output directory.
77
+ -b, --browser [brave|chrome|chromium|edge|opera|vivaldi|firefox|safari]
78
+ Browser to read cookies from.
79
+ -p, --profile TEXT Browser profile.
80
+ -d, --debug Enable debug output.
81
+ --no-log Ignore log (re-fetch everything).
82
+ -C, --include-comments Also download all comments (extends download
83
+ time significantly).
84
+ -h, --help Show this message and exit.
85
+ ```
86
+
87
+ Typical use:
88
+
89
+ ```shell
90
+ instagram-archiver -o ~/instagram-backups/username username
91
+ ```
92
+
93
+ ### `instagram-save-saved`
94
+
95
+ This tool saves your saved posts (at `www.instagram.com/username/saved/all-posts`).
96
+
97
+ ```plain
98
+ Usage: instagram-save-saved [OPTIONS]
99
+
100
+ Archive your saved posts.
101
+
102
+ Options:
103
+ -o, --output-dir DIRECTORY Output directory.
104
+ -b, --browser [brave|chrome|chromium|edge|opera|vivaldi|firefox|safari]
105
+ Browser to read cookies from.
106
+ -p, --profile TEXT Browser profile.
107
+ -d, --debug Enable debug output.
108
+ -C, --include-comments Also download all comments (extends download
109
+ time significantly).
110
+ -u, --unsave Unsave posts after successful archive.
111
+ -h, --help Show this message and exit.
112
+ ```
113
+
114
+ ## Notes
115
+
116
+ The default output path is the username under the current working directory.
117
+
118
+ Videos are saved using yt-dlp and its respective configuration.
119
+
@@ -0,0 +1,15 @@
1
+ instagram_archiver/__init__.py,sha256=FtYFsiDxC03kcF2BoC_fCDdSIV_Q81cMBQIeYhA9hwk,270
2
+ instagram_archiver/__main__.py,sha256=oQ6s6zvZTBiEOgt-qep3bDY9ayxSanQr7KHzr6ENK0o,115
3
+ instagram_archiver/client.py,sha256=xde2O-ADvyFw5q7bZxbX9R2ae02ToJUElw-IKSbQhss,11091
4
+ instagram_archiver/constants.py,sha256=NJ8QlQZviY3dwwrIONThK_G9VcvAzOQM6Yg-hSyaj9A,1459
5
+ instagram_archiver/main.py,sha256=5blHCN2PSNGdTlIvXPyWr5vbgM6fW1Wn9nbzJUtBS2Y,4173
6
+ instagram_archiver/profile_scraper.py,sha256=BGnJZD3rF2e4aHpqSvG7UBoAiDjTyrpcYKSzsDgHO3M,7988
7
+ instagram_archiver/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ instagram_archiver/saved_scraper.py,sha256=PyrRCQ00x65nyiIUvmMw5-paJ6_aVrl5xmcTrHi5tHo,2769
9
+ instagram_archiver/typing.py,sha256=svEWoYwGXhkbdISw1r6tuOzo_5TwrhQTn3q0VxzBIlY,4359
10
+ instagram_archiver/utils.py,sha256=l6f0W_brZhVPOjlKwoFYYum7ICyHJXpboTU7ANIQSPI,3842
11
+ instagram_archiver-0.3.1.dist-info/LICENSE.txt,sha256=cDLmbhzFwEUz5FL_OnA6Jp9zdz80330J6YyEq-00yNQ,1093
12
+ instagram_archiver-0.3.1.dist-info/METADATA,sha256=qoIzF1FkbuqwVB9npS6JE7xS5o-fVdVSf9k4XqvDISs,5925
13
+ instagram_archiver-0.3.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
14
+ instagram_archiver-0.3.1.dist-info/entry_points.txt,sha256=kNXd0Sy6896DEBRcx2mVYiaE-OR9-XR56MpWuaNa49g,128
15
+ instagram_archiver-0.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.5.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ instagram-archiver=instagram_archiver.main:main
3
+ instagram-save-saved=instagram_archiver.main:save_saved_main
4
+
@@ -1,31 +0,0 @@
1
- from typing import Iterator, cast
2
- import re
3
-
4
- from bs4 import BeautifulSoup
5
- from yt_dlp.cookies import extract_cookies_from_browser
6
- import requests
7
-
8
- from .constants import SHARED_HEADERS
9
- from .ig_typing import BrowserName
10
-
11
- __all__ = ('find_query_hashes',)
12
-
13
-
14
- def find_query_hashes(browser: BrowserName = 'chrome', profile: str = 'Default') -> Iterator[str]:
15
- """Gets the current query hashes in Instagram's JavaScript files."""
16
- with requests.Session() as session:
17
- session.headers.update({
18
- **SHARED_HEADERS,
19
- **dict(cookie='; '.join(f'{cookie.name}={cookie.value}' \
20
- for cookie in extract_cookies_from_browser(browser, profile)
21
- if 'instagram.com' in cookie.domain))
22
- })
23
- r = session.get('https://instagram.com')
24
- r.raise_for_status()
25
- soup = BeautifulSoup(r.content, 'html5lib')
26
- for script in soup.select('script'):
27
- if script.has_attr('type') or not script.has_attr('src'):
28
- continue
29
- r = session.get(cast(str, script['src']))
30
- r.raise_for_status()
31
- yield from re.findall(r'[a-z]="([a-f0-9]{32})"', r.content.decode())