kirapo-descrambler-rust

COMIC Meteor manga downloader and descrambler
git clone https://git.ea.contact/kirapo-descrambler-rust
Log | Files | Refs | README

main.rs (6028B)


      1 use std::io::Write;
      2 use serde_json::Value;
      3 use image::GenericImage;
      4 use image::{DynamicImage, ImageBuffer, RgbaImage};
      5 
      6 type CoordsTuple = (u32, u32, u32, u32, u32, u32);
      7 #[derive(Debug)]
      8 struct Views {
      9     width: u32,
     10     height: u32,
     11     coords: Vec<CoordsTuple>,
     12 }
     13 
     14 impl std::fmt::Display for Views {
     15     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     16         write!(f, "Views {{ width: {}, height: {}, coords: {:?} }}", self.width, self.height, self.coords)
     17     }
     18 }
     19 
     20 fn parse_json(json_str: &str) -> Result<Views, Box<dyn std::error::Error>> {
     21     let value: Value = serde_json::from_str(&json_str)?;
     22     
     23     // Get the views
     24     let views_json = value["views"]
     25         .get(0)
     26         .ok_or("No views field in the json")?
     27         .as_object()
     28         .ok_or("No views field in the json")?;
     29     let width = views_json["width"]
     30         .as_u64()
     31         .ok_or("Failed to parse width from json")?
     32         as u32;
     33     let height = views_json["height"]
     34         .as_u64()
     35         .ok_or("Failed to parse height from json")?
     36         as u32;
     37     let coords_value = views_json["coords"]
     38         .as_array()
     39         .ok_or("Failed to parse coords from json")?;
     40     
     41     let mut coords: Vec<CoordsTuple> = Vec::new();
     42     for coords_str in coords_value {
     43         let s = coords_str
     44             .as_str()
     45             .ok_or("Failed to convert a coords entry into a String")?
     46             .strip_prefix("i:")
     47             .ok_or("Failed to strip the 'i:' prefix")?;
     48         let (src_part, dst_part) = s
     49             .split_once('>')
     50             .ok_or("Failed to split the src and dst parts")?;
     51         let (src_pos, size) = src_part
     52             .split_once('+')
     53             .ok_or("Failed to split the src and size parts")?;
     54 
     55         let src_pos: Vec<&str> = src_pos.split(',').collect();
     56         let size: Vec<&str> = size.split(',').collect();
     57         let dst_pos: Vec<&str> = dst_part.split(',').collect();
     58 
     59         let src_x = src_pos[0].parse::<u32>()?;
     60         let src_y = src_pos[1].parse::<u32>()?;
     61         let w = size[0].parse::<u32>()?;
     62         let h = size[1].parse::<u32>()?;
     63         let dst_x = dst_pos[0].parse::<u32>()?;
     64         let dst_y = dst_pos[1].parse::<u32>()?;
     65         coords.push((src_x, src_y, w, h, dst_x, dst_y));
     66     }
     67 
     68     Ok(Views {
     69         width,
     70         height,
     71         coords,
     72     })
     73 }
     74 
     75 fn descramble(img: &DynamicImage, views: &Views) -> RgbaImage {
     76     let mut orig = ImageBuffer::new(views.width, views.height);
     77     for (src_x, src_y, w, h, dst_x, dst_y) in views.coords.iter() {
     78         let tile = img.crop_imm(*src_x, *src_y, *w, *h);
     79         orig.copy_from(&tile, *dst_x, *dst_y);  // TODO use GenericImageView::view
     80     }
     81     orig
     82 }
     83 
     84 fn main() {
     85     // Parse args or print help
     86     fn print_usage(args: Vec<String>) {
     87         println!("Usage: {} https://kirapo.jp/*/viewer\n\tThe argument is the url of the comic you need to download. Ends with /viewer", args[0]);
     88     }
     89     let args: Vec<String> = std::env::args().collect();
     90     if args.len() != 2 || args[1] == "--help" || args[1] == "-h" {
     91         print_usage(args);
     92         std::process::exit(1);
     93     }
     94     let re = regex::Regex::new(r"https://kirapo\.jp/.*/viewer$").unwrap();
     95     if !re.is_match(&args[1]) {
     96         println!("Invalid url: {}", args[1]);
     97         print_usage(args);
     98         std::process::exit(1);
     99     }
    100     let url = format!("{}/data/", args[1].strip_suffix("/viewer").unwrap()).to_string();
    101     let id = args[1]
    102         .strip_suffix("/viewer")
    103         .unwrap()
    104         .rfind('/')
    105         .unwrap();
    106     let p = args[1].rfind('/').unwrap();
    107     let id: u32 = args[1][id+1..p].parse().unwrap();
    108 
    109     // Download the images
    110     let client = reqwest::blocking::Client::builder()
    111         .user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36")
    112         .build()
    113         .unwrap();
    114     let mut imgs: Vec<image::DynamicImage> = Vec::new();
    115     let mut views: Vec<Views> = Vec::new();
    116     for i in 1.. {
    117         print!("\rDownloading image {:04}...", i);
    118         std::io::stdout().flush().unwrap();
    119         
    120         // Download the image
    121         let img_url = format!("{}{:04}.jpg", url, i);
    122         let img = client.get(&img_url).send();
    123         if img.is_err() {
    124             eprintln!("Error while downloading an image {}: {}", i, img.as_ref().unwrap().status());
    125             std::process::exit(1);
    126         }
    127         let img = img.unwrap();
    128         if img.status() == 404 {
    129             break;
    130         }
    131         if img.status() != 200 {
    132             eprintln!("Error while downloading an image {}: {}", i, img.status());
    133             std::process::exit(1);
    134         }
    135         let buffer = img
    136             .bytes()
    137             .unwrap();
    138         let img = image::load_from_memory(&buffer)
    139             .unwrap();
    140         imgs.push(img);
    141 
    142         // Parse the json
    143         let json_url = format!("{}{:04}.ptimg.json", url, i);
    144         let json_resp = client.get(&json_url).send();
    145         if json_resp.is_err() {
    146             eprintln!("Error: {}", json_resp.as_ref().unwrap().status());
    147             std::process::exit(1);
    148         }
    149         let json_str = json_resp.unwrap().text().unwrap();
    150         let view = parse_json(&json_str).unwrap();
    151         views.push(view);
    152     }
    153     println!("\n{} images downloaded. Descrambling...", imgs.len());
    154 
    155     // Descramble the images
    156     let mut descrambled_imgs: Vec<image::RgbaImage> = Vec::new();
    157     for (img, view) in imgs.iter().zip(views.iter()) {
    158         let orig = descramble(img, view);
    159         descrambled_imgs.push(orig);
    160     }
    161 
    162     // Save the images
    163     // make a directory
    164     let dir = format!("./{}", id);
    165     std::fs::create_dir(&dir).unwrap();
    166     println!("Saving images into {}...", dir);
    167     for (img, i) in descrambled_imgs.iter().zip(1..) {
    168         print!("\rSaving image {:04} (of {:04})...", i, imgs.len());
    169         std::io::stdout().flush().unwrap();
    170         let path = format!("{}/{}.png", dir, i);
    171         img.save(path).unwrap();
    172     }
    173 
    174     println!("\nDone.");
    175 }