diff --git a/docs/as-module.rst b/docs/as-module.rst index 229812a..4206817 100644 --- a/docs/as-module.rst +++ b/docs/as-module.rst @@ -1,7 +1,7 @@ .. meta:: :description: - Instaloader can also be used as a powerful and easy-to-use - Python API for Instagram, allowing to download media and metadata. + Documentation of Instaloader module, a powerful and easy-to-use + Python library to download Instagram media and metadata. Python Module :mod:`instaloader` -------------------------------- diff --git a/docs/basic-usage.rst b/docs/basic-usage.rst index e646a5e..9175929 100644 --- a/docs/basic-usage.rst +++ b/docs/basic-usage.rst @@ -1,3 +1,10 @@ +.. meta:: + :description: + How to download pictures from Instagram. Description of basic + usage of Instaloader, free tool to download photos from public + and private profiles, hashtags, stories, feeds, saved media, and + their metadata, comments and captions. + Download Pictures from Instagram --------------------------------- diff --git a/docs/index.rst b/docs/index.rst index d3a7e37..e18ac7e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,9 @@ .. meta:: :description: - Command line tool to download pictures (and videos) from Instagram. - Instaloader downloads public and private profiles, hashtags, user stories, - feeds, saved media, comments, geotags, captions and other metadata of each post. + Free command line tool to download photos from Instagram. + Scrapes public and private profiles, hashtags, stories, feeds, + saved media, and their metadata, comments and captions. + Written in Python. Instaloader =========== diff --git a/instaloader.py b/instaloader.py index b45a88e..5d1d556 100755 --- a/instaloader.py +++ b/instaloader.py @@ -7,6 +7,7 @@ import getpass import json import os import pickle +import platform import random import re import shutil @@ -30,8 +31,14 @@ import requests.utils import urllib3 -__version__ = '3.3.1' +__version__ = '3.3.3' +# NOTE: duplicated in README.rst and docs/index.rst +USAGE_STRING = """ +{0} [--comments] [--geotags] [--stories] + [--login YOUR-USERNAME] [--fast-update] + profile | "#hashtag" | :stories | :feed | :saved +{0} --help""".format(sys.argv[0]) try: # pylint:disable=wrong-import-position @@ -128,10 +135,10 @@ def mediaid_to_shortcode(mediaid: int) -> str: return b64encode(mediaid.to_bytes(9, 'big'), b'-_').decode().replace('A', ' ').lstrip().replace(' ','A') -def format_string_contains_key(format_string: str, key: str) -> bool: +def format_string_contains_key(format_string: '_PathPattern', key: str) -> bool: # pylint:disable=unused-variable for literal_text, field_name, format_spec, conversion in string.Formatter().parse(format_string): - if field_name == key or field_name.startswith(key + '.'): + if field_name and (field_name == key or field_name.startswith(key + '.')): return True return False @@ -606,6 +613,14 @@ def _requires_login(func: Callable) -> Callable: return call +class _PathPattern(str): + """Class overriding :meth:`str.format` for character substitution in paths for Windows, see issue #84.""" + + def format(self, *args: Any, **kwargs: Any) -> str: + ret = super().format(*args, **kwargs) + return ret.replace(':', '\ua789') if platform.system() == 'Windows' else ret + + class Instaloader: GRAPHQL_PAGE_LENGTH = 200 @@ -629,18 +644,19 @@ class Instaloader: self.username = None self.sleep = sleep self.quiet = quiet - self.dirname_pattern = dirname_pattern if dirname_pattern is not None else '{target}' + self.dirname_pattern = _PathPattern(dirname_pattern if dirname_pattern is not None else '{target}') if filename_pattern is not None: filename_pattern = re.sub(r"({(?:post\.)?date)([:}])", r"\1_utc\2", filename_pattern) self.filename_pattern_old = filename_pattern.replace('{date_utc}', '{date_utc:%Y-%m-%d_%H-%M-%S}') - self.filename_pattern_old = re.sub(r"(?i)({(?:post\.)?date_utc:[^}]*?)_UTC", - r"\1", self.filename_pattern_old) + self.filename_pattern_old = _PathPattern(re.sub(r"(?i)({(?:post\.)?date_utc:[^}]*?)_UTC", + r"\1", self.filename_pattern_old)) filename_pattern = re.sub(r"(?i)({(date_utc|post\.date_utc):(?![^}]*UTC[^}]*).*?)}", r"\1_UTC}", filename_pattern) - self.filename_pattern = filename_pattern.replace('{date_utc}', '{date_utc:%Y-%m-%d_%H-%M-%S_UTC}') + self.filename_pattern = _PathPattern(filename_pattern.replace('{date_utc}', + '{date_utc:%Y-%m-%d_%H-%M-%S_UTC}')) else: - self.filename_pattern = '{date_utc:%Y-%m-%d_%H-%M-%S_UTC}' - self.filename_pattern_old = '{date_utc:%Y-%m-%d_%H-%M-%S}' + self.filename_pattern = _PathPattern('{date_utc:%Y-%m-%d_%H-%M-%S_UTC}') + self.filename_pattern_old = _PathPattern('{date_utc:%Y-%m-%d_%H-%M-%S}') self.download_videos = download_videos self.download_video_thumbnails = download_video_thumbnails self.download_geotags = download_geotags @@ -1651,6 +1667,14 @@ class Instaloader: # Save session if it is useful if self.is_logged_in: self.save_session_to_file(sessionfile) + # User might be confused if Instaloader does nothing + if not targetlist: + if self.is_logged_in: + # Instaloader did at least save a session file + self._log("No targets were specified, thus nothing has been downloaded.") + else: + # Instloader did not do anything + self._log("usage:"+USAGE_STRING) if self.error_log: print("\nErrors occured:", file=sys.stderr) for err in self.error_log: @@ -1658,7 +1682,7 @@ class Instaloader: def main(): - parser = ArgumentParser(description=__doc__, add_help=False, + parser = ArgumentParser(description=__doc__, add_help=False, usage=USAGE_STRING, epilog="Report issues at https://github.com/instaloader/instaloader/issues. " "The complete documentation can be found at " "https://instaloader.github.io/.")