├── .gitattributes ├── LICENSE ├── README.md └── src └── com └── github └── rackyman └── HHRipper └── HHRipper.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 RackyMan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HHRipper - Fap Them All 2 | 3 | A Hentai Downloader for [hentaihaven.org](http://hentaihaven.org) 4 | 5 | ## Requirements 6 | + Install the latest Java from [here](http://www.oracle.com/technetwork/java/javase/downloads/index.html). 7 | + Make sure that java is in your system path. [Tutorial](https://stackoverflow.com/questions/1672281/environment-variables-for-java-installation) 8 | + Download the latest release from [here](https://github.com/rackyman/HHRipper/releases). 9 | 10 | ## Usage 11 | *After downloading the latest release* - 12 | **Windows User** - *Use the Launcher.bat file to launch the program.* 13 | **Unix User** - *Use the Launcher.sh file to launch the program.* 14 | 15 | **There are two options in the app.** 16 | 1. Let's you paste multiple links of series/episodes you wanna download. 17 | 2. Starts downloads *from the links found* **_in the list.txt file_** 18 | 19 | ## Settings 20 | By default the app *saves* the *best* download link of a video *in downloads.txt file*. 21 | 22 | 23 | **You can change the app settings in the app.properties file**. 24 | The following properties can be changed : 25 | ```python 26 | # Download videos in the most preferred quality 27 | # VALUES-1080,720,480,360,240 [Use , for MULTIPLE VALUES] 28 | # REMEMBER The preference is in descending order 29 | quality=1080,720,480,360,240 30 | 31 | # Show only title and download URL/Link 32 | # VALUES-true,false [DEFAULT-false] 33 | #true - Videos will be downloaded by the app 34 | #false - Videos won't be downloaded, instead links will be STORED in "downloads.txt" file 35 | download=false 36 | 37 | # What to do when a episode is ALREADY Downloaded 38 | # VALUES-force,skip,prompt [DEFAULT-prompt] 39 | #force - Force download video 40 | #skip- Skips the download 41 | #prompt - Ask for user input 42 | overwrite=prompt 43 | 44 | # Folder where the downloads will be placed [DEFAULT- downloads/] 45 | # PASTE CUSTOM FOLDER PATH TO DOWNLOAD THERE 46 | #eg- 'downloadLocation=D:/Downloads/fap' [WITHOUT THE 's] 47 | downloadLocation=downloads/ 48 | ``` 49 | *If you mess up the settings, just delete the app.properties file and run the program to create a default properties file.* 50 | 51 | ## Contributing 52 | + Error and Bug Reports are greatly appreciated. 53 | + Feel free to suggest new ideas which can be implemented. 54 | 55 | ## Acknowledgement 56 | + [jsoup](https://github.com/jhy/jsoup) - *for html parsing* 57 | + [commons-io](https://github.com/apache/commons-io) - *for downloading files* 58 | 59 | ## License 60 | ```MIT LICENSE``` 61 | -------------------------------------------------------------------------------- /src/com/github/rackyman/HHRipper/HHRipper.java: -------------------------------------------------------------------------------- 1 | package com.github.rackyman.HHRipper; 2 | 3 | import java.io.*; 4 | import java.net.URL; 5 | import java.net.URLConnection; 6 | import java.util.Properties; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.apache.commons.io.FileUtils; 10 | import org.apache.commons.io.IOUtils; 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.nodes.Element; 14 | import org.jsoup.select.Elements; 15 | 16 | class HHRipper{ 17 | final static String version ="1.0"; 18 | static String[] quality = {"1080","720","480","360","240"}; 19 | static boolean download = false; 20 | static boolean noSpaces = false; 21 | static String overwrite = "prompt"; 22 | static String downloadLocation = "downloads/"; 23 | 24 | static File settings = new File("app.properties"); 25 | static File list = new File("list.txt"); 26 | static File downloads = new File("downloads.txt"); 27 | 28 | public static void main(String[] a){ 29 | try{ 30 | BufferedReader rc = new BufferedReader(new InputStreamReader(System.in)); 31 | System.out.println("HENTAI HAVEN RIPPER - v"+version+"\n"); 32 | getSettings(); 33 | if(!download) 34 | System.out.println("DOWNLOADS ARE TURNED OFF. Download links will be saved in "+downloads.getName()+"\n"); 35 | System.out.print("\n1.Enter Link(s)\n2.Download from list.txt\n\nEnter your choice >>>>"); 36 | switch(Integer.parseInt(rc.readLine())) { 37 | case 1: 38 | System.out.print("\nEnter Episode/Series Link [ Use , for multiple input] >>>> "); 39 | String links[] = rc.readLine().split(","); 40 | for(String link : links) 41 | extract(link); 42 | break; 43 | case 2: 44 | if(list.exists()) { 45 | BufferedReader br = new BufferedReader(new FileReader(list)); 46 | String n=""; 47 | while((n=br.readLine())!=null) 48 | extract(n); 49 | br.close(); 50 | } 51 | else { 52 | FileWriter fw = new FileWriter(list); 53 | fw.flush(); 54 | fw.close(); 55 | System.out.println("List is empty. Please add some links and try again."); 56 | } 57 | break; 58 | default : 59 | System.out.println("INAVLID CHOICE PLEASE TRY AGAIN !!!!"); 60 | } 61 | }catch(Exception e){ 62 | System.out.println("Error at Main >>>>"+e); 63 | } 64 | } 65 | 66 | public static void extract(String link){ 67 | try{ 68 | System.out.println("\nLINK -> "+link); 69 | if(link.contains("hentaihaven")){ 70 | if(link.contains("series")) 71 | extract_series(link.trim()); 72 | else if(link.contains("episode")) 73 | extract_episode(link.trim()); 74 | else 75 | System.out.println("Not a valid Link. ONLY SERIES & EPISODES ARE SUPPORTED FOR NOW !!!!"); 76 | } 77 | else 78 | System.out.println("Not a HentaiHaven Link !!!!"); 79 | }catch(Exception e){ 80 | System.out.println("Error at extract >>>> "+e); 81 | } 82 | } 83 | 84 | public static void extract_series(String link){ 85 | try{ 86 | Document doc = Jsoup.connect(link).get(); 87 | String title = doc.title().contains("|") ? doc.title().split("\\|")[0].trim() : doc.title().trim(); 88 | System.out.println("Series -> "+title+"\n"); 89 | //System.out.println("Fetching episodes...\n"); 90 | Elements episodes = doc.select("a.brick-title"); 91 | for(Element episode : episodes) 92 | //System.out.println(episode.text()+" >>>>>>> "+episode.attr("href")); 93 | extract_episode(episode.attr("href")); 94 | }catch(Exception e){ 95 | System.out.println("Error at extract_series >>>> "+e); 96 | } 97 | } 98 | 99 | public static void extract_episode(String link){ 100 | try{ 101 | Document doc = Jsoup.connect(link).get(); 102 | String title = doc.title().contains("|") ? doc.title().split("\\|")[0].trim() : doc.title().trim(); 103 | System.out.println("Episode -> "+title); 104 | //System.out.println(doc.toString()); 105 | Elements links = doc.select("a.btn[href*='hh.cx']"); 106 | String dl_link = null; 107 | String dl_quality = null; 108 | mloop: 109 | for(Element l : links) 110 | if(l.attr("href").contains("hh.cx")){ 111 | dl_quality = l.text(); 112 | for(String q:quality) 113 | if(dl_quality.contains(q)) { 114 | dl_link = l.attr("href"); 115 | break mloop; 116 | } 117 | } 118 | if(dl_link == null) 119 | System.out.println("Can't download. Specified Quality is Absent."); 120 | else { 121 | System.out.println(dl_quality+" -> "+dl_link); 122 | 123 | if(download){ 124 | File video = new File(downloadLocation+title+".mp4"); 125 | if(video.exists()) { 126 | if(overwrite.equals("skip")) 127 | System.out.println("FILE ALREADY DOWNLOADED. SKIPPED"); 128 | else if(overwrite.equals("force")) { 129 | System.out.println("REDOWNLOADING FILE"); 130 | copyURLToFile(new URL(dl_link), video); 131 | } 132 | else if(overwrite.equals("prompt")) { 133 | BufferedReader rc = new BufferedReader(new InputStreamReader(System.in)); 134 | System.out.print("File is already downloaded. REDOWNLOAD (Y/N) ?"); 135 | if(rc.readLine().toLowerCase().trim().equals("y")) { 136 | System.out.println("REDOWNLOADING FILE"); 137 | copyURLToFile(new URL(dl_link), video); 138 | } 139 | else 140 | System.out.println("DOWNLOAD SKIPPED."); 141 | } 142 | } 143 | else 144 | copyURLToFile(new URL(dl_link), video); 145 | } 146 | else { 147 | BufferedWriter fw = new BufferedWriter(new FileWriter(downloads,true)); 148 | fw.write(dl_link); 149 | fw.newLine(); 150 | fw.flush(); 151 | fw.close(); 152 | } 153 | 154 | } 155 | }catch(Exception e){ 156 | System.out.println("Error at extract_episode >>>> "+e); 157 | } 158 | } 159 | public static void copyURLToFile(final URL url, final File destination) throws IOException { 160 | Long start=0l; 161 | Long end=0l; 162 | final int EOF = -1; 163 | final int DEFAULT_BUFFER_SIZE = 1024 * 4; 164 | 165 | //System.out.println(destination.getAbsolutePath()); 166 | final InputStream source=url.openStream(); 167 | try { 168 | start=System.nanoTime(); 169 | URLConnection urlConnection = url.openConnection(); 170 | urlConnection.connect(); 171 | int fileSize = urlConnection.getContentLength(); 172 | System.out.println("File Size : " 173 | +(fileSize/1048576) 174 | +" MB"); 175 | 176 | final FileOutputStream output = FileUtils.openOutputStream(destination); 177 | try { 178 | final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 179 | long count = 0; 180 | int n = 0; 181 | while (EOF != (n = source.read(buffer))) { 182 | output.write(buffer, 0, n); 183 | count += n; 184 | System.out.print("Downloading [" 185 | +count*100/fileSize 186 | +"%]\r"); 187 | } 188 | System.out.println("Downloaded [" 189 | +count*100/fileSize 190 | +"%]\r"); 191 | 192 | output.close(); // don't swallow close Exception if copy completes normally 193 | end=System.nanoTime(); 194 | System.out.println("Total time taken = " + TimeUnit.SECONDS.convert((end-start), TimeUnit.NANOSECONDS)+"s"); 195 | } 196 | catch(Exception e1){ 197 | System.out.println(e1.getMessage()); 198 | 199 | } 200 | finally { 201 | IOUtils.closeQuietly(output); 202 | } 203 | 204 | } 205 | catch(Exception e){ 206 | System.out.println(e.getMessage()); 207 | } 208 | finally { 209 | IOUtils.closeQuietly(source); 210 | } 211 | } 212 | public static void getSettings(){ 213 | try { 214 | if(settings.exists()) { 215 | Properties prop = new Properties(); 216 | prop.load(new FileReader(settings)); 217 | 218 | download = prop.getProperty("download").trim().matches("false|true") ? Boolean.parseBoolean(prop.getProperty("download").trim()) : download ; 219 | noSpaces = prop.getProperty("noSpaces").trim().matches("false|true") ? Boolean.parseBoolean(prop.getProperty("noSpaces").trim()) : noSpaces ; 220 | overwrite = prop.getProperty("overwrite").trim().matches("skip|force|prompt") ? prop.getProperty("overwrite").trim() : overwrite ; 221 | downloadLocation = prop.getProperty("downloadLocation").trim() !=null ? prop.getProperty("downloadLocation").trim() : downloadLocation ; 222 | quality = prop.getProperty("quality").trim() !=null ? prop.getProperty("quality").trim().split(",") : quality ; 223 | 224 | if(downloadLocation.charAt(downloadLocation.length()) != '/'); 225 | downloadLocation+="/"; 226 | 227 | System.out.println("Successfully loaded Settings"); 228 | } 229 | else 230 | createSettings(); 231 | }catch(Exception e) { 232 | System.out.println("Error at getSettings "+e); 233 | } 234 | 235 | } 236 | public static void createSettings()throws IOException { 237 | System.out.println("Settings file not found. Creating new Settings File."); 238 | BufferedWriter bw = new BufferedWriter(new FileWriter(settings)); 239 | bw.write("################################### HHRipper SETTINGS ###################################\r\n" + 240 | "# HENTAI HAVEN RIPPER - Fap Them All !\r\n" + 241 | "# Author - rackyman\r\n" + 242 | "# Version - 1.0\r\n" + 243 | "\r\n" + 244 | "\r\n" + 245 | "# If you mess up here just delete the file, and a new file with default settings will be created\r\n" + 246 | "# Only use the supported values given below\r\n" + 247 | "# For invalid values, deafault values will be used\r\n" + 248 | "\r\n" + 249 | "\r\n" + 250 | "\r\n" + 251 | "\r\n" + 252 | "# Download videos in the most preferred quality\r\n" + 253 | "# VALUES-1080,720,480,360,240\r\n" + 254 | "# Use , for MULTIPLE VALUES\r\n" + 255 | "# REMEMBER The prefernce is in descending order\r\n" + 256 | "quality=1080,720,480,360,240\r\n" + 257 | "\r\n" + 258 | "# Show only title and download URL/Link\r\n" + 259 | "# VALUES-true,false [DEFAULT-false]\r\n" + 260 | " #true - Videos will be downloaded by the app\r\n" + 261 | " #false - Videos won't be downloaded, instead links will be STORED in \"downloads.txt\" file\r\n" + 262 | "download=false\r\n" + 263 | "\r\n" + 264 | "# Replace SPACEs in name with UNDERSCORE\r\n" + 265 | "# VALUES-true,false [DEFAULT-false]\r\n" + 266 | "noSpaces=false\r\n" + 267 | "\r\n" + 268 | "# What to do when a episode is ALREADY Downloaded\r\n" + 269 | "# VALUES-force,skip,prompt [DEFAULT-prompt]\r\n" + 270 | " #force - Force download video\r\n" + 271 | " #skip- Skips the download\r\n" + 272 | " #prompt - Ask for user input\r\n" + 273 | "overwrite=prompt\r\n" + 274 | "\r\n" + 275 | "# Folder where the downloads will be placed [DEFAULT- downloads/]\r\n" + 276 | " #PASTE CUSTOM FOLDER PATH TO DOWNLOAD THERE\r\n" + 277 | " #eg- 'downloadLocation=D:/Downloads/fap' [WITHOUT THE 's]\r\n" + 278 | "downloadLocation=downloads/"); 279 | bw.flush(); 280 | bw.close(); 281 | getSettings(); 282 | } 283 | } 284 | --------------------------------------------------------------------------------