Compare commits

..
35 Commits
Author SHA1 Message Date
Alexander Graf 2e482b063a Release of version 4.14.1 2025-01-24 08:19:13 +01:00
Alexander Graf 0bc91b722e Fix download hashtags
Closes #2144.
2025-01-24 08:17:53 +01:00
tinyboxvk 4264d40f19 Fix typos (#2474)
Signed-off-by: tinyboxvk <13696594+tinyboxvk@users.noreply.github.com>
2025-01-15 08:29:32 +01:00
André Koch-Kramer b15a595083 Update .SRCINFO template for arch package 2024-11-21 08:41:30 +01:00
Alexander Graf 71ae32c85e Update PyPI and Arch packaging process 2024-11-21 08:26:05 +01:00
David Hotham f5134ff95e Publish wheels (#2430) 2024-11-21 07:58:27 +01:00
Alexander Graf 347f01b7fc Release of version 4.14 2024-11-07 08:21:03 +01:00
Alexander Graf 981eec9182 Third release candidate for version 4.14 2024-11-07 08:12:40 +01:00
Alexander Graf c54b61346e Merge branch 'master' into upcoming/v4.14 2024-11-07 08:12:00 +01:00
tinyboxvk fe9c8e1604 Bump pipenv, remove Python 3.8, add Python 3.13 (#2413) 2024-10-26 22:07:53 +02:00
tinyboxvk 524fb21b80 Update windows_exe.yml to address deprecation warnings (#2412) 2024-10-26 18:40:55 +02:00
Alexander Graf ed4add951f Second release candidate for version 4.14 2024-10-26 18:16:31 +02:00
Alexander Graf 8e6b2d83d3 Merge branch 'master' into upcoming/v4.14 2024-10-26 18:15:04 +02:00
Alexander Graf ba8faec6fe Release of version 4.13.2 2024-10-26 18:08:19 +02:00
Alexander Graf 9a74d9e109 Post._obtain_metadata: Use new XDT doc_id graphql query
Fixes #2384.
2024-10-26 18:08:19 +02:00
Alexander Graf 08f805482c First release candidate for version 4.14 2024-10-02 08:07:40 +02:00
Alexander Graf 308244b809 Merge branch 'master' into upcoming/v4.14 2024-10-02 08:06:35 +02:00
exwm b0c42a2662 feat: add --reels flag for downloading Reels videos from profiles (#2355) 2024-10-02 08:03:53 +02:00
Alexander Graf c5dacb5f6d Release of version 4.13.1 2024-08-28 08:29:57 +02:00
Eduardo Kalinowski 3a314de191 Remove rhx_gis header support (#2340)
The query used to obtain it does not work anymore.

The parameter was only used when obtaining a profile by its id, when
not logged in. This query is working fine without the special header,
so the whole functionality concerning rhx_gis was removed.

This also fixes this misleading messages logged when trying to obtain
a profile anonymously:
JSON Query to : Expecting value: line 1 column 1 (char 0)
2024-08-28 08:28:35 +02:00
Alex Chan 3a95250265 Clarify that post short codes should be preceded by a hyphen (#2343) 2024-08-28 08:27:58 +02:00
Yash Malik 2a3e5980ff minor doc update in basic-usage.rst (#2336)
(almost) nobody says "Id est". Even I, being a native English speaker, had to search for it. It should've been "i.e." in the first place. But, even that is not needed. Let's keep it simple.
2024-08-15 13:20:09 +02:00
Alexander Graf 8159229203 Update default user agent 2024-08-10 18:17:42 +02:00
Alexander Graf ae9a5efa85 Update dependencies 2024-08-10 18:17:42 +02:00
Alexander Graf e159065799 Release of Version 4.13 2024-08-10 17:36:41 +02:00
Eduardo Kalinowski ed21752365 Use new doc_id based query to get posts (#2330)
Fixes #2319.
2024-08-10 17:27:07 +02:00
Alexander Graf b0157d9bc1 Merge branch 'master' into upcoming/v4.13 2024-08-10 17:13:54 +02:00
Alexander Graf 754c3845d7 Release of Version 4.12.2 2024-08-10 17:00:58 +02:00
Caramel7 e3b8c38f8c Reduce GraphQL page length to 12 (#2332)
Fixes #2319.
2024-08-10 17:00:19 +02:00
Alexander Graf 7dac8ad1e5 Release of Version 4.12.1 2024-07-31 08:47:41 +02:00
Liponex b4cb9ea83a PEP 561 compliance enhancement (#2314) 2024-07-31 08:46:00 +02:00
Michel Ledig eb2accc088 feat: add max_count parameter to limit posts on download (#2276) 2024-07-17 08:03:57 +02:00
Seamus MullanandAlexander Graf 66c257362d Fix for issue #1085 (#2214)
Implement check for post.mediacount before checking if posts have been downloaded.

---------

Co-authored-by: Alexander Graf <17130992+aandergr@users.noreply.github.com>
2024-07-03 08:19:16 +02:00
Alexander Graf a0169b7094 Update default user agent 2024-06-29 19:48:19 +02:00
Alexander Graf 0a9fccaf26 Update dependencies 2024-06-29 19:48:19 +02:00
26 changed files with 528 additions and 320 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
cache: 'pipenv'
- name: "Install Dependencies"
run: |
python -m pip install pipenv==2023.12.1
python -m pip install pipenv==2024.2.0
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
- name: "Build Documentation"
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run make -C docs html SPHINXOPTS="-W -n"
+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout Instaloader Repository
uses: actions/checkout@v4
@@ -20,7 +20,7 @@ jobs:
cache: 'pipenv'
- name: Install Dependencies
run: |
python -m pip install pipenv==2023.12.1
python -m pip install pipenv==2024.2.0
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
- name: PyLint
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run pylint instaloader
+3 -3
View File
@@ -23,11 +23,11 @@ jobs:
run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
shell: bash
- name: "Install Dependencies"
run: python -m pip install setuptools
run: python -m pip install -U setuptools wheel build
- name: "Generate Python Package"
run: python setup.py sdist
run: python -m build
- name: "Publish Distribution to PyPI"
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PASSWORD }}
- name: "Publish Distribution to AUR"
+4 -14
View File
@@ -25,25 +25,15 @@ jobs:
env:
VERSION_TAG: v${{ steps.get_version.outputs.VERSION }}
run: python deploy/windows/create_exe.py
- name: Create draft release
- name: Create draft release and upload EXE as release asset
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.get_version.outputs.VERSION }}
release_name: Version ${{ steps.get_version.outputs.VERSION }}
name: Version ${{ steps.get_version.outputs.VERSION }}
draft: true
prerelease: contains(steps.get_version.outputs.VERSION, 'a') || contains(steps.get_version.outputs.VERSION, 'b') || contains(steps.get_version.outputs.VERSION, 'c')
- name: Upload EXE as release asset
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./instaloader-v${{ steps.get_version.outputs.VERSION }}-windows-standalone.zip
asset_name: instaloader-v${{ steps.get_version.outputs.VERSION }}-windows-standalone.zip
asset_content_type: application/zip
files: ./instaloader-v${{ steps.get_version.outputs.VERSION }}-windows-standalone.zip
+1 -1
View File
@@ -351,7 +351,7 @@ single-line-if-stmt=no
allow-any-import-level=
# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no
allow-reexport-from-package=yes
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
Generated
+157 -156
View File
@@ -24,11 +24,11 @@
},
"certifi": {
"hashes": [
"sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
"sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
"sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b",
"sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"
],
"markers": "python_version >= '3.6'",
"version": "==2024.2.2"
"version": "==2024.7.4"
},
"charset-normalizer": {
"hashes": [
@@ -128,11 +128,11 @@
},
"idna": {
"hashes": [
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
"sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
"sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc",
"sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"
],
"markers": "python_version >= '3.5'",
"version": "==3.6"
"version": "==3.7"
},
"jeepney": {
"hashes": [
@@ -224,30 +224,30 @@
},
"requests": {
"hashes": [
"sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==2.31.0"
"markers": "python_version >= '3.8'",
"version": "==2.32.3"
},
"urllib3": {
"hashes": [
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
"sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.1"
"version": "==2.2.2"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65",
"sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"
"sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e",
"sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"
],
"markers": "python_version >= '3.9'",
"version": "==0.7.16"
"markers": "python_version >= '3.10'",
"version": "==1.0.0"
},
"altgraph": {
"hashes": [
@@ -258,27 +258,27 @@
},
"astroid": {
"hashes": [
"sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819",
"sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"
"sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a",
"sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==3.1.0"
"version": "==3.2.4"
},
"babel": {
"hashes": [
"sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363",
"sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"
"sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b",
"sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"
],
"markers": "python_version >= '3.7'",
"version": "==2.14.0"
"markers": "python_version >= '3.8'",
"version": "==2.16.0"
},
"certifi": {
"hashes": [
"sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
"sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
"sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b",
"sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"
],
"markers": "python_version >= '3.6'",
"version": "==2024.2.2"
"version": "==2024.7.4"
},
"charset-normalizer": {
"hashes": [
@@ -387,19 +387,19 @@
},
"docutils": {
"hashes": [
"sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6",
"sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"
"sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f",
"sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"
],
"markers": "python_version >= '3.7'",
"version": "==0.20.1"
"markers": "python_version >= '3.9'",
"version": "==0.21.2"
},
"idna": {
"hashes": [
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
"sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
"sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc",
"sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"
],
"markers": "python_version >= '3.5'",
"version": "==3.6"
"version": "==3.7"
},
"imagesize": {
"hashes": [
@@ -419,11 +419,11 @@
},
"jinja2": {
"hashes": [
"sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
"sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
"sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369",
"sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.3"
"version": "==3.1.4"
},
"markupsafe": {
"hashes": [
@@ -501,37 +501,37 @@
},
"mypy": {
"hashes": [
"sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6",
"sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913",
"sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129",
"sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc",
"sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974",
"sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374",
"sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150",
"sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03",
"sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9",
"sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02",
"sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89",
"sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2",
"sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d",
"sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3",
"sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612",
"sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e",
"sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3",
"sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e",
"sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd",
"sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04",
"sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed",
"sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185",
"sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf",
"sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b",
"sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4",
"sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f",
"sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"
"sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54",
"sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a",
"sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72",
"sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69",
"sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b",
"sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe",
"sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4",
"sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd",
"sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0",
"sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525",
"sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2",
"sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c",
"sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5",
"sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de",
"sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74",
"sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c",
"sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e",
"sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58",
"sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b",
"sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417",
"sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411",
"sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb",
"sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03",
"sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca",
"sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8",
"sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08",
"sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.9.0"
"version": "==1.11.1"
},
"mypy-extensions": {
"hashes": [
@@ -543,11 +543,11 @@
},
"packaging": {
"hashes": [
"sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
"sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
"sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
"sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
],
"markers": "python_version >= '3.7'",
"version": "==24.0"
"markers": "python_version >= '3.8'",
"version": "==24.1"
},
"pefile": {
"hashes": [
@@ -560,78 +560,79 @@
},
"platformdirs": {
"hashes": [
"sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068",
"sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"
"sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee",
"sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"
],
"markers": "python_version >= '3.8'",
"version": "==4.2.0"
"version": "==4.2.2"
},
"psutil": {
"hashes": [
"sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d",
"sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73",
"sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8",
"sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2",
"sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e",
"sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36",
"sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7",
"sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c",
"sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee",
"sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421",
"sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf",
"sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81",
"sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0",
"sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631",
"sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4",
"sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"
"sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35",
"sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0",
"sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c",
"sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1",
"sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3",
"sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c",
"sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd",
"sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3",
"sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0",
"sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2",
"sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6",
"sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d",
"sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c",
"sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0",
"sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132",
"sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14",
"sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"
],
"index": "pypi",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==5.9.8"
"version": "==6.0.0"
},
"pygments": {
"hashes": [
"sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c",
"sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"
"sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199",
"sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"
],
"markers": "python_version >= '3.7'",
"version": "==2.17.2"
"markers": "python_version >= '3.8'",
"version": "==2.18.0"
},
"pyinstaller": {
"hashes": [
"sha256:0dae0edbe6d667b6b0ccd8c97a148f86474a82da7ce582296f9025f4c7242ec6",
"sha256:1b3b7d6d3b18d76a833fd5a4d7f4544c5e2c2a4db4a728ea191e62f69d5cc33c",
"sha256:5f432f3fdef053989e0a44134e483131c533dab7637e6afd80c3f7c26e6dbcc9",
"sha256:61865eee5e0d8f8252722f6d001baec497b7cee79ebe62c33a6ba86ba0c7010d",
"sha256:6cfee8a74ea2d3a1dc8e99e732a87b314739dc14363778143caac31f8aee9039",
"sha256:6ffd76a0194dac4df5e66dcfccc7b597f3eaa40ef9a3f63548f260aa2c187512",
"sha256:7c76bfcb624803c311fa8fb137e4780d0ec86d11b7d90a8f43f185e2554afdcc",
"sha256:81ec15c0deb8c7a0f95bea85b49eecc2df1bdeaf5fe487a41d97de6b0ad29dff",
"sha256:9d828213aea5401bb33a36ca396f8dc76a59a25bce1d76a13c9ad94ba29fbe42",
"sha256:a54968df2228f0128607b1dced41bbff94149d459987fb5cd1a41893e9bb85df",
"sha256:b1e55113c5a40cb7041c908a57f212f3ebd3e444dbb245ca2f91d86a76dabec5",
"sha256:e1266498893ce1d6cc7337e8d2acbf7905a10ed2b7c8377270117d6b7b922fc4"
"sha256:2bf4de17a1c63c0b797b38e13bfb4d03b5ee7c0a68e28b915a7eaacf6b76087f",
"sha256:43709c70b1da8441a730327a8ed362bfcfdc3d42c1bf89f3e2b0a163cc4e7d33",
"sha256:4e3e50743c091a06e6d01c59bdd6d03967b453ee5384a9e790759be4129db4a4",
"sha256:5ced2e83acf222b936ea94abc5a5cc96588705654b39138af8fb321d9cf2b954",
"sha256:7bf0c13c5a8560c89540746ae742f4f4b82290e95a6b478374d9f34959fe25d6",
"sha256:a0f378f64ad0655d11ade9fde7877e7573fd3d5066231608ce7dfa9040faecdd",
"sha256:b041be2fe78da47a269604d62c940d68c62f9a3913bdf64af4123f7689d47099",
"sha256:da994aba14c5686db88796684de265a8665733b4df09b939f7ebdf097d18df72",
"sha256:f15c1ef11ed5ceb32447dfbdab687017d6adbef7fc32aa359d584369bfe56eda",
"sha256:f18a3d551834ef8fb7830d48d4cc1527004d0e6b51ded7181e78374ad6111846",
"sha256:f2fc568de3d6d2a176716a3fc9f20da06d351e8bea5ddd10ecb5659fce3a05b0",
"sha256:f4a75c552facc2e2a370f1e422b971b5e5cdb4058ff38cea0235aa21fc0b378f"
],
"index": "pypi",
"markers": "python_version < '3.13' and python_version >= '3.8'",
"version": "==6.5.0"
"version": "==6.9.0"
},
"pyinstaller-hooks-contrib": {
"hashes": [
"sha256:6701752d525e1f4eda1eaec2c2affc206171e15c7a4e188a152fcf3ed3308024",
"sha256:d18657c29267c63563a96b8fc78db6ba9ae40af6702acb2f8c871df12c75b60b"
"sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a",
"sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2"
],
"markers": "python_version >= '3.7'",
"version": "==2024.3"
"markers": "python_version >= '3.8'",
"version": "==2024.8"
},
"pylint": {
"hashes": [
"sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74",
"sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"
"sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f",
"sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"
],
"index": "pypi",
"markers": "python_full_version >= '3.8.0'",
"version": "==3.1.0"
"version": "==3.2.6"
},
"pywin32-ctypes": {
"hashes": [
@@ -644,20 +645,20 @@
},
"requests": {
"hashes": [
"sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==2.31.0"
"markers": "python_version >= '3.8'",
"version": "==2.32.3"
},
"setuptools": {
"hashes": [
"sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e",
"sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"
"sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1",
"sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"
],
"markers": "python_version >= '3.8'",
"version": "==69.2.0"
"version": "==72.1.0"
},
"snowballstemmer": {
"hashes": [
@@ -668,45 +669,45 @@
},
"sphinx": {
"hashes": [
"sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560",
"sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"
"sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b",
"sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==7.2.6"
"markers": "python_version >= '3.10'",
"version": "==8.0.2"
},
"sphinx-autodoc-typehints": {
"hashes": [
"sha256:12c0e161f6fe191c2cdfd8fa3caea271f5387d9fbc67ebcd6f4f1f24ce880993",
"sha256:7f2cdac2e70fd9787926b6e9e541cd4ded1e838d2b46fda2a1bb0a75ec5b7f3a"
"sha256:b7058e8c5831e5598afca1a78fda0695d3291388d954464a6e480c36198680c0",
"sha256:fde3d888949bd0a91207cf1e54afda58121dbb4bf1f183d0cc78a0826654c974"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==2.0.0"
"markers": "python_version >= '3.9'",
"version": "==2.2.3"
},
"sphinxcontrib-applehelp": {
"hashes": [
"sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619",
"sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"
"sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1",
"sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"
],
"markers": "python_version >= '3.9'",
"version": "==1.0.8"
"version": "==2.0.0"
},
"sphinxcontrib-devhelp": {
"hashes": [
"sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f",
"sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"
"sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad",
"sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"
],
"markers": "python_version >= '3.9'",
"version": "==1.0.6"
"version": "==2.0.0"
},
"sphinxcontrib-htmlhelp": {
"hashes": [
"sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015",
"sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"
"sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8",
"sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"
],
"markers": "python_version >= '3.9'",
"version": "==2.0.5"
"version": "==2.1.0"
},
"sphinxcontrib-jquery": {
"hashes": [
@@ -727,19 +728,19 @@
},
"sphinxcontrib-qthelp": {
"hashes": [
"sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6",
"sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"
"sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab",
"sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"
],
"markers": "python_version >= '3.9'",
"version": "==1.0.7"
"version": "==2.0.0"
},
"sphinxcontrib-serializinghtml": {
"hashes": [
"sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7",
"sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"
"sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331",
"sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"
],
"markers": "python_version >= '3.9'",
"version": "==1.1.10"
"version": "==2.0.0"
},
"tomli": {
"hashes": [
@@ -752,36 +753,36 @@
},
"tomlkit": {
"hashes": [
"sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b",
"sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"
"sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72",
"sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"
],
"markers": "python_version >= '3.7'",
"version": "==0.12.4"
"markers": "python_version >= '3.8'",
"version": "==0.13.0"
},
"types-requests": {
"hashes": [
"sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d",
"sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5"
"sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358",
"sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==2.31.0.20240311"
"version": "==2.32.0.20240712"
},
"typing-extensions": {
"hashes": [
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version >= '3.8'",
"version": "==4.10.0"
"version": "==4.12.2"
},
"urllib3": {
"hashes": [
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
"sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.1"
"version": "==2.2.2"
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@
::
instaloader [--comments] [--geotags]
[--stories] [--highlights] [--tagged] [--igtv]
[--stories] [--highlights] [--tagged] [--reels] [--igtv]
[--login YOUR-USERNAME] [--fast-update]
profile | "#hashtag" | :stories | :feed | :saved
+6 -2
View File
@@ -5,9 +5,13 @@ pkgbase = instaloader
url = https://instaloader.github.io/
arch = any
license = MIT
makedepends = python-build
makedepends = python-installer
makedepends = python-setuptools
depends = python>=3.8
depends = python-requests>=2.4
makedepends = python-wheel
depends = python
depends = python-requests
optdepends = python-browser-cookie3: Import session cookies from browser
options = !emptydirs
source = instaloader-{{version}}.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v{{version}}
sha512sums = {{hash}}
+15 -5
View File
@@ -8,20 +8,30 @@ arch=('any')
url="https://instaloader.github.io/"
license=('MIT')
groups=()
depends=('python>=3.8' 'python-requests>=2.4')
makedepends=('python-setuptools')
depends=(
python
python-requests
)
makedepends=(
python-build
python-installer
python-setuptools
python-wheel
)
optdepends=(
'python-browser-cookie3: Import session cookies from browser'
)
options=('!emptydirs')
source=($pkgname-$pkgver.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v$pkgver)
sha512sums=('{{hash}}')
build() {
cd "$srcdir/$pkgname-$pkgver"
python setup.py clean --all
python setup.py build
python -m build --wheel --no-isolation
}
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
python setup.py install --root "$pkgdir/" --skip-build
python -m installer --destdir="$pkgdir" dist/*.whl
}
+1 -1
View File
@@ -44,7 +44,7 @@ with open('__main__.py', 'w+') as f:
f.writelines(lines)
# install dependencies and invoke PyInstaller
commands = ["pip install pipenv==2023.12.1",
commands = ["pip install pipenv==2024.2.0",
f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev",
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
+7 -3
View File
@@ -92,11 +92,14 @@ Instaloader supports the following targets:
- :option:`--tagged`
to **download posts where the user is tagged**, and
- :option:`--reels`
to **download Reels videos**.
- :option:`--igtv`
to **download IGTV videos**.
- ``"#hashtag"``
Posts with a certain **hashtag** (the quotes are usually necessary).
Posts with a certain **hashtag** (the quotes are usually necessary). Requires :ref:`login<login>`.
- ``%location id``
Posts tagged with a given location; the location ID is the numerical ID
@@ -122,7 +125,8 @@ Instaloader supports the following targets:
- ``-post``
Replace **post** with the post's shortcode to download single post. Must be preceded by ``--`` in
the argument list to not be mistaken as an option flag::
the argument list to not be mistaken as an option flag. For example, to download the post
https://www.instagram.com/p/**B_K4CykAOtf**, run the command::
instaloader -- -B_K4CykAOtf
@@ -231,7 +235,7 @@ The filter string must be a
where the attributes from :class:`Post` or
:class:`StoryItem` respectively are defined.
Id est, the following attributes can be used with both
The following attributes can be used with both
:option:`--post-filter` and :option:`--storyitem-filter`:
- :attr:`~Post.owner_username` (str), :attr:`~Post.owner_id` (int)
+7 -1
View File
@@ -131,6 +131,12 @@ What to Download of each Profile
.. versionadded:: 4.1
.. option:: --reels
Also download Reels videos.
.. versionadded:: 4.14
.. option:: --igtv
Also download IGTV videos.
@@ -304,7 +310,7 @@ How to Download
.. option:: --user-agent USER_AGENT
User Agent to use for HTTP requests. Per default, Instaloader pretends being
Chrome/120 on Linux.
Chrome/127 on Linux.
.. option:: --max-connection-attempts N
+1 -1
View File
@@ -48,7 +48,7 @@ See :ref:`install` for more options on how to install Instaloader.
::
instaloader [--comments] [--geotags]
[--stories] [--highlights] [--tagged] [--igtv]
[--stories] [--highlights] [--tagged] [--reels] [--igtv]
[--login YOUR-USERNAME] [--fast-update]
profile | "#hashtag" | %location_id |
:stories | :feed | :saved
+1 -1
View File
@@ -2,7 +2,7 @@ Exceptions
^^^^^^^^^^
.. module:: instaloader
:noindex:
:no-index:
.. highlight:: python
+1 -1
View File
@@ -2,7 +2,7 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. module:: instaloader
:noindex:
:no-index:
.. highlight:: python
+1 -1
View File
@@ -2,7 +2,7 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. module:: instaloader
:noindex:
:no-index:
.. highlight:: python
+1 -1
View File
@@ -2,7 +2,7 @@ Resumable Iterations
^^^^^^^^^^^^^^^^^^^^
.. module:: instaloader
:noindex:
:no-index:
.. highlight:: python
+1 -1
View File
@@ -2,7 +2,7 @@ Instagram Structures
^^^^^^^^^^^^^^^^^^^^
.. module:: instaloader
:noindex:
:no-index:
.. highlight:: python
+24 -9
View File
@@ -1,7 +1,7 @@
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
__version__ = '4.12'
__version__ = '4.14.1'
try:
@@ -13,11 +13,26 @@ else:
win_unicode_console.enable()
from .exceptions import *
from .instaloader import Instaloader
from .instaloadercontext import InstaloaderContext, RateController
from .lateststamps import LatestStamps
from .nodeiterator import NodeIterator, FrozenNodeIterator, resumable_iteration
from .structures import (Hashtag, Highlight, Post, PostSidecarNode, PostComment, PostCommentAnswer, PostLocation,
Profile, Story, StoryItem, TopSearchResults, TitlePic,
load_structure_from_file, save_structure_to_file,
load_structure, get_json_structure)
from .instaloader import Instaloader as Instaloader
from .instaloadercontext import (InstaloaderContext as InstaloaderContext,
RateController as RateController)
from .lateststamps import LatestStamps as LatestStamps
from .nodeiterator import (NodeIterator as NodeIterator,
FrozenNodeIterator as FrozenNodeIterator,
resumable_iteration as resumable_iteration)
from .structures import (Hashtag as Hashtag,
Highlight as Highlight,
Post as Post,
PostSidecarNode as PostSidecarNode,
PostComment as PostComment,
PostCommentAnswer as PostCommentAnswer,
PostLocation as PostLocation,
Profile as Profile,
Story as Story,
StoryItem as StoryItem,
TopSearchResults as TopSearchResults,
TitlePic as TitlePic,
load_structure_from_file as load_structure_from_file,
save_structure_to_file as save_structure_to_file,
load_structure as load_structure,
get_json_structure as get_json_structure)
+36 -14
View File
@@ -37,7 +37,7 @@ def usage_string():
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
return """
{0} [--comments] [--geotags]
{2:{1}} [--stories] [--highlights] [--tagged] [--igtv]
{2:{1}} [--stories] [--highlights] [--tagged] [--reels] [--igtv]
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
{0} --help""".format(argv0, len(argv0), '')
@@ -112,7 +112,7 @@ def get_cookies_from_instagram(domain, browser, cookie_file='', cookie_name=''):
print(f"Cookies loaded successfully from {browser}")
else:
raise LoginException(f"No cookies found for Instagram in {browser}, "
f"Are you logged in succesfully in {browser}?")
f"Are you logged in successfully in {browser}?")
if cookie_name:
return cookies.get(cookie_name, {})
@@ -139,6 +139,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
download_stories: bool = False,
download_highlights: bool = False,
download_tagged: bool = False,
download_reels: bool = False,
download_igtv: bool = False,
fast_update: bool = False,
latest_stamps_file: Optional[str] = None,
@@ -258,11 +259,13 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count,
post_filter=post_filter)
elif re.match(r"^[A-Za-z0-9._]+$", target):
download_profile_content = download_posts or download_tagged or download_reels or download_igtv
try:
profile = instaloader.check_profile_id(target, latest_stamps)
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
if download_profile_pic or ((download_posts or download_tagged or download_igtv)
and not profile.is_private):
if download_profile_pic or (
download_profile_content and not profile.is_private
):
raise ProfileNotExistsException("{} blocked you; But we download her anonymously."
.format(target))
else:
@@ -272,8 +275,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
except ProfileNotExistsException as err:
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
# check_profile_id() also does, since access to blocked profile may be responded with 404.
if instaloader.context.is_logged_in and (download_profile_pic or download_posts or
download_tagged or download_igtv):
if instaloader.context.is_logged_in and (download_profile_pic or download_profile_content):
instaloader.context.log(err)
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
with instaloader.anonymous_copy() as anonymous_loader:
@@ -297,18 +299,35 @@ def _main(instaloader: Instaloader, targetlist: List[str],
if instaloader.context.iphone_support and profiles and (download_profile_pic or download_posts) and \
not instaloader.context.is_logged_in:
instaloader.context.log("Hint: Login to download higher-quality versions of pictures.")
instaloader.download_profiles(profiles,
download_profile_pic, download_posts, download_tagged, download_igtv,
download_highlights, download_stories,
fast_update, post_filter, storyitem_filter, latest_stamps=latest_stamps)
instaloader.download_profiles(
profiles,
download_profile_pic,
download_posts,
download_tagged,
download_igtv,
download_highlights,
download_stories,
fast_update,
post_filter,
storyitem_filter,
latest_stamps=latest_stamps,
reels=download_reels,
)
if anonymous_retry_profiles:
instaloader.context.log("Downloading anonymously: {}"
.format(' '.join([p.username for p in anonymous_retry_profiles])))
with instaloader.anonymous_copy() as anonymous_loader:
anonymous_loader.download_profiles(anonymous_retry_profiles,
download_profile_pic, download_posts, download_tagged, download_igtv,
fast_update=fast_update, post_filter=post_filter,
latest_stamps=latest_stamps)
anonymous_loader.download_profiles(
anonymous_retry_profiles,
download_profile_pic,
download_posts,
download_tagged,
download_igtv,
fast_update=fast_update,
post_filter=post_filter,
latest_stamps=latest_stamps,
reels=download_reels
)
except KeyboardInterrupt:
print("\nInterrupted by user.", file=sys.stderr)
exit_code = ExitCode.USER_ABORTED
@@ -409,6 +428,8 @@ def main():
help='Also download highlights of each profile that is downloaded. Requires login.')
g_prof.add_argument('--tagged', action='store_true',
help='Also download posts where each profile is tagged.')
g_prof.add_argument('--reels', action='store_true',
help='Also download Reels videos.')
g_prof.add_argument('--igtv', action='store_true',
help='Also download IGTV videos.')
@@ -570,6 +591,7 @@ def main():
download_stories=download_stories,
download_highlights=args.highlights,
download_tagged=args.tagged,
download_reels=args.reels,
download_igtv=args.igtv,
fast_update=args.fast_update,
latest_stamps_file=args.latest_stamps,
+49 -6
View File
@@ -83,7 +83,7 @@ def _requires_login(func: Callable) -> Callable:
def _retry_on_connection_error(func: Callable) -> Callable:
"""Decorator to retry the function max_connection_attemps number of times.
"""Decorator to retry the function max_connection_attempts number of times.
Herewith-decorated functions need an ``_attempt`` keyword argument.
@@ -722,7 +722,7 @@ class Instaloader:
# Download the image(s) / video thumbnail and videos within sidecars if desired
downloaded = True
if post.typename == 'GraphSidecar':
if self.download_pictures or self.download_videos:
if (self.download_pictures or self.download_videos) and post.mediacount > 0:
if not _all_already_downloaded(
filename_template, enumerate(
(post.get_is_videos()[i]
@@ -866,8 +866,9 @@ class Instaloader:
last_scraped = latest_stamps.get_last_story_timestamp(name)
scraped_timestamp = datetime.now().astimezone()
for item in user_story.get_items():
if latest_stamps is not None and item.date_local <= last_scraped:
break
if latest_stamps is not None:
if item.date_local <= last_scraped:
break
if storyitem_filter is not None and not storyitem_filter(item):
self.context.log("<{} skipped>".format(item), flush=True)
continue
@@ -1214,6 +1215,7 @@ class Instaloader:
Use :meth:`Hashtag.get_posts_resumable`."""
return Hashtag.from_name(self.context, hashtag).get_posts_resumable()
@_requires_login
def download_hashtag(self, hashtag: Union[Hashtag, str],
max_count: Optional[int] = None,
post_filter: Optional[Callable[[Post], bool]] = None,
@@ -1280,6 +1282,31 @@ class Instaloader:
if latest_stamps is not None and tagged_posts.first_item is not None:
latest_stamps.set_last_tagged_timestamp(profile.username, tagged_posts.first_item.date_local)
def download_reels(self, profile: Profile, fast_update: bool = False,
post_filter: Optional[Callable[[Post], bool]] = None,
latest_stamps: Optional[LatestStamps] = None) -> None:
"""Download reels videos of a profile.
.. versionadded:: 4.14.0
"""
self.context.log("Retrieving reels videos for profile {}.".format(profile.username))
posts_takewhile: Optional[Callable[[Post], bool]] = None
if latest_stamps is not None:
last_scraped = latest_stamps.get_last_reels_timestamp(profile.username)
posts_takewhile = lambda p: p.date_local > last_scraped
reels = profile.get_reels()
self.posts_download_loop(
reels,
profile.username,
fast_update,
post_filter,
owner_profile=profile,
takewhile=posts_takewhile,
)
if latest_stamps is not None and reels.first_item is not None:
latest_stamps.set_last_reels_timestamp(profile.username, reels.first_item.date_local)
def download_igtv(self, profile: Profile, fast_update: bool = False,
post_filter: Optional[Callable[[Post], bool]] = None,
latest_stamps: Optional[LatestStamps] = None) -> None:
@@ -1409,7 +1436,9 @@ class Instaloader:
post_filter: Optional[Callable[[Post], bool]] = None,
storyitem_filter: Optional[Callable[[Post], bool]] = None,
raise_errors: bool = False,
latest_stamps: Optional[LatestStamps] = None):
latest_stamps: Optional[LatestStamps] = None,
max_count: Optional[int] = None,
reels: bool = False):
"""High-level method to download set of profiles.
:param profiles: Set of profiles to download.
@@ -1426,6 +1455,8 @@ class Instaloader:
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
catched and printed with :meth:`InstaloaderContext.error_catcher`.
:param latest_stamps: :option:`--latest-stamps`.
:param max_count: Maximum count of posts to download.
:param reels: :option:`--reels`.
.. versionadded:: 4.1
@@ -1434,6 +1465,12 @@ class Instaloader:
.. versionchanged:: 4.8
Add `latest_stamps` parameter.
.. versionchanged:: 4.13
Add `max_count` parameter.
.. versionchanged:: 4.14
Add `reels` parameter.
"""
@contextmanager
@@ -1477,6 +1514,12 @@ class Instaloader:
self.download_tagged(profile, fast_update=fast_update, post_filter=post_filter,
latest_stamps=latest_stamps)
# Download reels, if requested
if reels:
with self.context.error_catcher('Download reels of {}'.format(profile_name)):
self.download_reels(profile, fast_update=fast_update, post_filter=post_filter,
latest_stamps=latest_stamps)
# Download IGTV, if requested
if igtv:
with self.context.error_catcher('Download IGTV of {}'.format(profile_name)):
@@ -1499,7 +1542,7 @@ class Instaloader:
posts_to_download = profile.get_posts()
self.posts_download_loop(posts_to_download, profile_name, fast_update, post_filter,
total_count=profile.mediacount, owner_profile=profile,
takewhile=posts_takewhile, possibly_pinned=3)
takewhile=posts_takewhile, possibly_pinned=3, max_count=max_count)
if latest_stamps is not None and posts_to_download.first_item is not None:
latest_stamps.set_last_post_timestamp(profile_name,
posts_to_download.first_item.date_local)
+62 -32
View File
@@ -1,4 +1,3 @@
import hashlib
import json
import os
import pickle
@@ -33,7 +32,7 @@ def copy_session(session: requests.Session, request_timeout: Optional[float] = N
def default_user_agent() -> str:
return ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
'(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36')
def default_iphone_headers() -> Dict[str, Any]:
@@ -94,7 +93,6 @@ class InstaloaderContext:
self.quiet = quiet
self.max_connection_attempts = max_connection_attempts
self._graphql_page_length = 50
self._root_rhx_gis = None
self.two_factor_auth_pending = None
self.iphone_support = iphone_support
self.iphone_headers = default_iphone_headers()
@@ -104,7 +102,7 @@ class InstaloaderContext:
self._rate_controller = rate_controller(self) if rate_controller is not None else RateController(self)
# Can be set to True for testing, disables supression of InstaloaderContext._error_catcher
# Can be set to True for testing, disables suppression of InstaloaderContext._error_catcher
self.raise_all_errors = False
# HTTP status codes that should cause an AbortDownloadException
@@ -387,31 +385,42 @@ class InstaloaderContext:
def get_json(self, path: str, params: Dict[str, Any], host: str = 'www.instagram.com',
session: Optional[requests.Session] = None, _attempt=1,
response_headers: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
response_headers: Optional[Dict[str, Any]] = None,
use_post: bool = False) -> Dict[str, Any]:
"""JSON request to Instagram.
:param path: URL, relative to the given domain which defaults to www.instagram.com/
:param params: GET parameters
:param params: request parameters
:param host: Domain part of the URL from where to download the requested JSON; defaults to www.instagram.com
:param session: Session to use, or None to use self.session
:param use_post: Use POST instead of GET to make the request
:return: Decoded response dictionary
:raises QueryReturnedBadRequestException: When the server responds with a 400.
:raises QueryReturnedNotFoundException: When the server responds with a 404.
:raises ConnectionException: When query repeatedly failed.
.. versionchanged:: 4.13
Added `use_post` parameter.
"""
is_graphql_query = 'query_hash' in params and 'graphql/query' in path
is_doc_id_query = 'doc_id' in params and 'graphql/query' in path
is_iphone_query = host == 'i.instagram.com'
is_other_query = not is_graphql_query and host == "www.instagram.com"
is_other_query = not is_graphql_query and not is_doc_id_query and host == "www.instagram.com"
sess = session if session else self._session
try:
self.do_sleep()
if is_graphql_query:
self._rate_controller.wait_before_query(params['query_hash'])
if is_doc_id_query:
self._rate_controller.wait_before_query(params['doc_id'])
if is_iphone_query:
self._rate_controller.wait_before_query('iphone')
if is_other_query:
self._rate_controller.wait_before_query('other')
resp = sess.get('https://{0}/{1}'.format(host, path), params=params, allow_redirects=False)
if use_post:
resp = sess.post('https://{0}/{1}'.format(host, path), data=params, allow_redirects=False)
else:
resp = sess.get('https://{0}/{1}'.format(host, path), params=params, allow_redirects=False)
if resp.status_code in self.fatal_status_codes:
redirect = " redirect to {}".format(resp.headers['location']) if 'location' in resp.headers else ""
body = ""
@@ -462,6 +471,8 @@ class InstaloaderContext:
if isinstance(err, TooManyRequestsException):
if is_graphql_query:
self._rate_controller.handle_429(params['query_hash'])
if is_doc_id_query:
self._rate_controller.handle_429(params['doc_id'])
if is_iphone_query:
self._rate_controller.handle_429('iphone')
if is_other_query:
@@ -473,14 +484,48 @@ class InstaloaderContext:
raise ConnectionException(error_string) from err
def graphql_query(self, query_hash: str, variables: Dict[str, Any],
referer: Optional[str] = None, rhx_gis: Optional[str] = None) -> Dict[str, Any]:
referer: Optional[str] = None) -> Dict[str, Any]:
"""
Do a GraphQL Query.
:param query_hash: Query identifying hash.
:param variables: Variables for the Query.
:param referer: HTTP Referer, or None.
:param rhx_gis: 'rhx_gis' variable as somewhere returned by Instagram, needed to 'sign' request
:return: The server's response dictionary.
.. versionchanged:: 4.13.1
Removed the `rhx_gis` parameter.
"""
with copy_session(self._session, self.request_timeout) as tmpsession:
tmpsession.headers.update(self._default_http_header(empty_session_only=True))
del tmpsession.headers['Connection']
del tmpsession.headers['Content-Length']
tmpsession.headers['authority'] = 'www.instagram.com'
tmpsession.headers['scheme'] = 'https'
tmpsession.headers['accept'] = '*/*'
if referer is not None:
tmpsession.headers['referer'] = urllib.parse.quote(referer)
variables_json = json.dumps(variables, separators=(',', ':'))
resp_json = self.get_json('graphql/query',
params={'query_hash': query_hash,
'variables': variables_json},
session=tmpsession)
if 'status' not in resp_json:
self.error("GraphQL response did not contain a \"status\" field.")
return resp_json
def doc_id_graphql_query(self, doc_id: str, variables: Dict[str, Any],
referer: Optional[str] = None) -> Dict[str, Any]:
"""
Do a doc_id-based GraphQL Query using method POST.
.. versionadded:: 4.13
:param doc_id: doc_id for the query.
:param variables: Variables for the Query.
:param referer: HTTP Referer, or None.
:return: The server's response dictionary.
"""
with copy_session(self._session, self.request_timeout) as tmpsession:
@@ -495,16 +540,12 @@ class InstaloaderContext:
variables_json = json.dumps(variables, separators=(',', ':'))
if rhx_gis:
#self.log("rhx_gis {} query_hash {}".format(rhx_gis, query_hash))
values = "{}:{}".format(rhx_gis, variables_json)
x_instagram_gis = hashlib.md5(values.encode()).hexdigest()
tmpsession.headers['x-instagram-gis'] = x_instagram_gis
resp_json = self.get_json('graphql/query',
params={'query_hash': query_hash,
'variables': variables_json},
session=tmpsession)
params={'variables': variables_json,
'doc_id': doc_id,
'server_timestamps': 'true'},
session=tmpsession,
use_post=True)
if 'status' not in resp_json:
self.error("GraphQL response did not contain a \"status\" field.")
return resp_json
@@ -512,7 +553,7 @@ class InstaloaderContext:
def graphql_node_list(self, query_hash: str, query_variables: Dict[str, Any],
query_referer: Optional[str],
edge_extractor: Callable[[Dict[str, Any]], Dict[str, Any]],
rhx_gis: Optional[str] = None,
_rhx_gis: Optional[str] = None,
first_data: Optional[Dict[str, Any]] = None) -> Iterator[Dict[str, Any]]:
"""
Retrieve a list of GraphQL nodes.
@@ -524,7 +565,7 @@ class InstaloaderContext:
def _query():
query_variables['first'] = self._graphql_page_length
try:
return edge_extractor(self.graphql_query(query_hash, query_variables, query_referer, rhx_gis))
return edge_extractor(self.graphql_query(query_hash, query_variables, query_referer))
except QueryReturnedBadRequestException:
new_page_length = int(self._graphql_page_length / 2)
if new_page_length >= 12:
@@ -673,17 +714,6 @@ class InstaloaderContext:
raise QueryReturnedNotFoundException(self._response_error(resp))
raise ConnectionException(self._response_error(resp))
@property
def root_rhx_gis(self) -> Optional[str]:
"""rhx_gis string returned in the / query."""
if self.is_logged_in:
# At the moment, rhx_gis seems to be required for anonymous requests only. By returning None when logged
# in, we can save the root_rhx_gis lookup query.
return None
if self._root_rhx_gis is None:
self._root_rhx_gis = self.get_json('', {}).get('rhx_gis', '')
return self._root_rhx_gis or None
class RateController:
"""
+9
View File
@@ -18,6 +18,7 @@ class LatestStamps:
POST_TIMESTAMP = 'post-timestamp'
TAGGED_TIMESTAMP = 'tagged-timestamp'
IGTV_TIMESTAMP = 'igtv-timestamp'
REELS_TIMESTAMP = 'reels-timestamp'
STORY_TIMESTAMP = 'story-timestamp'
ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%f%z'
@@ -87,6 +88,14 @@ class LatestStamps:
"""Sets timestamp of last download of a profile's tagged posts."""
self._set_timestamp(profile_name, self.TAGGED_TIMESTAMP, timestamp)
def get_last_reels_timestamp(self, profile_name: str) -> datetime:
"""Returns timestamp of last download of a profile's reels posts."""
return self._get_timestamp(profile_name, self.REELS_TIMESTAMP)
def set_last_reels_timestamp(self, profile_name: str, timestamp: datetime):
"""Sets timestamp of last download of a profile's reels posts."""
self._set_timestamp(profile_name, self.REELS_TIMESTAMP, timestamp)
def get_last_igtv_timestamp(self, profile_name: str) -> datetime:
"""Returns timestamp of last download of a profile's igtv posts."""
return self._get_timestamp(profile_name, self.IGTV_TIMESTAMP)
+43 -22
View File
@@ -7,11 +7,11 @@ from datetime import datetime, timedelta
from lzma import LZMAError
from typing import Any, Callable, Dict, Iterable, Iterator, NamedTuple, Optional, Tuple, TypeVar
from .exceptions import AbortDownloadException, InvalidArgumentException, QueryReturnedBadRequestException
from .exceptions import AbortDownloadException, InvalidArgumentException
from .instaloadercontext import InstaloaderContext
class FrozenNodeIterator(NamedTuple):
query_hash: str
query_hash: Optional[str]
query_variables: Dict
query_referer: Optional[str]
context_username: Optional[str]
@@ -19,6 +19,7 @@ class FrozenNodeIterator(NamedTuple):
best_before: Optional[float]
remaining_data: Optional[Dict]
first_node: Optional[Dict]
doc_id: Optional[str]
FrozenNodeIterator.query_hash.__doc__ = """The GraphQL ``query_hash`` parameter."""
FrozenNodeIterator.query_variables.__doc__ = """The GraphQL ``query_variables`` parameter."""
FrozenNodeIterator.query_referer.__doc__ = """The HTTP referer used for the GraphQL query."""
@@ -28,6 +29,7 @@ FrozenNodeIterator.best_before.__doc__ = """Date when parts of the stored nodes
FrozenNodeIterator.remaining_data.__doc__ = \
"""The already-retrieved, yet-unprocessed ``edges`` and the ``page_info`` at time of freezing."""
FrozenNodeIterator.first_node.__doc__ = """Node data of the first item, if an item has been produced."""
FrozenNodeIterator.doc_id.__doc__ = """The GraphQL ``doc_id`` parameter."""
T = TypeVar('T')
@@ -64,22 +66,27 @@ class NodeIterator(Iterator[T]):
NodeIterators are matching if and only if they have the same magic.
See also :func:`resumable_iteration` for a high-level context manager that handles a resumable iteration.
.. versionchanged: 4.13
Included support for `doc_id`-based queries (using POST method).
"""
_graphql_page_length = 50
_graphql_page_length = 12
_shelf_life = timedelta(days=29)
def __init__(self,
context: InstaloaderContext,
query_hash: str,
query_hash: Optional[str],
edge_extractor: Callable[[Dict[str, Any]], Dict[str, Any]],
node_wrapper: Callable[[Dict], T],
query_variables: Optional[Dict[str, Any]] = None,
query_referer: Optional[str] = None,
first_data: Optional[Dict[str, Any]] = None,
is_first: Optional[Callable[[T, Optional[T]], bool]] = None):
is_first: Optional[Callable[[T, Optional[T]], bool]] = None,
doc_id: Optional[str] = None):
self._context = context
self._query_hash = query_hash
self._doc_id = doc_id
self._edge_extractor = edge_extractor
self._node_wrapper = node_wrapper
self._query_variables = query_variables if query_variables is not None else {}
@@ -95,26 +102,38 @@ class NodeIterator(Iterator[T]):
self._is_first = is_first
def _query(self, after: Optional[str] = None) -> Dict:
if self._doc_id is not None:
return self._query_doc_id(self._doc_id, after)
else:
assert self._query_hash is not None
return self._query_query_hash(self._query_hash, after)
def _query_doc_id(self, doc_id: str, after: Optional[str] = None) -> Dict:
pagination_variables: Dict[str, Any] = {'__relay_internal__pv__PolarisFeedShareMenurelayprovider': False}
if after is not None:
pagination_variables['after'] = after
pagination_variables['before'] = None
pagination_variables['first'] = 12
pagination_variables['last'] = None
data = self._edge_extractor(
self._context.doc_id_graphql_query(
doc_id, {**self._query_variables, **pagination_variables}, self._query_referer
)
)
self._best_before = datetime.now() + NodeIterator._shelf_life
return data
def _query_query_hash(self, query_hash: str, after: Optional[str] = None) -> Dict:
pagination_variables: Dict[str, Any] = {'first': NodeIterator._graphql_page_length}
if after is not None:
pagination_variables['after'] = after
try:
data = self._edge_extractor(
self._context.graphql_query(
self._query_hash, {**self._query_variables, **pagination_variables}, self._query_referer
)
data = self._edge_extractor(
self._context.graphql_query(
query_hash, {**self._query_variables, **pagination_variables}, self._query_referer
)
self._best_before = datetime.now() + NodeIterator._shelf_life
return data
except QueryReturnedBadRequestException:
new_page_length = int(NodeIterator._graphql_page_length / 2)
if new_page_length >= 12:
NodeIterator._graphql_page_length = new_page_length
self._context.error("HTTP Error 400 (Bad Request) on GraphQL Query. Retrying with shorter page length.",
repeat_at_end=False)
return self._query(after)
else:
raise
)
self._best_before = datetime.now() + NodeIterator._shelf_life
return data
def __iter__(self):
return self
@@ -203,6 +222,7 @@ class NodeIterator(Iterator[T]):
best_before=self._best_before.timestamp() if self._best_before else None,
remaining_data=remaining_data,
first_node=self._first_node,
doc_id=self._doc_id,
)
def thaw(self, frozen: FrozenNodeIterator) -> None:
@@ -220,7 +240,8 @@ class NodeIterator(Iterator[T]):
if (self._query_hash != frozen.query_hash or
self._query_variables != frozen.query_variables or
self._query_referer != frozen.query_referer or
self._context.username != frozen.context_username):
self._context.username != frozen.context_username or
self._doc_id != frozen.doc_id):
raise InvalidArgumentException("Mismatching resume information.")
if not frozen.best_before:
raise InvalidArgumentException("\"best before\" date missing.")
+89 -36
View File
@@ -180,7 +180,7 @@ class Post:
This class unifies access to the properties associated with a post. It implements == and is
hashable.
:param context: :attr:`Instaloader.context` used for additional queries if neccessary..
:param context: :attr:`Instaloader.context` used for additional queries if necessary..
:param node: Node structure, as returned by Instagram.
:param owner_profile: The Profile of the owner, if already known at creation.
"""
@@ -232,21 +232,32 @@ class Post:
"title": media.get("title"),
"viewer_has_liked": media["has_liked"],
"edge_media_preview_like": {"count": media["like_count"]},
"accessibility_caption": media.get("accessibility_caption"),
"comments": media.get("comment_count"),
"iphone_struct": media,
}
with suppress(KeyError):
fake_node["display_url"] = media['image_versions2']['candidates'][0]['url']
with suppress(KeyError):
with suppress(KeyError, TypeError):
fake_node["video_url"] = media['video_versions'][-1]['url']
fake_node["video_duration"] = media["video_duration"]
fake_node["video_view_count"] = media["view_count"]
with suppress(KeyError):
fake_node["edge_sidecar_to_children"] = {"edges": [{"node": {
"display_url": node['image_versions2']['candidates'][0]['url'],
"is_video": media_types[node["media_type"]] == "GraphVideo",
}} for node in media["carousel_media"]]}
with suppress(KeyError, TypeError):
fake_node["edge_sidecar_to_children"] = {"edges": [{"node":
Post._convert_iphone_carousel(node, media_types)}
for node in media["carousel_media"]]}
return cls(context, fake_node, Profile.from_iphone_struct(context, media["user"]) if "user" in media else None)
@staticmethod
def _convert_iphone_carousel(iphone_node: Dict[str, Any], media_types: Dict[int, str]) -> Dict[str, Any]:
fake_node = {
"display_url": iphone_node["image_versions2"]["candidates"][0]["url"],
"is_video": media_types[iphone_node["media_type"]] == "GraphVideo",
}
if "video_versions" in iphone_node and iphone_node["video_versions"] is not None:
fake_node["video_url"] = iphone_node["video_versions"][0]["url"]
return fake_node
@staticmethod
def shortcode_to_mediaid(code: str) -> int:
if len(code) > 11:
@@ -308,13 +319,23 @@ class Post:
def _obtain_metadata(self):
if not self._full_metadata_dict:
pic_json = self._context.graphql_query(
'2b0673e0dc4580674a88d426fe00ea90',
{'shortcode': self.shortcode}
)
self._full_metadata_dict = pic_json['data']['shortcode_media']
if self._full_metadata_dict is None:
pic_json = self._context.doc_id_graphql_query(
"8845758582119845", {"shortcode": self.shortcode}
)["data"]["xdt_shortcode_media"]
if pic_json is None:
raise BadResponseException("Fetching Post metadata failed.")
try:
xdt_types = {
"XDTGraphImage": "GraphImage",
"XDTGraphVideo": "GraphVideo",
"XDTGraphSidecar": "GraphSidecar",
}
pic_json["__typename"] = xdt_types[pic_json["__typename"]]
except KeyError as exc:
raise BadResponseException(
f"Unknown __typename in metadata: {pic_json['__typename']}."
) from exc
self._full_metadata_dict = pic_json
if self.shortcode != self._full_metadata_dict['shortcode']:
self._node.update(self._full_metadata_dict)
raise PostChangedException
@@ -491,14 +512,14 @@ class Post:
@property
def caption_hashtags(self) -> List[str]:
"""List of all lowercased hashtags (without preceeding #) that occur in the Post's caption."""
"""List of all lowercased hashtags (without preceding #) that occur in the Post's caption."""
if not self.caption:
return []
return _hashtag_regex.findall(self.caption.lower())
@property
def caption_mentions(self) -> List[str]:
"""List of all lowercased profiles that are mentioned in the Post's caption, without preceeding @."""
"""List of all lowercased profiles that are mentioned in the Post's caption, without preceding @."""
if not self.caption:
return []
return _mention_regex.findall(self.caption.lower())
@@ -723,7 +744,11 @@ class Post:
# Avoid doing additional requests if there are no comments
return []
comment_edges = self._field('edge_media_to_comment', 'edges')
try:
comment_edges = self._field("edge_media_to_parent_comment", "edges")
except KeyError:
comment_edges = self._field("edge_media_to_comment", "edges")
answers_count = sum(edge['node'].get('edge_threaded_comments', {}).get('count', 0) for edge in comment_edges)
if self.comments == len(comment_edges) + answers_count:
@@ -897,8 +922,7 @@ class Profile:
'include_reel': True,
'include_suggested_users': False,
'include_logged_out_extras': False,
'include_highlight_reels': False},
rhx_gis=context.root_rhx_gis)['data']['user']
'include_highlight_reels': False})['data']['user']
if data:
profile = cls(context, data['reel']['owner'])
else:
@@ -1055,7 +1079,7 @@ class Profile:
@property
def biography_hashtags(self) -> List[str]:
"""
List of all lowercased hashtags (without preceeding #) that occur in the Profile's biography.
List of all lowercased hashtags (without preceding #) that occur in the Profile's biography.
.. versionadded:: 4.10
"""
@@ -1066,7 +1090,7 @@ class Profile:
@property
def biography_mentions(self) -> List[str]:
"""
List of all lowercased profiles that are mentioned in the Profile's biography, without preceeding @.
List of all lowercased profiles that are mentioned in the Profile's biography, without preceding @.
.. versionadded:: 4.10
"""
@@ -1174,14 +1198,17 @@ class Profile:
:rtype: NodeIterator[Post]"""
self._obtain_metadata()
return NodeIterator(
self._context,
'003056d32c2554def87228bc3fd9668a',
lambda d: d['data']['user']['edge_owner_to_timeline_media'],
lambda n: Post(self._context, n, self),
{'id': self.userid},
'https://www.instagram.com/{0}/'.format(self.username),
self._metadata('edge_owner_to_timeline_media'),
Profile._make_is_newest_checker()
context = self._context,
edge_extractor = lambda d: d['data']['xdt_api__v1__feed__user_timeline_graphql_connection'],
node_wrapper = lambda n: Post.from_iphone_struct(self._context, n),
query_variables = {'data': {
'count': 12, 'include_relationship_info': True,
'latest_besties_reel_media': True, 'latest_reel_media': True},
'username': self.username},
query_referer = 'https://www.instagram.com/{0}/'.format(self.username),
is_first = Profile._make_is_newest_checker(),
doc_id = '7898261790222653',
query_hash = None,
)
def get_saved_posts(self) -> NodeIterator[Post]:
@@ -1218,6 +1245,30 @@ class Profile:
is_first=Profile._make_is_newest_checker()
)
def get_reels(self) -> NodeIterator[Post]:
"""Retrieve all reels from a profile.
:rtype: NodeIterator[Post]
.. versionadded:: 4.14.0
"""
self._obtain_metadata()
return NodeIterator(
context = self._context,
edge_extractor = lambda d: d['data']['xdt_api__v1__clips__user__connection_v2'],
# Reels post info is incomplete relative to regular posts so we create a Post from the shortcode
# and fetch the additional metadata with an additional API request per Reel
node_wrapper = lambda n: Post.from_shortcode(context=self._context, shortcode=n["media"]["code"]),
query_variables = {'data': {
'page_size': 12, 'include_feed_video': True, "target_user_id": str(self.userid)}},
query_referer = 'https://www.instagram.com/{0}/'.format(self.username),
is_first = Profile._make_is_newest_checker(),
# fb_api_req_friendly_name=PolarisProfileReelsTabContentQuery_connection
doc_id = '7845543455542541',
query_hash = None,
)
def get_igtv_posts(self) -> NodeIterator[Post]:
"""Retrieve all IGTV posts.
@@ -1483,7 +1534,7 @@ class StoryItem:
@property
def caption_hashtags(self) -> List[str]:
"""
List of all lowercased hashtags (without preceeding #) that occur in the StoryItem's caption.
List of all lowercased hashtags (without preceding #) that occur in the StoryItem's caption.
.. versionadded:: 4.10
"""
@@ -1494,7 +1545,7 @@ class StoryItem:
@property
def caption_mentions(self) -> List[str]:
"""
List of all lowercased profiles that are mentioned in the StoryItem's caption, without preceeding @.
List of all lowercased profiles that are mentioned in the StoryItem's caption, without preceding @.
.. versionadded:: 4.10
"""
@@ -1793,11 +1844,11 @@ class Hashtag:
@classmethod
def from_name(cls, context: InstaloaderContext, name: str):
"""
Create a Hashtag instance from a given hashtag name, without preceeding '#'. Raises an Exception if there is no
Create a Hashtag instance from a given hashtag name, without preceding '#'. Raises an Exception if there is no
hashtag with the given name.
:param context: :attr:`Instaloader.context`
:param name: Hashtag, without preceeding '#'
:param name: Hashtag, without preceding '#'
:raises: :class:`QueryReturnedNotFoundException`
"""
# pylint:disable=protected-access
@@ -1807,11 +1858,13 @@ class Hashtag:
@property
def name(self):
"""Hashtag name lowercased, without preceeding '#'"""
"""Hashtag name lowercased, without preceding '#'"""
return self._node["name"].lower()
def _query(self, params):
json_response = self._context.get_json("explore/tags/{0}/".format(self.name), params)
json_response = self._context.get_iphone_json(
"api/v1/tags/web_info/", {**params, "tag_name": self.name}
)
return json_response["graphql"]["hashtag"] if "graphql" in json_response else json_response["data"]
def _obtain_metadata(self):
@@ -2134,7 +2187,7 @@ def load_structure(context: InstaloaderContext, json_structure: dict) -> JsonExp
"""Loads a :class:`Post`, :class:`Profile`, :class:`StoryItem`, :class:`Hashtag` or :class:`FrozenNodeIterator` from
a json structure.
:param context: :attr:`Instaloader.context` linked to the new object, used for additional queries if neccessary.
:param context: :attr:`Instaloader.context` linked to the new object, used for additional queries if necessary.
:param json_structure: Instaloader JSON structure
.. versionadded:: 4.8
@@ -2164,7 +2217,7 @@ def load_structure_from_file(context: InstaloaderContext, filename: str) -> Json
"""Loads a :class:`Post`, :class:`Profile`, :class:`StoryItem`, :class:`Hashtag` or :class:`FrozenNodeIterator` from
a '.json' or '.json.xz' file that has been saved by :func:`save_structure_to_file`.
:param context: :attr:`Instaloader.context` linked to the new object, used for additional queries if neccessary.
:param context: :attr:`Instaloader.context` linked to the new object, used for additional queries if necessary.
:param filename: Filename, ends in '.json' or '.json.xz'
"""
compressed = filename.endswith('.xz')
+5 -5
View File
@@ -18,10 +18,10 @@ def get_version():
raise SystemExit("Could not find version string.")
if sys.version_info < (3, 8):
sys.exit('Instaloader requires Python >= 3.8.')
if sys.version_info < (3, 9):
sys.exit('Instaloader requires Python >= 3.9.')
requirements = ['requests>=2.4']
requirements = ['requests>=2.25']
optional_requirements = {
'browser_cookie3': ['browser_cookie3>=0.19.1'],
}
@@ -46,7 +46,7 @@ setup(
'from Instagram.',
long_description=open(os.path.join(SRC, 'README.rst')).read(),
install_requires=requirements,
python_requires='>=3.8',
python_requires='>=3.9',
extras_require=optional_requirements,
entry_points={'console_scripts': ['instaloader=instaloader.__main__:main']},
zip_safe=False,
@@ -58,11 +58,11 @@ setup(
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet',
'Topic :: Multimedia :: Graphics'