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 }