Esempio n. 1
0
 def on_item_finish(_, __, item, file_path):
     nonlocal downs, existings
     if file_path is not None:
         downs += 1
     else:
         existings += 1
         bar.set_description_str("[" + "♻︎ Existing".center(15) + "]")
         time.sleep(0.1)
     if dump_metadata:
         bar.set_description_str("[" + "Ⓓ Dumping".center(15) + "]")
         data_dumper(final_dest, to_datetime(item.created_time), item.as_dict(extra=True))
     bar.update(1)
Esempio n. 2
0
def _down_posts(posts,
                dest: str = None,
                directory: str = None,
                dump_metadata: bool = False):
    """High-level function for downloading media of a list of posts. Decorates the process with tqdm progress bar.
    * This function calls `down_containers` function and wraps it with 'for' loop & progress bar to support downloading multiple posts.

    Arguments:
        posts: a generator which generates `Post` instances or a list that contains preloaded `Post` instances
        dest: download destination (should be a directory)
        directory: make a new directory inside `dest` to store all the files
        dump_metadata: (force create a sub directory of the post and) dump metadata of each post to a file inside if True

    Returns:
        bool: True if file already exists and skipped the download process
        path: full path to the download destination if download succeeded
    """
    is_preloaded = isinstance(posts, list)
    path = None
    total = len(posts) if is_preloaded else None
    logger.info("Downloading {0} posts {1}...".format(
        total or "(?)", "with " + str(sum([len(x) for x in posts])) +
        " media in total" if is_preloaded else ""))
    # prepare progress bar, hide progress bar when quiet and show download details when debugging
    with progress(total=total, desc="Processing", ascii=False) as bar:
        for i, p in enumerate(posts, start=1):
            bar.set_postfix_str("(" + (
                p.shortcode if len(p.shortcode) <= 11 else p.shortcode[:8] +
                "...") + ") " + p.typename)
            logger.debug("Downloading {0} of {1} posts...".format(
                i, total or "(?)"))
            # download
            subdir = to_datetime(p.created_time) + "_" + p.shortcode
            # NOTE: force_subdir if dump_metadata ?
            path = _down_containers(
                p, dest, directory, subdir, force_subdir=False
            )  # `subdir` can also be the filename if the post has only one media
            # dump metadata
            if dump_metadata:
                filename = subdir + ".json"
                metadata_file = os.path.join(
                    path, filename)  # path inside the sub directory
                logger.debug("-> [{0}] dump metadata".format(filename))
                with open(metadata_file, "w+") as f:
                    json.dump(p.as_dict(), f, indent=4)
            bar.update(1)
    if path:  # path is None if error occurred in `_down_containers()`
        logger.info("Destination: {0}".format(path))
    return path
Esempio n. 3
0
def cookies_handler(**args):
    action = args.get("action")
    insta = load_obj()
    cookies = [f for f in os.listdir(COOKIES_DIR) if os.path.splitext(f)[1] == ".cookie"]

    if action == "save":
        if insta is None:
            error_print("No session is logged in currently.", exit=1)
        dump_cookie(insta.my_username, insta.cookies)
        print(Fore.LIGHTGREEN_EX + Style.BRIGHT + "⬇ Saved cookie for session of " + Fore.WHITE + "@{}".format(insta.my_username))

    elif action == "remove":
        ids = args.get("id")
        valid = False
        for id in ids:
            for i, filename in enumerate(cookies, start=1):
                if i == id:
                    os.remove(os.path.join(COOKIES_DIR, filename))
                    print(Fore.LIGHTGREEN_EX + "♻ Removed cookie file of", Style.BRIGHT + "@{}".format(os.path.splitext(filename)[0]))
                    valid = True
        if not valid:
            error_print("Invalid ID. No cookie was removed.")

    else:
        print("\n" + "    " + Style.BRIGHT + "\033[4mSaved Cookies\n" + Style.RESET_ALL)
        print(Style.BRIGHT + "Location:", COOKIES_DIR, end="\n\n")
        for i, filename in enumerate(cookies):
            username, ext = os.path.splitext(filename)
            # print entry
            symbol = (Fore.LIGHTYELLOW_EX + "*" + Fore.RESET) if insta and username == insta.my_username else " "
            modtime = os.path.getmtime(os.path.join(COOKIES_DIR, filename))
            expiretime = next(x for x in load_cookie(username, modtime=True) if x.name == "csrftoken").expires
            print(Fore.MAGENTA + "(" + str(i + 1) + ")", symbol, username)
            print(Fore.CYAN + "Last Login:"******"%Y-%m-%d %X"))
            print(Fore.CYAN + "Expire Time:", Fore.LIGHTBLACK_EX + to_datetime(int(expiretime), "%Y-%m-%d %X"), end="\n\n")
        print(Style.DIM + "If the cookie file of your current logged in session is removed, you will be forced to perform a REAL logout in the next logout action.")
Esempio n. 4
0
    def download(self,
                 dest: str = None,
                 *,
                 write: bool = True,
                 verify: bool = True,
                 on_item_start: Callable = None,
                 on_item_finish: Callable = None,
                 on_item_error: Callable = None):
        """Download all reel items of this story.

        Arguments:
            dest: Path to the destination directory.
            write: Write file to disk if True, write to memory otherwise.
            verify: Verify file integrity if True, check the size of file in bytes otherwise. See 'MediaItem.download()'.
            on_item_start: A callable (Story, int, ReelItem). Called on start of each item.
            on_item_finish: A callable (Story, int, ReelItem, str). Called on finish of each item.
            on_item_error: A callable (Story, int, ReelItem, Exception). Called on error of each item.
        """
        dest = os.path.abspath(dest or "./")
        reel_items = self.reel_items()
        logger.debug("Downloading {0} ({1} media) [{2}]...".format(
            repr(self), len(reel_items), self.typename))
        logger.debug("Dest: " + dest)

        for i, item in enumerate(reel_items):
            if on_item_start is not None:
                on_item_start(self, i, item)
            try:
                filename = to_datetime(item.created_time)
                file_path = item.download(dest,
                                          filename,
                                          write=write,
                                          verify=verify)
                if file_path is not None:
                    set_mtime(file_path, item.created_time)
                if on_item_finish is not None:
                    on_item_finish(self, i, item, file_path)
            except Exception as e:
                # NOTE: if the Story has multiple reel items to download, the occurrence of exception will NOT interrupt
                # the whole download of the story, unless user reraises the exception in 'on_item_error()'.
                exc_type, exc_value, tb = sys.exc_info()
                logger.error("{}: {}".format(exc_type.__name__, exc_value))
                logger.debug("".join(traceback.format_tb(tb)))
                if on_item_error is not None:
                    on_item_error(self, i, item, e)
                continue
Esempio n. 5
0
def _down_structure(structure, dest: str = None, directory: str = None, subdir: str = None, force_subdir: bool = False):
    """Download media of containers of a single structure to `dest`. May deecorate the proccess with progress bar.
    - If there is multiple media in the structure, a sub directory will be created to store the media.
    * Called in `download_story` and `download_post` individualy.
    * If a file with the same path and filename found, it will skip the download process.

    [dest]
        [directory]
            [sub directory] (multi or dump_metadata=True)
            [file]
            ...

    Arguments:
        structure: a structure object that has attrubute `obtain_media()` (`Post` or `Story`)
        dest: destination path, one will be created if directory not found (must be a directory)
        directory: make a new directory inside `dest` to store all files
        subdir: name of the sub directory which is created when downloading multiple media
        force_subdir: force create a sub directory and store all the media (used when dump_metadata=True)

    Returns:
        str: full path to the download destination
        tuple: (downs, exists)
    """
    dest = dest or "./"
    path = os.path.abspath(dest)
    if not os.path.isdir(path):
        logger.debug("{0} directory not found. Creating one...".format(path))
        os.mkdir(path)
    if directory:
        path = os.path.join(path, directory)
        if not os.path.isdir(path):
            os.mkdir(path)
    return_path = path

    media = structure.obtain_media()
    multi = len(media) > 1
    if multi or force_subdir:
        if subdir:
            # create a sub directory for multiple media of a post
            path = os.path.join(path, subdir)
            if not os.path.isdir(path):
                os.mkdir(path)

    logger.debug("Downloading {0} ({1} media) [{2}]...".format(subdir or directory, len(media), structure.typename))
    logger.debug("Path: " + path)
    downs = exists = 0
    with progress(len(media), disable=False) as bar:
        for i, (tname, src) in enumerate(media, start=1):
            bar.set_postfix_str(tname)
            if multi:
                filename = str(i)
            else:
                filename = subdir or str(i)

            if structure.__class__.__name__ in ("Story", "Highlight"):
                # * exclusively and explictly change filename to datetime string for Story and Highlight
                filename = to_datetime(structure.created_time_list[i - 1])

            # check if the file / directory already exists
            if os.path.isfile(os.path.join(path, filename + ".jpg")) or os.path.isfile(os.path.join(path, filename + ".mp4")):
                exists += 1
                logger.debug("file already downloaded, skipped !")
                bar.set_description_str(Back.BLUE + Fore.BLACK + "[" + "Exists".center(11) + "]" + Style.RESET_ALL)
                time.sleep(0.1)  # give some time for displaying the 'Exists' badge of the progress bar
            else:
                # download
                state = _down_from_src(src, filename, path)
                if state:
                    downs += 1
            bar.update(1)
    return return_path, (downs, exists)
Esempio n. 6
0
def login_handler(**args):
    parser = args.get("parser")
    username = args.get("username")
    password = args.get("password")
    cookie_name = None
    if password and not username:
        parser.error("username must be specified together with password")
    insta = load_obj()
    cookies = [
        f for f in os.listdir(COOKIES_DIR)
        if os.path.splitext(f)[1] == ".cookie"
    ]

    if username:
        for filename in cookies:
            name, _ = os.path.splitext(filename)
            if name == username:
                cookie_name = name
                break
        else:
            if not password:
                warn_print("No cookie file associated with user",
                           Style.BRIGHT + Fore.WHITE + "'{}'".format(username))
                password = prompt("Password: "******"\n" + "    " + Style.BRIGHT + "\033[4mSaved Cookies\n" +
              Style.RESET_ALL)
        for i, filename in enumerate(cookies):
            name, ext = os.path.splitext(filename)
            # print entry
            symbol = (
                Fore.LIGHTYELLOW_EX + "*" +
                Fore.RESET) if insta and name == insta.my_username else " "
            mtime = os.path.getmtime(os.path.join(COOKIES_DIR, filename))
            print(Fore.MAGENTA + "(" + str(i + 1) + ")", symbol, name)
            print(Fore.CYAN + "Last Login:"******"%Y-%m-%d %X"),
                  end="\n\n")
        # print option: Login New Account
        print(Fore.MAGENTA + "(" + str(len(cookies) + 1) + ")",
              Fore.LIGHTGREEN_EX + "+", "[Login New Account]\n")
        # user choice input
        choice = prompt(
            "(1-{})choice> ".format(len(cookies) + 1),
            lambda x: x.isdigit() and 0 < int(x) <= len(cookies) + 1,
            "invalid index")
        index = int(choice) - 1
        if index == len(cookies):
            username = prompt("Username: "******"Password: "******"You have already logged into",
                   Style.BRIGHT + Fore.WHITE + "@{}".format(insta.my_username))
        yorn = prompt(
            "Are you sure you want to give up the current session? (Y/n)> ",
            lambda x: x.isalpha() and (x.lower() in ("y", "n")))
        if yorn == "y":
            logout_handler(real=False)
        else:
            error_print("Operation aborted.", exit=1)
    insta = Instagram()

    # Login
    try:
        insta.login(username, password,
                    load_cookie(cookie_name) if cookie_name else None)

    # handle 2FA
    except TwoFactorAuthRequired:
        code = prompt("[2FA] Security Code: ",
                      lambda x: x.isdigit() and len(str(x)) == 6,
                      "invalid security code")
        with error_catcher():
            insta.two_factor_login(code)

    # handle Checkpoint
    except CheckpointChallengeRequired:
        print(
            "ⓘ Note that if there is no phone number bound to this account, choosing SMS will fail the challenge."
        )
        mode = prompt("[Checkpoint] (0)-SMS (1)-Email (0|1)> ",
                      lambda x: x.isdigit() and (int(x) in (0, 1)),
                      "invalid choice, only 0 or 1 is accepted")
        with error_catcher():
            auth = insta.checkpoint_challenge_login(int(mode))
        code = prompt("[Checkpoint] Security Code: ",
                      lambda x: x.isdigit() and len(str(x)) == 6,
                      "invalid security code")
        with error_catcher():
            auth(code)

    except InstascrapeError as e:
        error_print(str(e), exit=1)

    print("• Dumping cookie...")
    dump_cookie(insta.my_username, insta.cookies)
    print("• Dumping object...")
    dump_obj(insta)
    if cookie_name:
        print(Fore.LIGHTBLUE_EX + Style.BRIGHT + "▶︎ Resumed session of " +
              Fore.WHITE + "@{}".format(insta.my_username))
    else:
        print(Fore.LIGHTGREEN_EX + Style.BRIGHT + "✔ Logged in as " +
              Fore.WHITE + "@{}".format(insta.my_username))