mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-16 21:52:17 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed4add951f | ||
|
|
8e6b2d83d3 | ||
|
|
ba8faec6fe | ||
|
|
9a74d9e109 | ||
|
|
08f805482c | ||
|
|
308244b809 | ||
|
|
b0c42a2662 | ||
|
|
c5dacb5f6d | ||
|
|
3a314de191 | ||
|
|
3a95250265 | ||
|
|
2a3e5980ff | ||
|
|
8159229203 | ||
|
|
ae9a5efa85 | ||
|
|
e159065799 | ||
|
|
ed21752365 | ||
|
|
b0157d9bc1 | ||
|
|
754c3845d7 | ||
|
|
e3b8c38f8c | ||
|
|
7dac8ad1e5 | ||
|
|
b4cb9ea83a | ||
|
|
eb2accc088 | ||
|
|
66c257362d | ||
|
|
a0169b7094 | ||
|
|
0a9fccaf26 |
@@ -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.0.1
|
||||
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"
|
||||
|
||||
@@ -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.0.1
|
||||
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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.0.1",
|
||||
f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev",
|
||||
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
|
||||
|
||||
|
||||
@@ -92,6 +92,9 @@ 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**.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@ Exceptions
|
||||
^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
:no-index:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
:no-index:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
:no-index:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Resumable Iterations
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
:no-index:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Instagram Structures
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
:no-index:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
|
||||
+24
-9
@@ -1,7 +1,7 @@
|
||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||
|
||||
|
||||
__version__ = '4.12'
|
||||
__version__ = '4.14rc2'
|
||||
|
||||
|
||||
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)
|
||||
|
||||
+35
-13
@@ -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), '')
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -1280,6 +1281,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 +1435,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 +1454,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 +1464,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 +1513,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 +1541,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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
@@ -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.")
|
||||
|
||||
+74
-23
@@ -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
|
||||
@@ -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:
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user