ik-mini-epub-cli-interactive 0.2.0-alpha.4

An Interactive CLI tool to download IK Storeis into epubs.
use cliclack::{confirm, input, intro, log, note, outro, password, spinner};
use ik_mini::types::StoryResponse;
use ik_mini::InkittClient;
use ik_mini_epub::download_story_to_folder;
use reqwest::Client;
use std::path::PathBuf;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> anyhow::Result<(), anyhow::Error> {
    intro("KittDownload")?;

    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));

    tracing_subscriber::fmt().with_env_filter(filter).init();

    let http_client = Client::builder()
        .cookie_store(true)
        .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36")
        .build()?;

    let inkitt_client = InkittClient::new();

    let story_id: u64;

    loop {
        let story_id_input: u64 = input("Enter Story ID")
            .placeholder("e.g. 123456789")
            .interact()?;

        let StoryResponse {
            title: story_title,
            teaser: story_teaser,
            age_rating: story_age_rating,
            ..
        } = inkitt_client.story.get_story_info(story_id_input).await?;

        let mut story_teaser = story_teaser;
        const MAX_CAPTION_LENGTH: usize = 100;

        if 0 < MAX_CAPTION_LENGTH {
            let ellipsis = "...";
            if story_teaser.chars().count() > MAX_CAPTION_LENGTH {
                if MAX_CAPTION_LENGTH > ellipsis.len() {
                    let trim_to = MAX_CAPTION_LENGTH - ellipsis.len();
                    story_teaser = story_teaser.chars().take(trim_to).collect();
                    story_teaser.push_str(ellipsis);
                } else {
                    story_teaser.clear();
                }
            }
        } else {
            story_teaser.clear();
        }

        note(
            "Story Details",
            format!(
                "Title:       {}\nDescription: {}\nAge Rating:  {}",
                story_title, story_teaser, story_age_rating
            ),
        )?;

        let is_correct = confirm("Is this the story you want to proceed?").interact()?;

        if is_correct {
            story_id = story_id_input;
            break;
        } else {
            log::info("Don't worry, let's start over")?;
        }
    }

    let include_images = confirm("Do you want to include images?").interact()?;

    let semaphore_count: u64 = input(
        "Please enter no of concurrent chapters to be processed concurrently. (Default is 20)",
    )
    .placeholder("e.g. 20")
    .default_input("20")
    .interact()?;

    let output_dir: PathBuf =
        input("Please enter output directory path (relative or absolute) (Default is current dir)")
            .placeholder("Ex: C:/book_downloads")
            .default_input("./")
            .interact()?;

    loop {
        let email: String = input("Please enter your Inkitt email").interact()?;
        let pass: String = password("Please enter your Inkitt password")
            .mask('')
            .interact()?;

        let spin = spinner();
        spin.start("Checking authentication status...");

        if ik_mini_epub::login(&inkitt_client, &email, &pass)
            .await
            .is_ok()
        {
            spin.stop("Authentication successful!");
            break;
        } else {
            spin.error("Authentication failed");
            log::error("Failed to authenticate, retry.")?;
        }
    }

    let spin = spinner();
    spin.start("Processing download...");

    download_story_to_folder(
        &inkitt_client,
        &http_client,
        story_id,
        include_images,
        semaphore_count as usize,
        &output_dir,
    )
    .await?;

    spin.stop("Download complete!");

    outro("Job's done!!!")?;

    Ok(())
}