Merge branch 'upcoming/v4.15'

This commit is contained in:
Alexander Graf
2025-11-08 22:19:37 +01:00
15 changed files with 800 additions and 604 deletions
+2 -2
View File
@@ -14,11 +14,11 @@ jobs:
- name: "Setup Python" - name: "Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.12 python-version: 3.14
cache: 'pipenv' cache: 'pipenv'
- name: "Install Dependencies" - name: "Install Dependencies"
run: | run: |
python -m pip install pipenv==2024.2.0 python -m pip install pipenv==2025.0.4
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
- name: "Build Documentation" - name: "Build Documentation"
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run make -C docs html SPHINXOPTS="-W -n" run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run make -C docs html SPHINXOPTS="-W -n"
+3 -2
View File
@@ -9,7 +9,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps: steps:
- name: Checkout Instaloader Repository - name: Checkout Instaloader Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -20,9 +20,10 @@ jobs:
cache: 'pipenv' cache: 'pipenv'
- name: Install Dependencies - name: Install Dependencies
run: | run: |
python -m pip install pipenv==2024.2.0 python -m pip install pipenv==2025.0.4
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
- name: PyLint - name: PyLint
if: matrix.python-version != '3.9'
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run pylint instaloader run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run pylint instaloader
- name: MyPy - name: MyPy
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run mypy -m instaloader run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run mypy -m instaloader
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- name: "Setup Python" - name: "Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.12 python-version: 3.14
- name: "Get the tagged version" - name: "Get the tagged version"
id: get_version id: get_version
env: env:
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: "3.12" python-version: "3.14"
cache: 'pipenv' cache: 'pipenv'
architecture: x64 architecture: x64
- name: Get the tagged version - name: Get the tagged version
+1
View File
@@ -433,6 +433,7 @@ disable=bad-inline-option,
too-many-instance-attributes, too-many-instance-attributes,
too-many-lines, too-many-lines,
too-many-locals, too-many-locals,
too-many-positional-arguments,
too-many-public-methods, too-many-public-methods,
too-many-statements, too-many-statements,
unnecessary-lambda-assignment, unnecessary-lambda-assignment,
Generated
+739 -564
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -44,7 +44,7 @@ with open('__main__.py', 'w+') as f:
f.writelines(lines) f.writelines(lines)
# install dependencies and invoke PyInstaller # install dependencies and invoke PyInstaller
commands = ["pip install pipenv==2024.2.0", commands = ["pip install pipenv==2025.0.4",
f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev", f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev",
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"] "pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
+12 -10
View File
@@ -208,22 +208,24 @@ Instead of :option:`--login`, it is possible to use
.. option:: --load-cookies BROWSER-NAME, -b BROWSER-NAME .. option:: --load-cookies BROWSER-NAME, -b BROWSER-NAME
Use Instagram cookie in your browser to login. Load Instagram session cookies from a browser instead of using Instaloader's own login mechanism.
This feature requires the browser_cookie3 library. This method requires the `browser_cookie3 <https://github.com/borisbabic/browser_cookie3>`__ library.
Compatible with :option:`--cookiefile` if you want to load cookies from browser profiles. By default, cookies are loaded from the browser's main profile, but you can specify a custom cookie file using :option:`--cookiefile`.
Incompatible with :option:`--login` due to potential username mismatch between user input and browser login.
Supported browsers: Brave, Chrome, Chromium, Edge, Firefox, LibreWolf, Opera, Opera_GX, Safari and Vivaldi.
In subsequent runs, you can just use :option:`--login` to reuse the Since :option:`--load-cookies` and :option:`--login` are alternative ways to acquire a session, they cannot be used together when initially acquiring a session.
same session, which is saved by Instaloader.
Supported browsers: Arc, Brave, Chrome, Chromium, Edge, Firefox, LibreWolf, Opera, Opera_GX, Safari, and Vivaldi.
Once logged in via browser cookies, Instaloader saves the session, allowing you to use :option:`--login` in subsequent runs.
.. versionadded:: 4.11 .. versionadded:: 4.11
.. option:: --cookiefile COOKIE-FILE, -B COOKIE-FILE .. option:: --cookiefile COOKIE-FILE, -B COOKIE-FILE
Cookie file path of a browser profile to load cookies from. Specifies a cookie file path for :option:`--load-cookies` to load cookies from a particular browser profile.
This option must be used together with :option:`--load-cookies` and does not work on its own.
.. versionadded:: 4.11 .. versionadded:: 4.11
.. option:: --sessionfile SESSIONFILE, -f SESSIONFILE .. option:: --sessionfile SESSIONFILE, -f SESSIONFILE
@@ -310,7 +312,7 @@ How to Download
.. option:: --user-agent USER_AGENT .. option:: --user-agent USER_AGENT
User Agent to use for HTTP requests. Per default, Instaloader pretends being User Agent to use for HTTP requests. Per default, Instaloader pretends being
Chrome/127 on Linux. Chrome/142 on Linux.
.. option:: --max-connection-attempts N .. option:: --max-connection-attempts N
+1 -1
View File
@@ -2,7 +2,7 @@ import instaloader
L = instaloader.Instaloader() L = instaloader.Instaloader()
posts = instaloader.Hashtag.from_name(L.context, 'urbanphotography').get_posts() posts = instaloader.Hashtag.from_name(L.context, 'urbanphotography').get_posts_resumable()
users = set() users = set()
@@ -3,7 +3,7 @@ import instaloader
L = instaloader.Instaloader() L = instaloader.Instaloader()
posts = instaloader.Hashtag.from_name(L.context, "urbanphotography").get_posts() posts = instaloader.Hashtag.from_name(L.context, "urbanphotography").get_posts_resumable()
SINCE = datetime(2020, 5, 10) # further from today, inclusive SINCE = datetime(2020, 5, 10) # further from today, inclusive
UNTIL = datetime(2020, 5, 11) # closer to today, not inclusive UNTIL = datetime(2020, 5, 11) # closer to today, not inclusive
+1 -1
View File
@@ -1,7 +1,7 @@
"""Download pictures (or videos) along with their captions and other metadata from Instagram.""" """Download pictures (or videos) along with their captions and other metadata from Instagram."""
__version__ = '4.14.2' __version__ = '4.15a1'
try: try:
+5 -4
View File
@@ -737,19 +737,20 @@ class Instaloader:
suffix: Optional[str] = str(edge_number) suffix: Optional[str] = str(edge_number)
if '{filename}' in self.filename_pattern: if '{filename}' in self.filename_pattern:
suffix = None suffix = None
if self.download_pictures and (not sidecar_node.is_video or self.download_video_thumbnails): video_url = sidecar_node.video_url
if self.download_pictures and (video_url is None or self.download_video_thumbnails):
# pylint:disable=cell-var-from-loop # pylint:disable=cell-var-from-loop
sidecar_filename = self.__prepare_filename(filename_template, sidecar_filename = self.__prepare_filename(filename_template,
lambda: sidecar_node.display_url) lambda: sidecar_node.display_url)
# Download sidecar picture or video thumbnail (--no-pictures implies --no-video-thumbnails) # Download sidecar picture or video thumbnail (--no-pictures implies --no-video-thumbnails)
downloaded &= self.download_pic(filename=sidecar_filename, url=sidecar_node.display_url, downloaded &= self.download_pic(filename=sidecar_filename, url=sidecar_node.display_url,
mtime=post.date_local, filename_suffix=suffix) mtime=post.date_local, filename_suffix=suffix)
if sidecar_node.is_video and self.download_videos: if video_url is not None and self.download_videos:
# pylint:disable=cell-var-from-loop # pylint:disable=cell-var-from-loop
sidecar_filename = self.__prepare_filename(filename_template, sidecar_filename = self.__prepare_filename(filename_template,
lambda: sidecar_node.video_url) lambda: video_url)
# Download sidecar video if desired # Download sidecar video if desired
downloaded &= self.download_pic(filename=sidecar_filename, url=sidecar_node.video_url, downloaded &= self.download_pic(filename=sidecar_filename, url=video_url,
mtime=post.date_local, filename_suffix=suffix) mtime=post.date_local, filename_suffix=suffix)
else: else:
downloaded = False downloaded = False
+4 -4
View File
@@ -32,15 +32,15 @@ def copy_session(session: requests.Session, request_timeout: Optional[float] = N
def default_user_agent() -> str: def default_user_agent() -> str:
return ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' return ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36') '(KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36')
def default_iphone_headers() -> Dict[str, Any]: def default_iphone_headers() -> Dict[str, Any]:
return {'User-Agent': 'Instagram 273.0.0.16.70 (iPad13,8; iOS 16_3; en_US; en-US; ' \ return {'User-Agent': 'Instagram 361.0.0.35.82 (iPad13,8; iOS 18_0; en_US; en-US; ' \
'scale=2.00; 2048x2732; 452417278) AppleWebKit/420+', 'scale=2.00; 2048x2732; 674117118) AppleWebKit/420+',
'x-ads-opt-out': '1', 'x-ads-opt-out': '1',
'x-bloks-is-panorama-enabled': 'true', 'x-bloks-is-panorama-enabled': 'true',
'x-bloks-version-id': '01507c21540f73e2216b6f62a11a5b5e51aa85491b72475c080da35b1228ddd6', 'x-bloks-version-id': '16b7bd25c6c06886d57c4d455265669345a2d96625385b8ee30026ac2dc5ed97',
'x-fb-client-ip': 'True', 'x-fb-client-ip': 'True',
'x-fb-connection-type': 'wifi', 'x-fb-connection-type': 'wifi',
'x-fb-http-engine': 'Liger', 'x-fb-http-engine': 'Liger',
+27 -12
View File
@@ -20,7 +20,7 @@ class PostSidecarNode(NamedTuple):
"""Item of a Sidecar Post.""" """Item of a Sidecar Post."""
is_video: bool is_video: bool
display_url: str display_url: str
video_url: str video_url: Optional[str]
PostSidecarNode.is_video.__doc__ = "Whether this node is a video." PostSidecarNode.is_video.__doc__ = "Whether this node is a video."
@@ -1206,18 +1206,33 @@ class Profile:
:rtype: NodeIterator[Post]""" :rtype: NodeIterator[Post]"""
self._obtain_metadata() self._obtain_metadata()
logged_in = self._context.is_logged_in
return NodeIterator( return NodeIterator(
context = self._context, context=self._context,
edge_extractor = lambda d: d['data']['xdt_api__v1__feed__user_timeline_graphql_connection'], edge_extractor=(
node_wrapper = lambda n: Post.from_iphone_struct(self._context, n), (lambda d: d["data"]["xdt_api__v1__feed__user_timeline_graphql_connection"])
query_variables = {'data': { if logged_in
'count': 12, 'include_relationship_info': True, else (lambda d: d["data"]["user"]["edge_owner_to_timeline_media"])
'latest_besties_reel_media': True, 'latest_reel_media': True}, ),
'username': self.username}, node_wrapper=(
query_referer = 'https://www.instagram.com/{0}/'.format(self.username), (lambda n: Post.from_iphone_struct(self._context, n))
is_first = Profile._make_is_newest_checker(), if logged_in
doc_id = '7898261790222653', else (lambda n: Post(self._context, n, self))
query_hash = None, ),
query_variables={
"data": {
"count": 12,
"include_relationship_info": True,
"latest_besties_reel_media": True,
"latest_reel_media": True,
},
**({"username": self.username} if logged_in else {"id": self.userid}),
},
query_referer="https://www.instagram.com/{0}/".format(self.username),
is_first=Profile._make_is_newest_checker(),
doc_id="7898261790222653" if logged_in else "7950326061742207",
query_hash=None,
first_data=(None if logged_in else self._metadata("edge_owner_to_timeline_media")),
) )
def get_saved_posts(self) -> NodeIterator[Post]: def get_saved_posts(self) -> NodeIterator[Post]:
+1
View File
@@ -63,6 +63,7 @@ setup(
'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet', 'Topic :: Internet',
'Topic :: Multimedia :: Graphics' 'Topic :: Multimedia :: Graphics'