mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-24 01:19:38 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3d763cf3 | ||
|
|
e2d42807a2 | ||
|
|
d07bd6a096 | ||
|
|
0462d5c34a | ||
|
|
3cc29a4ceb | ||
|
|
ffb0aea5eb | ||
|
|
84bb60c155 | ||
|
|
7857909886 | ||
|
|
2206a7e5d7 | ||
|
|
f1b5e9cdf1 | ||
|
|
8debcbb7ab |
@@ -17,10 +17,10 @@ jobs:
|
|||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
- name: "Install Dependencies"
|
- name: "Install Dependencies"
|
||||||
run: |
|
run: |
|
||||||
python -m pip install pipenv==2022.1.8
|
python -m pip install pipenv==2023.2.4
|
||||||
pipenv sync --dev
|
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
|
||||||
- name: "Build Documentation"
|
- name: "Build Documentation"
|
||||||
run: pipenv run make -C docs html SPHINXOPTS="-W -n"
|
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run make -C docs html SPHINXOPTS="-W -n"
|
||||||
- name: "Deploy Documentation"
|
- name: "Deploy Documentation"
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.2.5
|
uses: JamesIves/github-pages-deploy-action@v4.2.5
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ jobs:
|
|||||||
name: PyLint and MyPy
|
name: PyLint and MyPy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Instaloader Repository
|
- name: Checkout Instaloader Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -18,9 +19,9 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install pipenv==2022.1.8
|
python -m pip install pipenv==2023.2.4
|
||||||
pipenv sync --dev
|
pipenv --python `python --version | grep -Eo '3\.[0-9]+'` sync --dev
|
||||||
- name: PyLint
|
- name: PyLint
|
||||||
run: pipenv run pylint instaloader
|
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run pylint instaloader
|
||||||
- name: MyPy
|
- name: MyPy
|
||||||
run: pipenv run mypy -m instaloader
|
run: pipenv --python `python --version | grep -Eo '3\.[0-9]+'` run mypy -m instaloader
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
stale-issue-message: 'There has been no activity on this question for an extended period of time. This issue will be closed after further 14 days of inactivity.'
|
stale-issue-message: 'There has been no activity on this question for an extended period of time. This issue will be closed after further 14 days of inactivity.'
|
||||||
stale-issue-label: 'stale'
|
stale-issue-label: 'stale'
|
||||||
exempt-issue-labels: 'leave open'
|
exempt-issue-labels: 'leave open'
|
||||||
days-before-stale: 15
|
days-before-stale: 21
|
||||||
days-before-close: -1
|
days-before-close: -1
|
||||||
remove-stale-when-updated: false
|
remove-stale-when-updated: false
|
||||||
- uses: actions/stale@v1
|
- uses: actions/stale@v1
|
||||||
@@ -30,5 +30,5 @@ jobs:
|
|||||||
stale-pr-label: 'stale'
|
stale-pr-label: 'stale'
|
||||||
exempt-issue-label: 'leave open'
|
exempt-issue-label: 'leave open'
|
||||||
exempt-pr-label: 'leave open'
|
exempt-pr-label: 'leave open'
|
||||||
days-before-stale: 135
|
days-before-stale: 189
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ psutil = "*"
|
|||||||
typing-extensions = "*"
|
typing-extensions = "*"
|
||||||
types-requests = "*"
|
types-requests = "*"
|
||||||
typed-ast = "*"
|
typed-ast = "*"
|
||||||
|
jinja2 = "~=3.0.1"
|
||||||
|
wrapt = "*" # Needed for Python<3.11
|
||||||
|
tomli = "*" # Needed for Python<3.11
|
||||||
|
dill = "*" # Needed for Python<3.11
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
requests = "*"
|
requests = "*"
|
||||||
|
|||||||
Generated
+570
-267
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,9 @@ Supporters
|
|||||||
|
|
||||||
.. current-sponsors-start
|
.. current-sponsors-start
|
||||||
|
|
||||||
|
| Instaloader is proudly sponsored by
|
||||||
|
| `@socialmethod <https://github.com/socialmethod>`__
|
||||||
|
|
||||||
See `Alex' GitHub Sponsors <https://github.com/sponsors/aandergr>`__ page for
|
See `Alex' GitHub Sponsors <https://github.com/sponsors/aandergr>`__ page for
|
||||||
how you can sponsor the development of Instaloader!
|
how you can sponsor the development of Instaloader!
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pkgbase = instaloader
|
|||||||
arch = any
|
arch = any
|
||||||
license = MIT
|
license = MIT
|
||||||
makedepends = python-setuptools
|
makedepends = python-setuptools
|
||||||
depends = python>=3.6
|
depends = python>=3.8
|
||||||
depends = python-requests>=2.4
|
depends = python-requests>=2.4
|
||||||
options = !emptydirs
|
options = !emptydirs
|
||||||
source = instaloader-{{version}}.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v{{version}}
|
source = instaloader-{{version}}.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v{{version}}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ arch=('any')
|
|||||||
url="https://instaloader.github.io/"
|
url="https://instaloader.github.io/"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
groups=()
|
groups=()
|
||||||
depends=('python>=3.6' 'python-requests>=2.4')
|
depends=('python>=3.8' 'python-requests>=2.4')
|
||||||
makedepends=('python-setuptools')
|
makedepends=('python-setuptools')
|
||||||
options=('!emptydirs')
|
options=('!emptydirs')
|
||||||
source=($pkgname-$pkgver.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v$pkgver)
|
source=($pkgname-$pkgver.tar.gz::https://codeload.github.com/instaloader/instaloader/tar.gz/v$pkgver)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ with open('__main__.py', 'w+') as f:
|
|||||||
f.writelines(lines)
|
f.writelines(lines)
|
||||||
|
|
||||||
# install dependencies and invoke PyInstaller
|
# install dependencies and invoke PyInstaller
|
||||||
commands = ["pip install pipenv==2022.1.8",
|
commands = ["pip install pipenv==2023.2.4",
|
||||||
f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev",
|
f"pipenv --python {sys.version_info.major}.{sys.version_info.minor} sync --dev",
|
||||||
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
|
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Install Instaloader
|
|||||||
To **install Instaloader**,
|
To **install Instaloader**,
|
||||||
|
|
||||||
#. Ensure that you have `Python <https://www.python.org/>`__, at least
|
#. Ensure that you have `Python <https://www.python.org/>`__, at least
|
||||||
version 3.6, and `pip <https://pypi.python.org/pypi/pip>`__
|
version 3.8, and `pip <https://pypi.python.org/pypi/pip>`__
|
||||||
installed.
|
installed.
|
||||||
|
|
||||||
#. Then, install Instaloader using::
|
#. Then, install Instaloader using::
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.9.3'
|
__version__ = '4.9.6'
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ def filterstr_to_filterfunc(filter_str: str, item_type: type):
|
|||||||
|
|
||||||
class TransformFilterAst(ast.NodeTransformer):
|
class TransformFilterAst(ast.NodeTransformer):
|
||||||
def visit_Name(self, node: ast.Name):
|
def visit_Name(self, node: ast.Name):
|
||||||
# pylint:disable=no-self-use
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
raise InvalidArgumentException("Invalid filter: Modifying variables ({}) not allowed.".format(node.id))
|
raise InvalidArgumentException("Invalid filter: Modifying variables ({}) not allowed.".format(node.id))
|
||||||
if node.id == "datetime":
|
if node.id == "datetime":
|
||||||
|
|||||||
+15
-10
@@ -221,8 +221,8 @@ class Instaloader:
|
|||||||
download_comments: bool = False,
|
download_comments: bool = False,
|
||||||
save_metadata: bool = True,
|
save_metadata: bool = True,
|
||||||
compress_json: bool = True,
|
compress_json: bool = True,
|
||||||
post_metadata_txt_pattern: str = None,
|
post_metadata_txt_pattern: Optional[str] = None,
|
||||||
storyitem_metadata_txt_pattern: str = None,
|
storyitem_metadata_txt_pattern: Optional[str] = None,
|
||||||
max_connection_attempts: int = 3,
|
max_connection_attempts: int = 3,
|
||||||
request_timeout: float = 300.0,
|
request_timeout: float = 300.0,
|
||||||
rate_controller: Optional[Callable[[InstaloaderContext], RateController]] = None,
|
rate_controller: Optional[Callable[[InstaloaderContext], RateController]] = None,
|
||||||
@@ -447,7 +447,7 @@ class Instaloader:
|
|||||||
"""Updates picture caption / Post metadata info"""
|
"""Updates picture caption / Post metadata info"""
|
||||||
def _elliptify(caption):
|
def _elliptify(caption):
|
||||||
pcaption = caption.replace('\n', ' ').strip()
|
pcaption = caption.replace('\n', ' ').strip()
|
||||||
return '[' + ((pcaption[:29] + u"\u2026") if len(pcaption) > 31 else pcaption) + ']'
|
return '[' + ((pcaption[:29] + "\u2026") if len(pcaption) > 31 else pcaption) + ']'
|
||||||
filename += '.txt'
|
filename += '.txt'
|
||||||
caption += '\n'
|
caption += '\n'
|
||||||
pcaption = _elliptify(caption)
|
pcaption = _elliptify(caption)
|
||||||
@@ -878,13 +878,18 @@ class Instaloader:
|
|||||||
filename_template = os.path.join(dirname, self.format_filename(item, target=target))
|
filename_template = os.path.join(dirname, self.format_filename(item, target=target))
|
||||||
filename = self.__prepare_filename(filename_template, lambda: item.url)
|
filename = self.__prepare_filename(filename_template, lambda: item.url)
|
||||||
downloaded = False
|
downloaded = False
|
||||||
if not item.is_video or self.download_video_thumbnails is True:
|
video_url_fetch_failed = False
|
||||||
|
if item.is_video and self.download_videos is True:
|
||||||
|
video_url = item.video_url
|
||||||
|
if video_url:
|
||||||
|
filename = self.__prepare_filename(filename_template, lambda: str(video_url))
|
||||||
|
downloaded |= (not _already_downloaded(filename + ".mp4") and
|
||||||
|
self.download_pic(filename=filename, url=video_url, mtime=date_local))
|
||||||
|
else:
|
||||||
|
video_url_fetch_failed = True
|
||||||
|
if video_url_fetch_failed or not item.is_video or self.download_video_thumbnails is True:
|
||||||
downloaded = (not _already_downloaded(filename + ".jpg") and
|
downloaded = (not _already_downloaded(filename + ".jpg") and
|
||||||
self.download_pic(filename=filename, url=item.url, mtime=date_local))
|
self.download_pic(filename=filename, url=item.url, mtime=date_local))
|
||||||
if item.is_video and self.download_videos is True:
|
|
||||||
filename = self.__prepare_filename(filename_template, lambda: str(item.video_url))
|
|
||||||
downloaded |= (not _already_downloaded(filename + ".mp4") and
|
|
||||||
self.download_pic(filename=filename, url=item.video_url, mtime=date_local))
|
|
||||||
# Save caption if desired
|
# Save caption if desired
|
||||||
metadata_string = _ArbitraryItemFormatter(item).format(self.storyitem_metadata_txt_pattern).strip()
|
metadata_string = _ArbitraryItemFormatter(item).format(self.storyitem_metadata_txt_pattern).strip()
|
||||||
if metadata_string:
|
if metadata_string:
|
||||||
@@ -1075,7 +1080,7 @@ class Instaloader:
|
|||||||
'has_stories': False})["data"]
|
'has_stories': False})["data"]
|
||||||
|
|
||||||
@_requires_login
|
@_requires_login
|
||||||
def download_feed_posts(self, max_count: int = None, fast_update: bool = False,
|
def download_feed_posts(self, max_count: Optional[int] = None, fast_update: bool = False,
|
||||||
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Download pictures from the user's feed.
|
Download pictures from the user's feed.
|
||||||
@@ -1096,7 +1101,7 @@ class Instaloader:
|
|||||||
self.posts_download_loop(self.get_feed_posts(), ":feed", fast_update, post_filter, max_count=max_count)
|
self.posts_download_loop(self.get_feed_posts(), ":feed", fast_update, post_filter, max_count=max_count)
|
||||||
|
|
||||||
@_requires_login
|
@_requires_login
|
||||||
def download_saved_posts(self, max_count: int = None, fast_update: bool = False,
|
def download_saved_posts(self, max_count: Optional[int] = None, fast_update: bool = False,
|
||||||
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
||||||
"""Download user's saved pictures.
|
"""Download user's saved pictures.
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ def copy_session(session: requests.Session, request_timeout: Optional[float] = N
|
|||||||
"""Duplicates a requests.Session."""
|
"""Duplicates a requests.Session."""
|
||||||
new = requests.Session()
|
new = requests.Session()
|
||||||
new.cookies = requests.utils.cookiejar_from_dict(requests.utils.dict_from_cookiejar(session.cookies))
|
new.cookies = requests.utils.cookiejar_from_dict(requests.utils.dict_from_cookiejar(session.cookies))
|
||||||
new.headers = session.headers.copy()
|
new.headers = session.headers.copy() # type: ignore
|
||||||
# Override default timeout behavior.
|
# Override default timeout behavior.
|
||||||
# Need to silence mypy bug for this. See: https://github.com/python/mypy/issues/2427
|
# Need to silence mypy bug for this. See: https://github.com/python/mypy/issues/2427
|
||||||
new.request = partial(new.request, timeout=request_timeout) # type: ignore
|
new.request = partial(new.request, timeout=request_timeout) # type: ignore
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
||||||
@@ -331,7 +331,8 @@ class InstaloaderContext:
|
|||||||
while resp.is_redirect:
|
while resp.is_redirect:
|
||||||
redirect_url = resp.headers['location']
|
redirect_url = resp.headers['location']
|
||||||
self.log('\nHTTP redirect from https://{0}/{1} to {2}'.format(host, path, redirect_url))
|
self.log('\nHTTP redirect from https://{0}/{1} to {2}'.format(host, path, redirect_url))
|
||||||
if redirect_url.startswith('https://www.instagram.com/accounts/login'):
|
if (redirect_url.startswith('https://www.instagram.com/accounts/login') or
|
||||||
|
redirect_url.startswith('https://i.instagram.com/accounts/login')):
|
||||||
if not self.is_logged_in:
|
if not self.is_logged_in:
|
||||||
raise LoginRequiredException("Redirected to login page. Use --login.")
|
raise LoginRequiredException("Redirected to login page. Use --login.")
|
||||||
# alternate rate limit exceeded behavior
|
# alternate rate limit exceeded behavior
|
||||||
@@ -589,7 +590,6 @@ class RateController:
|
|||||||
"""Wait given number of seconds."""
|
"""Wait given number of seconds."""
|
||||||
# Not static, to allow for the behavior of this method to depend on context-inherent properties, such as
|
# Not static, to allow for the behavior of this method to depend on context-inherent properties, such as
|
||||||
# whether we are logged in.
|
# whether we are logged in.
|
||||||
# pylint:disable=no-self-use
|
|
||||||
time.sleep(secs)
|
time.sleep(secs)
|
||||||
|
|
||||||
def _dump_query_timestamps(self, current_time: float, failed_query_type: str):
|
def _dump_query_timestamps(self, current_time: float, failed_query_type: str):
|
||||||
@@ -613,7 +613,6 @@ class RateController:
|
|||||||
control."""
|
control."""
|
||||||
# Not static, to allow for the count_per_sliding_window to depend on context-inherent properties, such as
|
# Not static, to allow for the count_per_sliding_window to depend on context-inherent properties, such as
|
||||||
# whether we are logged in.
|
# whether we are logged in.
|
||||||
# pylint:disable=no-self-use
|
|
||||||
return 75 if query_type == 'other' else 200
|
return 75 if query_type == 'other' else 200
|
||||||
|
|
||||||
def _reqs_in_sliding_window(self, query_type: Optional[str], current_time: float, window: float) -> List[float]:
|
def _reqs_in_sliding_window(self, query_type: Optional[str], current_time: float, window: float) -> List[float]:
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class NodeIterator(Iterator[T]):
|
|||||||
query_variables: Optional[Dict[str, Any]] = None,
|
query_variables: Optional[Dict[str, Any]] = None,
|
||||||
query_referer: Optional[str] = None,
|
query_referer: Optional[str] = None,
|
||||||
first_data: Optional[Dict[str, Any]] = None,
|
first_data: Optional[Dict[str, Any]] = None,
|
||||||
is_first: Optional[Callable[[T], bool]] = None):
|
is_first: Optional[Callable[[T, Optional[T]], bool]] = None):
|
||||||
self._context = context
|
self._context = context
|
||||||
self._query_hash = query_hash
|
self._query_hash = query_hash
|
||||||
self._edge_extractor = edge_extractor
|
self._edge_extractor = edge_extractor
|
||||||
@@ -131,7 +131,7 @@ class NodeIterator(Iterator[T]):
|
|||||||
raise
|
raise
|
||||||
item = self._node_wrapper(node)
|
item = self._node_wrapper(node)
|
||||||
if self._is_first is not None:
|
if self._is_first is not None:
|
||||||
if self._is_first(item):
|
if self._is_first(item, self.first_item):
|
||||||
self._first_node = node
|
self._first_node = node
|
||||||
else:
|
else:
|
||||||
if self._first_node is None:
|
if self._first_node is None:
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class SectionIterator(Iterator[T]):
|
|||||||
def _query(self, max_id: Optional[str] = None) -> Dict[str, Any]:
|
def _query(self, max_id: Optional[str] = None) -> Dict[str, Any]:
|
||||||
pagination_variables = {"max_id": max_id} if max_id is not None else {}
|
pagination_variables = {"max_id": max_id} if max_id is not None else {}
|
||||||
return self._sections_extractor(
|
return self._sections_extractor(
|
||||||
self._context.get_json(self._query_path, params={"__a": 1, **pagination_variables})
|
self._context.get_json(self._query_path, params={"__a": 1, "__d": "dis", **pagination_variables})
|
||||||
)
|
)
|
||||||
|
|
||||||
def __next__(self) -> T:
|
def __next__(self) -> T:
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ class Post:
|
|||||||
.. versionadded:: 4.2.6"""
|
.. versionadded:: 4.2.6"""
|
||||||
def _elliptify(caption):
|
def _elliptify(caption):
|
||||||
pcaption = ' '.join([s.replace('/', '\u2215') for s in caption.splitlines() if s]).strip()
|
pcaption = ' '.join([s.replace('/', '\u2215') for s in caption.splitlines() if s]).strip()
|
||||||
return (pcaption[:30] + u"\u2026") if len(pcaption) > 31 else pcaption
|
return (pcaption[:30] + "\u2026") if len(pcaption) > 31 else pcaption
|
||||||
return _elliptify(self.caption) if self.caption else ''
|
return _elliptify(self.caption) if self.caption else ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -558,7 +558,7 @@ class Post:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
comment_edges = self._field('edge_media_to_comment', 'edges')
|
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])
|
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:
|
if self.comments == len(comment_edges) + answers_count:
|
||||||
# If the Post's metadata already contains all parent comments, don't do GraphQL requests to obtain them
|
# If the Post's metadata already contains all parent comments, don't do GraphQL requests to obtain them
|
||||||
@@ -638,7 +638,7 @@ class Post:
|
|||||||
location_id = int(loc['id'])
|
location_id = int(loc['id'])
|
||||||
if any(k not in loc for k in ('name', 'slug', 'has_public_page', 'lat', 'lng')):
|
if any(k not in loc for k in ('name', 'slug', 'has_public_page', 'lat', 'lng')):
|
||||||
loc.update(self._context.get_json("explore/locations/{0}/".format(location_id),
|
loc.update(self._context.get_json("explore/locations/{0}/".format(location_id),
|
||||||
params={'__a': 1})['native_location_data']['location_info'])
|
params={'__a': 1, '__d': 'dis'})['native_location_data']['location_info'])
|
||||||
self._location = PostLocation(location_id, loc['name'], loc['slug'], loc['has_public_page'],
|
self._location = PostLocation(location_id, loc['name'], loc['slug'], loc['has_public_page'],
|
||||||
loc.get('lat'), loc.get('lng'))
|
loc.get('lat'), loc.get('lng'))
|
||||||
return self._location
|
return self._location
|
||||||
@@ -1039,17 +1039,8 @@ class Profile:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _make_is_newest_checker() -> Callable[[Post], bool]:
|
def _make_is_newest_checker() -> Callable[[Post, Optional[Post]], bool]:
|
||||||
newest_date: Optional[datetime] = None
|
return lambda post, first: first is None or post.date_local > first.date_local
|
||||||
def is_newest(p: Post) -> bool:
|
|
||||||
nonlocal newest_date
|
|
||||||
post_date = p.date_local
|
|
||||||
if newest_date is None or post_date > newest_date:
|
|
||||||
newest_date = post_date
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
return is_newest
|
|
||||||
|
|
||||||
def get_followers(self) -> NodeIterator['Profile']:
|
def get_followers(self) -> NodeIterator['Profile']:
|
||||||
"""
|
"""
|
||||||
@@ -1559,7 +1550,7 @@ class Hashtag:
|
|||||||
|
|
||||||
def _obtain_metadata(self):
|
def _obtain_metadata(self):
|
||||||
if not self._has_full_metadata:
|
if not self._has_full_metadata:
|
||||||
self._node = self._query({"__a": 1})
|
self._node = self._query({"__a": 1, "__d": "dis"})
|
||||||
self._has_full_metadata = True
|
self._has_full_metadata = True
|
||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self):
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ def get_version():
|
|||||||
raise SystemExit("Could not find version string.")
|
raise SystemExit("Could not find version string.")
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 8):
|
||||||
sys.exit('Instaloader requires Python >= 3.6.')
|
sys.exit('Instaloader requires Python >= 3.8.')
|
||||||
|
|
||||||
requirements = ['requests>=2.4']
|
requirements = ['requests>=2.4']
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ setup(
|
|||||||
'from Instagram.',
|
'from Instagram.',
|
||||||
long_description=open(os.path.join(SRC, 'README.rst')).read(),
|
long_description=open(os.path.join(SRC, 'README.rst')).read(),
|
||||||
install_requires=requirements,
|
install_requires=requirements,
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.8',
|
||||||
entry_points={'console_scripts': ['instaloader=instaloader.__main__:main']},
|
entry_points={'console_scripts': ['instaloader=instaloader.__main__:main']},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
keywords=keywords,
|
keywords=keywords,
|
||||||
@@ -54,10 +54,10 @@ setup(
|
|||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 3.6',
|
|
||||||
'Programming Language :: Python :: 3.7',
|
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
|
'Programming Language :: Python :: 3.11',
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Topic :: Multimedia :: Graphics'
|
'Topic :: Multimedia :: Graphics'
|
||||||
|
|||||||
Reference in New Issue
Block a user