├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── ReadMe.md └── src ├── Comic.png ├── ComicCore ├── Chapter.java ├── Comic.java ├── ComicInfo.java └── comicfactory.java ├── DownLoad ├── DLControl.java └── DLThread.java ├── Start ├── LOG.java └── Start.java ├── UI ├── ButtonAnaly.java ├── ButtonDownLoad.java ├── ButtonFilePath.java ├── CheckBoxAll.java ├── FrameComic.java ├── PanelControl.java ├── SelectThread.java ├── SelectWebSite.java ├── TextComicID.java ├── TextFileText.java └── UIConfig.java ├── config.js └── manhuagui ├── DlManhuagui.java └── manhuaguiCfg.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | GetComic 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/ComicCore/Chapter.java=UTF-8 3 | encoding//src/DownLoad/DLControl.java=UTF-8 4 | encoding//src/DownLoad/DLThread.java=UTF-8 5 | encoding//src/Start/LOG.java=UTF-8 6 | encoding//src/Start/Start.java=UTF-8 7 | encoding//src/UI/ButtonAnaly.java=UTF-8 8 | encoding//src/UI/ButtonDownLoad.java=UTF-8 9 | encoding//src/UI/ButtonFilePath.java=UTF-8 10 | encoding//src/UI/CheckBoxAll.java=UTF-8 11 | encoding//src/UI/FrameComic.java=UTF-8 12 | encoding//src/UI/PanelControl.java=UTF-8 13 | encoding//src/UI/SelectThread.java=UTF-8 14 | encoding//src/UI/TextComicID.java=UTF-8 15 | encoding//src/UI/TextFileText.java=UTF-8 16 | encoding//src/UI/UIConfig.java=UTF-8 17 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | #增加网站的方法 2 | 在使用工厂模式重构代码后,软件已经基本具备可扩展性,如果需要增加一个新的下载漫画的网址,需要做如下的事情: 3 | 1)实现ComicCore/Comic接口中的三个方法。可以参考已有实现DlManhuagui; 4 | 2)在UI/UIConfig中依照注释样例增加一个网址名; 5 | 当然,你也可以使用LOG.log(String)来给自己的类添加打印信息以方便调试,这个打印信息会自动保存到当前程序下的Comiclog.txt文本中。 6 | -------------------------------------------------------------------------------- /src/Comic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiguiwoxin/GetComic/c38bdf9e28ca1c7f0b20586b7fe4dd55da29b8a5/src/Comic.png -------------------------------------------------------------------------------- /src/ComicCore/Chapter.java: -------------------------------------------------------------------------------- 1 | package ComicCore; 2 | 3 | public class Chapter{ 4 | private String Title = null; 5 | private String Html = null; 6 | 7 | public Chapter(String Title, String Html) 8 | { 9 | this.Title = Title; 10 | this.Html = Html; 11 | } 12 | 13 | public String getTitle() { 14 | return Title; 15 | } 16 | 17 | public String getHtml() { 18 | return Html; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ComicCore/Comic.java: -------------------------------------------------------------------------------- 1 | package ComicCore; 2 | 3 | import java.util.ArrayList; 4 | /**************************** 5 | * 对于漫画下载软件来说: 6 | * 1)获取到漫画的章节信息,可以用于显示在界面上让用户选择哪些章节 7 | * 2)获取每个章节中的漫画图片地址,按照地址进行下载即可 8 | * 3) 获取漫画书名,用于保存时用作保存文件夹名字 9 | * 因此抽象出这两个方法作为接口,可以认为任何类只要实现了这两个方法,就都可以进行漫画下载了 10 | ****************************/ 11 | public interface Comic{ 12 | public ArrayList GetChapetr(String ComicId); 13 | public ArrayList GetPicUrlByChapter(String ChapterUrl); 14 | public String GetBookName(); 15 | } -------------------------------------------------------------------------------- /src/ComicCore/ComicInfo.java: -------------------------------------------------------------------------------- 1 | package ComicCore; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.net.URL; 11 | import java.net.URLConnection; 12 | import java.net.URLEncoder; 13 | import java.security.SecureRandom; 14 | import java.security.cert.CertificateException; 15 | import java.security.cert.X509Certificate; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | import javax.net.ssl.HttpsURLConnection; 20 | import javax.net.ssl.SSLContext; 21 | import javax.net.ssl.TrustManager; 22 | import javax.net.ssl.X509TrustManager; 23 | 24 | import Start.LOG; 25 | /****************************** 26 | * 在抽象类中增加2个公共方法: 27 | * 1)获取网页信息的方法,这在获取章节和图片地址时必然会用到 28 | * 2)保存图片的方法,这在下载图片的时候也必然会用到 29 | ******************************/ 30 | public abstract class ComicInfo implements Comic{ 31 | protected String BookName = null; 32 | 33 | public String GetBookName() 34 | { 35 | return BookName; 36 | } 37 | 38 | public String GetHtmlInfo(String UrlAdd) 39 | { 40 | String HttpInfo = null; 41 | if(null == UrlAdd) return null; 42 | String line; 43 | URL url; 44 | int trytime = 5; 45 | 46 | while(trytime > 0) 47 | { 48 | try { 49 | url = new URL(UrlAdd); 50 | URLConnection urlcon = url.openConnection(); 51 | if(UrlAdd.startsWith("https")) 52 | { 53 | trustall((HttpsURLConnection) urlcon); 54 | } 55 | 56 | urlcon.setRequestProperty("accept", "*/*"); 57 | urlcon.setRequestProperty("connection", "Keep-Alive"); 58 | urlcon.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"); 59 | urlcon.setConnectTimeout(10 * 1000); 60 | urlcon.setReadTimeout(10 * 1000); 61 | urlcon.connect(); 62 | BufferedReader urlRead = new BufferedReader(new InputStreamReader(urlcon.getInputStream(), "utf-8")); 63 | while((line = urlRead.readLine()) != null) 64 | { 65 | HttpInfo += line; 66 | } 67 | urlRead.close(); 68 | return HttpInfo; 69 | } catch (Exception e) { 70 | trytime --; 71 | LOG.log(String.format("%s倒数第%d次重连,信息%s", UrlAdd, trytime, e.getMessage())); 72 | continue; 73 | } 74 | } 75 | 76 | if(HttpInfo == null) 77 | { 78 | LOG.log("获取网址信息错误:" + UrlAdd); 79 | } 80 | 81 | return HttpInfo; 82 | } 83 | 84 | private static void trustall(HttpsURLConnection con) 85 | { 86 | TrustManager[] trustallcert = {new X509TrustManager() 87 | { 88 | @Override 89 | public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 90 | } 91 | 92 | @Override 93 | public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 94 | } 95 | 96 | @Override 97 | public X509Certificate[] getAcceptedIssuers() { 98 | return null; 99 | }}}; 100 | SSLContext ssl; 101 | try { 102 | ssl = SSLContext.getInstance("SSL"); 103 | ssl.init(null, trustallcert, new SecureRandom()); 104 | con.setSSLSocketFactory(ssl.getSocketFactory()); 105 | } catch (Exception e) { 106 | e.printStackTrace(); 107 | } 108 | 109 | } 110 | 111 | public static int SavePicture(String UrlAdd, String Path, String FileName, String refer) 112 | { 113 | if(null == Path || null == FileName) return 0; 114 | //提前判断文件是否存在,如果存在直接跳过下载,加快续传速度 115 | File file = new File(Path + FileName); 116 | if(file.exists()) 117 | { 118 | return 1; 119 | } 120 | 121 | byte[] imgData = getImgData(UrlAdd, refer); 122 | if(null == imgData) return 0; 123 | 124 | FileOutputStream fop = null; 125 | 126 | try { 127 | try { 128 | fop = new FileOutputStream(file); 129 | fop.write(imgData); 130 | fop.flush(); 131 | } catch (Exception e) { 132 | e.printStackTrace(); 133 | LOG.log(e.getMessage()); 134 | return 0; 135 | } 136 | finally 137 | { 138 | fop.close(); 139 | } 140 | } 141 | catch(Exception e) 142 | { 143 | LOG.log(e.getMessage()); 144 | e.printStackTrace(); 145 | } 146 | 147 | return 1; 148 | } 149 | //获取图片的二进制流 150 | private static byte[] getImgData(String UrlAdd, String refer) 151 | { 152 | byte[] ImgData = null; 153 | //如果连接失败,有5次重连机会 154 | int ConnectTime = 5; 155 | if(null == UrlAdd) return null; 156 | 157 | while(ConnectTime > 0) 158 | { 159 | try { 160 | UrlAdd = ConvertURLAddToChar(UrlAdd, "utf-8"); 161 | URL url = new URL(UrlAdd); 162 | URLConnection urlcon = url.openConnection(); 163 | if(UrlAdd.startsWith("https")) 164 | { 165 | trustall((HttpsURLConnection)urlcon); 166 | } 167 | urlcon.setRequestProperty("Referer", refer); 168 | urlcon.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"); 169 | urlcon.setConnectTimeout(15 * 1000); 170 | urlcon.setReadTimeout(15 * 1000); 171 | 172 | ImgData = GetbyteFromStream(urlcon.getInputStream()); 173 | return ImgData; 174 | } catch (Exception e) { 175 | e.printStackTrace(); 176 | LOG.log(String.format("%s倒数第%d次重连,信息%s", UrlAdd, ConnectTime, e.getMessage())); 177 | ConnectTime --; 178 | continue; 179 | } 180 | } 181 | 182 | return null; 183 | } 184 | //将图片的Http流转化为二进制流 185 | private static byte[] GetbyteFromStream(InputStream in) throws IOException 186 | { 187 | int len; 188 | 189 | byte[] buffer = new byte[3 * 100 * 1024]; 190 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 191 | while((len = in.read(buffer)) != -1) 192 | { 193 | out.write(buffer, 0 ,len); 194 | } 195 | 196 | byte[] imgData = out.toByteArray(); 197 | in.close(); 198 | out.close(); 199 | 200 | return imgData; 201 | } 202 | //将链接中可能的中文进行编码 203 | private static String ConvertURLAddToChar(String UrlAdd,String Charset) 204 | { 205 | try 206 | { 207 | Matcher matcher = Pattern.compile("[\\u4e00-\\u9fa5]").matcher(UrlAdd); 208 | while (matcher.find()) 209 | { 210 | String CHNWord=matcher.group(); 211 | UrlAdd=UrlAdd.replaceAll(CHNWord, URLEncoder.encode(CHNWord,Charset)); 212 | } 213 | } 214 | catch (Exception e) 215 | { 216 | e.printStackTrace(); 217 | LOG.log(String.format("encode string[%s] fail with[%s]", UrlAdd, Charset)); 218 | } 219 | 220 | return UrlAdd; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/ComicCore/comicfactory.java: -------------------------------------------------------------------------------- 1 | package ComicCore; 2 | 3 | import Start.LOG; 4 | 5 | public class comicfactory { 6 | @SuppressWarnings("unchecked") 7 | public Comic create(String package_addr) 8 | { 9 | Comic getcomic = null; 10 | Class cls = null; 11 | try { 12 | cls = (Class) Class.forName(package_addr); 13 | getcomic = (Comic)cls.newInstance(); 14 | } catch (ClassNotFoundException e) { 15 | LOG.log("comicfactory:找不到指定的类:" + package_addr); 16 | } catch (InstantiationException e) { 17 | LOG.log("comicfactory:没有默认构造器:" + package_addr); 18 | } catch (IllegalAccessException e) { 19 | LOG.log("comicfactory:没有默认构造器:" + package_addr); 20 | } 21 | return getcomic; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/DownLoad/DLControl.java: -------------------------------------------------------------------------------- 1 | package DownLoad; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | import javax.swing.JOptionPane; 9 | 10 | import ComicCore.Chapter; 11 | import ComicCore.Comic; 12 | import ComicCore.comicfactory; 13 | import UI.FrameComic; 14 | //这个类为下载控制中心,主要用于与面板的通信和下载线程的控制 15 | public class DLControl { 16 | public static volatile boolean RunThread = true; 17 | 18 | private String ComicId = null; 19 | private String package_addr = null; 20 | private int PoolSize = 0; 21 | private String FilePath = null; 22 | private FrameComic fc = null; 23 | private ExecutorService fixpool = null; 24 | private String BookName = null; 25 | private Comic getComic = null; 26 | 27 | //获取网页的章节 28 | public boolean AnalyChapter() 29 | { 30 | if(null == ComicId) return false; 31 | getComic = new comicfactory().create(package_addr); 32 | if(null == getComic) return false; 33 | ArrayList Chapters = getComic.GetChapetr(ComicId); 34 | if(null == Chapters) return false; 35 | BookName = getComic.GetBookName(); 36 | if (BookName == null) BookName = "default"; 37 | 38 | if(Chapters.isEmpty()) 39 | { 40 | return false; 41 | } 42 | fc.addChapters(Chapters); 43 | return true; 44 | } 45 | 46 | public void InterrputDL() 47 | { 48 | if(null != fixpool) 49 | { 50 | fixpool.shutdownNow(); 51 | DLControl.RunThread = false; 52 | } 53 | fc.InterrputDL(); 54 | } 55 | 56 | public DLControl(FrameComic fc) 57 | { 58 | this.fc = fc; 59 | } 60 | 61 | public boolean StartDL(int poolsize) 62 | { 63 | ArrayList Chapters = fc.getDLInfo(); 64 | if(fixpool == null) 65 | { 66 | PoolSize = poolsize; 67 | fixpool = Executors.newFixedThreadPool(PoolSize); 68 | } 69 | else if(fixpool != null) 70 | { 71 | PoolSize = poolsize; 72 | fixpool.shutdownNow(); 73 | fixpool = Executors.newFixedThreadPool(PoolSize); 74 | } 75 | 76 | if(Chapters.isEmpty()) 77 | { 78 | JOptionPane.showMessageDialog(null, "没有任何选择章节", "错误说明", JOptionPane.CLOSED_OPTION); 79 | return false; 80 | } 81 | 82 | if(ThreadControl(Chapters)) 83 | { 84 | fc.disableAllbox(); 85 | return true; 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public boolean ThreadControl(ArrayList Chapters) 92 | { 93 | File HeadDir = new File(FilePath + "/" + BookName + "(" + ComicId + ")"); 94 | 95 | if(!(HeadDir.exists() && HeadDir.isDirectory())) 96 | { 97 | if(!HeadDir.mkdirs()) 98 | { 99 | JOptionPane.showMessageDialog(null, "创建初始文件夹失败,请检查磁盘是否已满/地址错误:"+ HeadDir.getAbsolutePath(), "错误说明", JOptionPane.CLOSED_OPTION); 100 | return false; 101 | } 102 | } 103 | 104 | for(Chapter c : Chapters) 105 | { 106 | DLThread dl = new DLThread(c, HeadDir.getAbsolutePath(), getComic); 107 | dl.setFC(fc); 108 | fixpool.execute(dl); 109 | } 110 | 111 | return true; 112 | } 113 | 114 | public void setComicId(String ComicId) { 115 | this.ComicId = ComicId; 116 | } 117 | 118 | public void setFilePath(String filePath) { 119 | FilePath = filePath; 120 | } 121 | 122 | public void setpackage_addr(String package_addr) { 123 | this.package_addr = package_addr; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/DownLoad/DLThread.java: -------------------------------------------------------------------------------- 1 | package DownLoad; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | 6 | import ComicCore.Chapter; 7 | import ComicCore.Comic; 8 | import ComicCore.ComicInfo; 9 | import Start.LOG; 10 | import UI.FrameComic; 11 | 12 | public class DLThread implements Runnable{ 13 | private String html = null; 14 | private String title = null; 15 | private String path = null; 16 | private FrameComic fc = null; 17 | private int len = 0; 18 | private Chapter chapter = null; 19 | private Comic getComic = null; 20 | 21 | public DLThread(Chapter chapter,String path, Comic getComic) 22 | { 23 | this.chapter = chapter; 24 | this.html = chapter.getHtml(); 25 | this.title = chapter.getTitle(); 26 | this.path = path; 27 | this.getComic = getComic; 28 | processTitle(); 29 | } 30 | 31 | public void setFC(FrameComic fc) 32 | { 33 | this.fc = fc; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | 39 | if(!DLControl.RunThread) 40 | { 41 | return; 42 | } 43 | 44 | File dir = new File(path + "/" + title); 45 | if(!(dir.exists() && dir.isDirectory())) 46 | { 47 | if(!dir.mkdir()) 48 | { 49 | LOG.log("创建章节文件夹失败,请检查磁盘是否已满/地址错误:" + dir.getAbsolutePath()); 50 | return; 51 | } 52 | } 53 | 54 | ArrayList PicPath = getComic.GetPicUrlByChapter(html); 55 | if(PicPath == null) 56 | { 57 | fc.FinishDl(chapter, 0); 58 | return; 59 | } 60 | else if(PicPath.isEmpty()) 61 | { 62 | LOG.log("解析图片地址失败:" + html); 63 | fc.FinishDl(chapter, 0); 64 | return; 65 | } 66 | 67 | len = PicPath.size(); 68 | int numlen = getNumLen(len); 69 | int index = 1; 70 | int result = 0; 71 | 72 | for(String path : PicPath) 73 | { 74 | fc.UpdateDLinfo(chapter, index, len); 75 | result = ComicInfo.SavePicture(path, dir.getAbsolutePath() + "/", String.format("%0" + numlen + "d", index) + ".jpg", html); 76 | 77 | if(!DLControl.RunThread) 78 | { 79 | return; 80 | } 81 | 82 | if(0 == result) 83 | { 84 | LOG.log("下载图片地址失败:" + path); 85 | fc.FinishDl(chapter, 0); 86 | return; 87 | } 88 | 89 | index ++; 90 | } 91 | 92 | fc.FinishDl(chapter, 1); 93 | } 94 | //一些带奇怪符号的名称可能会导致创建文件夹失败,所以需要进行预处理 95 | private void processTitle() 96 | { 97 | title = title.replaceAll("[\\^%&',.;=?$]+", ""); 98 | } 99 | 100 | private int getNumLen(int num) 101 | { 102 | int len = 0; 103 | 104 | do 105 | { 106 | num = num /10; 107 | len ++; 108 | } 109 | while(num != 0); 110 | 111 | return len; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Start/LOG.java: -------------------------------------------------------------------------------- 1 | package Start; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.util.Date; 7 | 8 | import javax.swing.JOptionPane; 9 | 10 | public class LOG { 11 | 12 | private static StringBuffer logbuff = new StringBuffer(); 13 | private static int lognum = 0; 14 | 15 | static 16 | { 17 | loginDate(); 18 | } 19 | 20 | public static synchronized void log(String str) 21 | { 22 | lognum ++; 23 | logbuff = logbuff.append(String.format("%tT:%s\r\n", new Date(), str)); 24 | 25 | if(lognum > 20 || str.length() > 1024) 26 | { 27 | logintofile(); 28 | } 29 | } 30 | 31 | private static void loginDate() 32 | { 33 | logbuff = logbuff.append(String.format("%1$tF %1$tT:START!!!\r\n", new Date())); 34 | logintofile(); 35 | } 36 | 37 | public static synchronized void logintofile() 38 | { 39 | if(logbuff.length() <= 0) return; 40 | File file = new File("./Comiclog.txt"); 41 | try { 42 | FileWriter fw = new FileWriter(file, true); 43 | fw.write(logbuff.toString()); 44 | logbuff.setLength(0);; 45 | lognum = 0; 46 | fw.close(); 47 | } catch (IOException e) { 48 | JOptionPane.showMessageDialog(null, "写入日志错误,请关闭日志文件", "错误", JOptionPane.CLOSED_OPTION); 49 | return; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Start/Start.java: -------------------------------------------------------------------------------- 1 | package Start; 2 | 3 | import java.util.Timer; 4 | import java.util.TimerTask; 5 | 6 | import DownLoad.DLControl; 7 | import UI.FrameComic; 8 | import UI.PanelControl; 9 | 10 | public class Start { 11 | 12 | public static void main(String[] args) throws InterruptedException 13 | { 14 | PanelControl pc = new PanelControl(); 15 | FrameComic fc = new FrameComic(pc); 16 | DLControl dlc = new DLControl(fc); 17 | pc.setDlc(dlc); 18 | fc.setVisible(true); 19 | Timer logtime = new Timer(); 20 | logtime.schedule(new TimerTask() 21 | { 22 | @Override 23 | public void run() { 24 | LOG.logintofile();; 25 | } 26 | }, 27 | 1, 2 * 1000); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/UI/ButtonAnaly.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.event.MouseAdapter; 4 | import java.awt.event.MouseEvent; 5 | 6 | import javax.swing.JButton; 7 | 8 | @SuppressWarnings("serial") 9 | public class ButtonAnaly extends JButton{ 10 | private PanelControl pc= null; 11 | 12 | public void setControl(PanelControl pc) 13 | { 14 | this.pc = pc; 15 | } 16 | 17 | public ButtonAnaly() 18 | { 19 | this.setText("获取章节"); 20 | this.addMouseListener(new MouseAdapter() 21 | { 22 | @Override 23 | public void mousePressed(MouseEvent e) 24 | { 25 | if(ButtonAnaly.this.isEnabled()) 26 | { 27 | ButtonAnaly.this.setText("获取中...."); 28 | ButtonAnaly.this.setEnabled(false); 29 | ButtonAnaly.this.paintImmediately(0, 0, ButtonAnaly.this.getWidth(), ButtonAnaly.this.getHeight()); 30 | pc.StartAnaly(ButtonAnaly.this); 31 | } 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/UI/ButtonDownLoad.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.event.MouseAdapter; 4 | import java.awt.event.MouseEvent; 5 | 6 | import javax.swing.JButton; 7 | import javax.swing.JOptionPane; 8 | 9 | import DownLoad.DLControl; 10 | 11 | @SuppressWarnings("serial") 12 | public class ButtonDownLoad extends JButton{ 13 | private boolean DLState = false; 14 | private PanelControl pc= null; 15 | 16 | public void setControl(PanelControl pc) 17 | { 18 | this.pc = pc; 19 | } 20 | 21 | public ButtonDownLoad() 22 | { 23 | this.setText("下载"); 24 | this.setEnabled(false); 25 | this.addMouseListener(new MouseAdapter() 26 | { 27 | @Override 28 | public void mousePressed(MouseEvent e) 29 | { 30 | if(ButtonDownLoad.this.isEnabled() && ButtonDownLoad.this.getText().equals("下载")) 31 | { 32 | ButtonDownLoad.this.setText("中断"); 33 | DLState = true; 34 | DLControl.RunThread = true; 35 | boolean result = pc.StartDL(ButtonDownLoad.this); 36 | if(false == result) 37 | { 38 | ButtonDownLoad.this.setEnabled(true); 39 | ButtonDownLoad.this.setText("下载"); 40 | DLState = false; 41 | } 42 | } 43 | else if(!ButtonDownLoad.this.isEnabled() && ButtonDownLoad.this.getText().equals("下载")) 44 | { 45 | JOptionPane.showMessageDialog(null, "请先分析以获取章节信息", "错误说明", JOptionPane.CLOSED_OPTION); 46 | } 47 | else if(!ButtonDownLoad.this.isEnabled() && ButtonDownLoad.this.getText().equals("下载中")) 48 | { 49 | JOptionPane.showMessageDialog(null, "就算你再怎么点,也没办法加快下载速度呀-_-!", "错误说明", JOptionPane.CLOSED_OPTION); 50 | } 51 | else if(ButtonDownLoad.this.getText().equals("中断")) 52 | { 53 | pc.InterrputDL(); 54 | } 55 | } 56 | }); 57 | } 58 | 59 | public boolean getDLState() { 60 | return DLState; 61 | } 62 | 63 | public void resetDLStat() 64 | { 65 | this.DLState = false; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/UI/ButtonFilePath.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.event.MouseAdapter; 4 | import java.awt.event.MouseEvent; 5 | import java.io.File; 6 | 7 | import javax.swing.JButton; 8 | import javax.swing.JFileChooser; 9 | 10 | @SuppressWarnings("serial") 11 | public class ButtonFilePath extends JButton{ 12 | private PanelControl pc= null; 13 | 14 | public void setControl(PanelControl pc) 15 | { 16 | this.pc = pc; 17 | } 18 | 19 | public ButtonFilePath() 20 | { 21 | this.setText("选择"); 22 | this.setBounds(392, 0, 93, 30); 23 | this.addMouseListener(new MouseAdapter() 24 | { 25 | @Override 26 | public void mousePressed(MouseEvent e) 27 | { 28 | if (!ButtonFilePath.this.isEnabled()) return; 29 | JFileChooser chooser = new JFileChooser(); 30 | chooser.setCurrentDirectory(new File(pc.getFilePath())); 31 | chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 32 | chooser.showOpenDialog(null); 33 | if(chooser.getSelectedFile() != null) 34 | { 35 | String SelectedFile = chooser.getSelectedFile().getAbsolutePath(); 36 | pc.setFilePath(SelectedFile); 37 | } 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/UI/CheckBoxAll.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.event.ItemEvent; 4 | import java.awt.event.ItemListener; 5 | 6 | import javax.swing.JCheckBox; 7 | 8 | @SuppressWarnings("serial") 9 | public class CheckBoxAll extends JCheckBox{ 10 | public CheckBoxAll(String text, FrameComic fc) 11 | { 12 | this.setText(text); 13 | this.setSelected(true); 14 | 15 | this.addItemListener(new ItemListener() 16 | { 17 | @Override 18 | public void itemStateChanged(ItemEvent e) { 19 | if(CheckBoxAll.this.getText().equals("全选")) 20 | { 21 | if(ItemEvent.SELECTED == e.getStateChange()) 22 | { 23 | fc.SelectAll(); 24 | } 25 | else 26 | { 27 | fc.DeSelectAll(); 28 | } 29 | } 30 | } 31 | 32 | }); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/UI/FrameComic.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import Start.LOG; 4 | 5 | import java.awt.Color; 6 | import java.awt.Container; 7 | import java.awt.Font; 8 | import java.awt.Toolkit; 9 | import java.awt.event.WindowAdapter; 10 | import java.awt.event.WindowEvent; 11 | import java.util.ArrayList; 12 | import java.util.LinkedHashMap; 13 | import java.util.Set; 14 | 15 | import javax.swing.BoxLayout; 16 | import javax.swing.JCheckBox; 17 | import javax.swing.JFrame; 18 | import javax.swing.JOptionPane; 19 | import javax.swing.JPanel; 20 | import javax.swing.JScrollPane; 21 | import javax.swing.JTextArea; 22 | 23 | import ComicCore.Chapter; 24 | 25 | @SuppressWarnings("serial") 26 | public class FrameComic extends JFrame{ 27 | private JPanel JpScroll = null; 28 | private LinkedHashMap Boxs = new LinkedHashMap(); 29 | CheckBoxAll CheckAll = null; 30 | private int TaskNum = 0; 31 | PanelControl pc = null; 32 | JScrollPane scrollpane = null; 33 | 34 | public FrameComic(PanelControl pc) 35 | { 36 | this.pc = pc; 37 | initLayout(); 38 | } 39 | 40 | private void initLayout() 41 | { 42 | this.setTitle("一键获取漫画工具"); 43 | this.setResizable(false); 44 | //采取如此奇怪的读取方式是为了在JAR中能读取到img 45 | this.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getClassLoader().getResource("Comic.png"))); 46 | this.setSize(UIConfig.ComicW, UIConfig.ComicH); 47 | this.setLocation((UIConfig.WinW - UIConfig.ComicW) / 2, (UIConfig.WinH - UIConfig.ComicH) / 2); 48 | this.addWindowListener(new WindowAdapter() 49 | { 50 | @Override 51 | public void windowClosing(WindowEvent e) 52 | { 53 | LOG.logintofile();; 54 | if(CheckAll != null && CheckAll.isEnabled() == false) 55 | { 56 | int ExitRst = JOptionPane.showConfirmDialog(null, "正在下载中,确认退出?", "确认", JOptionPane.YES_NO_OPTION); 57 | if(JOptionPane.NO_OPTION == ExitRst) 58 | { 59 | return; 60 | } 61 | } 62 | System.exit(0); 63 | } 64 | }); 65 | this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); 66 | Container con = this.getContentPane(); 67 | con.setBackground(new Color(144, 238, 144)); 68 | con.setLayout(null); 69 | con.add(pc); 70 | 71 | setScrollUI(con); 72 | setFileUI(con); 73 | setHelpUI(con); 74 | } 75 | 76 | private void setHelpUI(Container con) 77 | { 78 | JTextArea ta = new JTextArea(); 79 | ta.setEditable(false); 80 | ta.setLineWrap(true); 81 | ta.setFont(new Font("宋体", Font.PLAIN, 15)); 82 | ta.setDisabledTextColor(Color.BLACK); 83 | ta.setText("本软件只能抓取到http://www.manhuagui.com/该网站的漫画数据(此文本框内可以用CTRL+C进行复制)。\n" + 84 | "1.漫画ID是什么?\n" + 85 | "进入http://www.manhuagui.com/网站后,搜索到你想要的漫画,点击进入漫画页面。比如http://www.manhuagui.com/comic/1626/这个漫画,则漫画ID就是1626。\n" 86 | + "另外即使是网页显示因版权而被屏蔽的漫画也可以试一下哦,但切记不要一天之内下载过多此类被禁漫画,很容易被网站后台发现而封IP。\n" + 87 | "2.下载失败后,如何判断是不是被封IP了?如何解决?\n" + 88 | "可以尝试在浏览器上打开http://www.manhuagui.com/,如果发现无法进入,则可以使用你的手机(注意请不要使用和电脑一样的wifi网络,使用你的无线网络或其他网络)尝试登录该网站,如果手机可以进入而电脑不行,则说明确实被封IP了。\n" + 89 | "封IP不要紧张,如果你是拨号上网的用户,那么重新拨号就可以获取到新IP。如果你是光纤用户,则需要断电重启一下光钎盒子。\n" + 90 | "3.其他说明\n" + 91 | "1)如果出现下载不动的情况(进度一直没动),可以中断一下然后继续点下载,这并不会重新下载已下载文件,而是会重新建立连接下载(就像用迅雷等软件下载没速度重连一下可能会好一样的道理),可能会加快下载速度\n" + 92 | "2)如果中断后发现选择章节界面是灰色的,这时候可以直接关闭程序,然后重新打开重新分析下载,只要你不改动保存位置也不删除已下载文件,那么对于已下载内容同样会直接跳过,不会重新下载。"); 93 | 94 | JScrollPane sp = new JScrollPane(ta); 95 | sp.setBounds(5, 450, 485, 115); 96 | con.add(sp); 97 | } 98 | 99 | private void setFileUI(Container con) 100 | { 101 | JPanel filepanel = new JPanel(); 102 | filepanel.setBounds(5, 45, 485, 30); 103 | filepanel.setBackground(new Color(144, 238, 144)); 104 | filepanel.setLayout(null); 105 | 106 | filepanel.add(pc.getTextFileText()); 107 | filepanel.add(pc.getButtonFilePath()); 108 | 109 | con.add(filepanel); 110 | 111 | } 112 | 113 | private void setScrollUI(Container con) 114 | { 115 | JpScroll = new JPanel(); 116 | JpScroll.setLayout(new BoxLayout(JpScroll, BoxLayout.Y_AXIS)); 117 | scrollpane = new JScrollPane(JpScroll); 118 | scrollpane.getVerticalScrollBar().setUnitIncrement(15); 119 | scrollpane.setBounds(5, 80, 485, 365); 120 | con.add(scrollpane); 121 | } 122 | 123 | public void addChapters(ArrayList Chapters) 124 | { 125 | JpScroll.removeAll(); 126 | scrollpane.getVerticalScrollBar().setValue(0); 127 | Boxs.clear(); 128 | CheckAll = new CheckBoxAll("全选", this); 129 | JpScroll.add(CheckAll); 130 | for(Chapter chapter : Chapters) 131 | { 132 | JCheckBox tmp = new JCheckBox(chapter.getTitle()); 133 | tmp.setSelected(true); 134 | JpScroll.add(tmp); 135 | Boxs.put(chapter, tmp); 136 | } 137 | JpScroll.updateUI(); 138 | } 139 | 140 | public ArrayList getDLInfo() 141 | { 142 | this.TaskNum = 0; 143 | ArrayList info = new ArrayList(); 144 | Set keys = Boxs.keySet(); 145 | for(Chapter key : keys) 146 | { 147 | JCheckBox cb = Boxs.get(key); 148 | if(cb.isSelected() && cb.isEnabled()) 149 | { 150 | info.add(key); 151 | TaskNum ++; 152 | } 153 | } 154 | 155 | return info; 156 | } 157 | 158 | public void SelectAll() 159 | { 160 | Set keys = Boxs.keySet(); 161 | for(Chapter key : keys) 162 | { 163 | Boxs.get(key).setSelected(true); 164 | } 165 | 166 | JpScroll.updateUI(); 167 | return; 168 | } 169 | 170 | public void DeSelectAll() 171 | { 172 | Set keys = Boxs.keySet(); 173 | for(Chapter key : keys) 174 | { 175 | Boxs.get(key).setSelected(false); 176 | } 177 | 178 | JpScroll.updateUI(); 179 | return; 180 | } 181 | 182 | public void disableAllbox() 183 | { 184 | Set keys = Boxs.keySet(); 185 | CheckAll.setEnabled(false); 186 | for(Chapter key : keys) 187 | { 188 | Boxs.get(key).setEnabled(false);; 189 | } 190 | 191 | JpScroll.updateUI(); 192 | return; 193 | } 194 | 195 | public void UpdateDLinfo(Chapter chapter,int curr,int all) 196 | { 197 | JCheckBox box = Boxs.get(chapter); 198 | String currtext = box.getText(); 199 | int index = currtext.indexOf("任务进展:"); 200 | if(-1 != index) 201 | { 202 | currtext = currtext.substring(0, index - 1); 203 | } 204 | 205 | box.setText(currtext + " 任务进展:" + curr + "/" + all); 206 | box.updateUI(); 207 | } 208 | 209 | public void FinishDl(Chapter chapter, int type) 210 | { 211 | TaskNum --; 212 | JCheckBox box = Boxs.get(chapter); 213 | String currtext = box.getText(); 214 | int index = currtext.indexOf("任务进展:"); 215 | if(-1 != index) 216 | { 217 | currtext = currtext.substring(0, index - 1); 218 | } 219 | 220 | if(type == 1) 221 | { 222 | box.setText(currtext + " 下载完成喽~"); 223 | box.setForeground(Color.RED); 224 | } 225 | else if(type == 0) 226 | { 227 | box.setText(currtext + " 任务进展:任务失败,可以尝试再次下载,不会影响已下图片" ); 228 | } 229 | 230 | box.updateUI(); 231 | if(TaskNum == 0) 232 | { 233 | pc.ResetCompnent(); 234 | ActiveAllbox(); 235 | Toolkit.getDefaultToolkit().beep(); 236 | JOptionPane.showMessageDialog(null, "下载完成^_^", "说明", JOptionPane.CLOSED_OPTION); 237 | } 238 | } 239 | 240 | public void ActiveAllbox() 241 | { 242 | Set keys = Boxs.keySet(); 243 | CheckAll.setEnabled(true); 244 | for(Chapter key : keys) 245 | { 246 | if(Boxs.get(key).getForeground() == Color.RED) 247 | { 248 | continue; 249 | } 250 | 251 | Boxs.get(key).setEnabled(true); 252 | } 253 | 254 | JpScroll.updateUI(); 255 | return; 256 | } 257 | 258 | public void InterrputDL() 259 | { 260 | ActiveAllbox(); 261 | this.TaskNum = 0; 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /src/UI/PanelControl.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.Color; 4 | import java.awt.GridLayout; 5 | 6 | import javax.swing.JOptionPane; 7 | import javax.swing.JPanel; 8 | import DownLoad.DLControl; 9 | 10 | @SuppressWarnings("serial") 11 | public class PanelControl extends JPanel{ 12 | private TextComicID ComicId = new TextComicID(); 13 | private ButtonDownLoad buttonDl = new ButtonDownLoad(); 14 | private ButtonAnaly buttonanaly = new ButtonAnaly(); 15 | private SelectThread select = new SelectThread(); 16 | private DLControl dlc = null; 17 | private ButtonFilePath filepath = new ButtonFilePath(); 18 | private TextFileText filetext = new TextFileText(); 19 | private SelectWebSite website = new SelectWebSite(); 20 | 21 | public PanelControl() 22 | { 23 | initComponent(); 24 | initLayout(); 25 | } 26 | 27 | public void setDlc(DLControl dlc) 28 | { 29 | this.dlc = dlc; 30 | } 31 | 32 | private void initComponent() 33 | { 34 | buttonDl.setControl(this); 35 | buttonanaly.setControl(this); 36 | filepath.setControl(this); 37 | } 38 | 39 | private void initLayout() 40 | { 41 | this.add(ComicId); 42 | this.add(website); 43 | this.add(select); 44 | this.add(buttonanaly); 45 | this.add(buttonDl); 46 | this.setBounds(5, 10, 485, 30); 47 | this.setBackground(new Color(144, 238, 144)); 48 | this.setLayout(new GridLayout(1, 4, 5, 0)); 49 | } 50 | 51 | public void StartAnaly(ButtonAnaly buttonanaly) 52 | { 53 | //安全性考虑,这样不是由这个PanelControl本身创建的ButtonAnaly对象无法调用此方法。 54 | if(buttonanaly != this.buttonanaly) return; 55 | if(ComicId.getText().equals("")) 56 | { 57 | JOptionPane.showMessageDialog(null, "请输入漫画ID", "错误说明", JOptionPane.CLOSED_OPTION); 58 | return; 59 | } 60 | 61 | if(buttonDl.getDLState()) 62 | { 63 | JOptionPane.showMessageDialog(null, "正在下载中,暂时无法操作", "错误说明", JOptionPane.CLOSED_OPTION); 64 | return; 65 | } 66 | 67 | dlc.setComicId(ComicId.getText()); 68 | String package_addr = UIConfig.ComicWebList.get(website.getSelectedIndex()).getpackage_addr(); 69 | dlc.setpackage_addr(package_addr); 70 | 71 | if(dlc.AnalyChapter()) 72 | { 73 | buttonDl.setEnabled(true); 74 | } 75 | else 76 | { 77 | JOptionPane.showMessageDialog(null, "可能的错误说明:\n1.漫画ID错误或网站已禁止该漫画,请检查。\n2.有可能是因为你下载过多导致被禁止IP了哟(尤其是网站上的版权受限漫画,下载过多特别容易被察觉而禁止),\n可以尝试ping www.manhuagui.com来确认。如果ping不通而通过手机无线网络却可以访问就基本确实是被封IP了。\n(可以重新拨号或重启光钎盒子来换IP)", 78 | "错误说明", JOptionPane.CLOSED_OPTION); 79 | } 80 | 81 | buttonanaly.setText("获取章节"); 82 | buttonanaly.setEnabled(true); 83 | return; 84 | } 85 | 86 | public boolean StartDL(ButtonDownLoad buttonDl) 87 | { 88 | if(buttonDl != this.buttonDl) return false; 89 | 90 | buttonanaly.setEnabled(false); 91 | select.setEnabled(false); 92 | website.setEnabled(false); 93 | filepath.setEnabled(false); 94 | filetext.setEditable(false); 95 | dlc.setFilePath(filetext.getText()); 96 | boolean result = dlc.StartDL((int)Math.pow(2, select.getSelectedIndex()) * 4); 97 | if(false == result) 98 | { 99 | buttonanaly.setEnabled(true); 100 | select.setEnabled(true); 101 | website.setEnabled(true); 102 | filepath.setEnabled(true); 103 | filetext.setEditable(true); 104 | } 105 | 106 | return result; 107 | } 108 | 109 | public void ResetCompnent() 110 | { 111 | buttonDl.setEnabled(true); 112 | buttonDl.setText("下载"); 113 | buttonDl.resetDLStat(); 114 | buttonanaly.setEnabled(true); 115 | select.setEnabled(true); 116 | website.setEnabled(true); 117 | filepath.setEnabled(true); 118 | } 119 | 120 | public void InterrputDL() 121 | { 122 | ResetCompnent(); 123 | dlc.InterrputDL(); 124 | } 125 | 126 | public String getFilePath() 127 | { 128 | return filetext.getText(); 129 | } 130 | 131 | public void setFilePath(String filepath) 132 | { 133 | filetext.setText(filepath); 134 | } 135 | 136 | public ButtonFilePath getButtonFilePath() { 137 | return filepath; 138 | } 139 | 140 | public TextFileText getTextFileText() { 141 | return filetext; 142 | } 143 | 144 | public SelectWebSite getWebsite() { 145 | return website; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/UI/SelectThread.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import javax.swing.JComboBox; 4 | 5 | @SuppressWarnings("serial") 6 | public class SelectThread extends JComboBox{ 7 | 8 | public SelectThread() 9 | { 10 | this.addItem("4线程"); 11 | this.addItem("8线程"); 12 | this.setSelectedIndex(0); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/UI/SelectWebSite.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import javax.swing.JComboBox; 4 | 5 | @SuppressWarnings("serial") 6 | public class SelectWebSite extends JComboBox{ 7 | public SelectWebSite() 8 | { 9 | for(ComicWebInfo cw : UIConfig.ComicWebList) 10 | { 11 | this.addItem(cw.getname()); 12 | } 13 | this.setSelectedIndex(0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/UI/TextComicID.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.Color; 4 | import java.awt.event.KeyAdapter; 5 | import java.awt.event.KeyEvent; 6 | import java.awt.event.MouseAdapter; 7 | import java.awt.event.MouseEvent; 8 | 9 | import javax.swing.JTextField; 10 | 11 | @SuppressWarnings("serial") 12 | public class TextComicID extends JTextField{ 13 | 14 | public TextComicID() 15 | { 16 | this.setText("请输入漫画ID"); 17 | this.setForeground(Color.BLUE); 18 | this.addMouseListener(new MouseAdapter() 19 | { 20 | @Override 21 | public void mousePressed(MouseEvent e) 22 | { 23 | if(TextComicID.this.getText().equals("请输入漫画ID")) 24 | { 25 | TextComicID.this.setText(""); 26 | TextComicID.this.setForeground(Color.BLACK); 27 | } 28 | } 29 | }); 30 | 31 | this.addKeyListener(new KeyAdapter() 32 | { 33 | @Override 34 | public void keyTyped(KeyEvent e) 35 | { 36 | if(TextComicID.this.getText().equals("请输入漫画ID")) 37 | { 38 | TextComicID.this.setText(""); 39 | TextComicID.this.setForeground(Color.BLACK); 40 | } 41 | int keychar = e.getKeyChar(); 42 | if(!(keychar >= KeyEvent.VK_0 && keychar <= KeyEvent.VK_9)) 43 | { 44 | e.consume(); 45 | } 46 | } 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/UI/TextFileText.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.io.File; 4 | 5 | import javax.swing.JTextField; 6 | 7 | @SuppressWarnings("serial") 8 | public class TextFileText extends JTextField{ 9 | 10 | public TextFileText() 11 | { 12 | this.setBounds(0, 0, 387, 30); 13 | this.setText((new File(".")).getAbsolutePath()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/UI/UIConfig.java: -------------------------------------------------------------------------------- 1 | package UI; 2 | 3 | import java.awt.Toolkit; 4 | import java.util.ArrayList; 5 | 6 | //这个类用于记录一些常用的变量 7 | public class UIConfig { 8 | public static final int WinH = (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight(); 9 | public static final int WinW = (int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(); 10 | public static final int ComicH = 600; 11 | public static final int ComicW = 500; 12 | public static ArrayList ComicWebList = new ArrayList(); 13 | 14 | static 15 | { 16 | ComicWebList.add(new ComicWebInfo("manhuagui.DlManhuagui", "漫画柜")); 17 | /*第一个参数表明你新增Comic子类的地址,需要包含包名 18 | * 第二个参数表明你想给这个网站取的名字,会显示在主界面的选择框中 19 | * */ 20 | //ComicWebList.add(new ComicWebInfo("类地址", "漫画名")); 21 | } 22 | } 23 | 24 | class ComicWebInfo 25 | { 26 | private String package_addr = null; 27 | private String name = null; 28 | 29 | public ComicWebInfo(String package_addr,String name) 30 | { 31 | this.package_addr = package_addr; 32 | this.name = name; 33 | } 34 | 35 | public String getpackage_addr() 36 | { 37 | return package_addr; 38 | } 39 | 40 | public String getname() 41 | { 42 | return name; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | var LZString = (function () { 2 | var f = String.fromCharCode; 3 | var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 4 | var baseReverseDic = {}; 5 | 6 | function getBaseValue(alphabet, character) { 7 | if (!baseReverseDic[alphabet]) { 8 | baseReverseDic[alphabet] = {}; 9 | for (var i = 0; i < alphabet.length; i++) { 10 | baseReverseDic[alphabet][alphabet.charAt(i)] = i 11 | } 12 | } 13 | return baseReverseDic[alphabet][character] 14 | } 15 | var LZString = { 16 | decompressFromBase64: function (input) { 17 | if (input == null) return ""; 18 | if (input == "") return null; 19 | return LZString._0(input.length, 32, function (index) { 20 | return getBaseValue(keyStrBase64, input.charAt(index)) 21 | }) 22 | }, _0: function (length, resetValue, getNextValue) { 23 | var dictionary = [], 24 | next, enlargeIn = 4, 25 | dictSize = 4, 26 | numBits = 3, 27 | entry = "", 28 | result = [], 29 | i, w, bits, resb, maxpower, power, c, data = { 30 | val: getNextValue(0), 31 | position: resetValue, 32 | index: 1 33 | }; 34 | for (i = 0; i < 3; i += 1) { 35 | dictionary[i] = i 36 | } 37 | bits = 0; 38 | maxpower = Math.pow(2, 2); 39 | power = 1; 40 | while (power != maxpower) { 41 | resb = data.val & data.position; 42 | data.position >>= 1; 43 | if (data.position == 0) { 44 | data.position = resetValue; 45 | data.val = getNextValue(data.index++) 46 | } 47 | bits |= (resb > 0 ? 1 : 0) * power; 48 | power <<= 1 49 | } 50 | switch (next = bits) { 51 | case 0: 52 | bits = 0; 53 | maxpower = Math.pow(2, 8); 54 | power = 1; 55 | while (power != maxpower) { 56 | resb = data.val & data.position; 57 | data.position >>= 1; 58 | if (data.position == 0) { 59 | data.position = resetValue; 60 | data.val = getNextValue(data.index++) 61 | } 62 | bits |= (resb > 0 ? 1 : 0) * power; 63 | power <<= 1 64 | } 65 | c = f(bits); 66 | break; 67 | case 1: 68 | bits = 0; 69 | maxpower = Math.pow(2, 16); 70 | power = 1; 71 | while (power != maxpower) { 72 | resb = data.val & data.position; 73 | data.position >>= 1; 74 | if (data.position == 0) { 75 | data.position = resetValue; 76 | data.val = getNextValue(data.index++) 77 | } 78 | bits |= (resb > 0 ? 1 : 0) * power; 79 | power <<= 1 80 | } 81 | c = f(bits); 82 | break; 83 | case 2: 84 | return "" 85 | } 86 | dictionary[3] = c; 87 | w = c; 88 | result.push(c); 89 | while (true) { 90 | if (data.index > length) { 91 | return "" 92 | } 93 | bits = 0; 94 | maxpower = Math.pow(2, numBits); 95 | power = 1; 96 | while (power != maxpower) { 97 | resb = data.val & data.position; 98 | data.position >>= 1; 99 | if (data.position == 0) { 100 | data.position = resetValue; 101 | data.val = getNextValue(data.index++) 102 | } 103 | bits |= (resb > 0 ? 1 : 0) * power; 104 | power <<= 1 105 | } 106 | switch (c = bits) { 107 | case 0: 108 | bits = 0; 109 | maxpower = Math.pow(2, 8); 110 | power = 1; 111 | while (power != maxpower) { 112 | resb = data.val & data.position; 113 | data.position >>= 1; 114 | if (data.position == 0) { 115 | data.position = resetValue; 116 | data.val = getNextValue(data.index++) 117 | } 118 | bits |= (resb > 0 ? 1 : 0) * power; 119 | power <<= 1 120 | } 121 | dictionary[dictSize++] = f(bits); 122 | c = dictSize - 1; 123 | enlargeIn--; 124 | break; 125 | case 1: 126 | bits = 0; 127 | maxpower = Math.pow(2, 16); 128 | power = 1; 129 | while (power != maxpower) { 130 | resb = data.val & data.position; 131 | data.position >>= 1; 132 | if (data.position == 0) { 133 | data.position = resetValue; 134 | data.val = getNextValue(data.index++) 135 | } 136 | bits |= (resb > 0 ? 1 : 0) * power; 137 | power <<= 1 138 | } 139 | dictionary[dictSize++] = f(bits); 140 | c = dictSize - 1; 141 | enlargeIn--; 142 | break; 143 | case 2: 144 | return result.join('') 145 | } 146 | if (enlargeIn == 0) { 147 | enlargeIn = Math.pow(2, numBits); 148 | numBits++ 149 | } 150 | if (dictionary[c]) { 151 | entry = dictionary[c] 152 | } else { 153 | if (c === dictSize) { 154 | entry = w + w.charAt(0) 155 | } else { 156 | return null 157 | } 158 | } 159 | result.push(entry); 160 | dictionary[dictSize++] = w + entry.charAt(0); 161 | enlargeIn--; 162 | w = entry; 163 | if (enlargeIn == 0) { 164 | enlargeIn = Math.pow(2, numBits); 165 | numBits++ 166 | } 167 | } 168 | } 169 | }; 170 | return LZString 171 | })(); 172 | 173 | function depressBase64(str,f) 174 | { 175 | var x = LZString.decompressFromBase64(str); 176 | return x.split(f); 177 | } 178 | 179 | function getcinfo(p, a, c, y, e, d) 180 | { 181 | e = function (c) 182 | { 183 | return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) 184 | }; 185 | if (!''.replace(/^/, String)) 186 | { 187 | while (c--) d[e(c)] = y[c] || e(c); 188 | y = [ 189 | function (e) { 190 | return d[e] 191 | } 192 | ]; 193 | e = function () { 194 | return '\\w+' 195 | }; 196 | c = 1; 197 | }; 198 | while (c--) 199 | if (y[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), y[c]); 200 | return p; 201 | } 202 | 203 | function parase(p, a, c, y, e) 204 | { 205 | return getcinfo(p, a, c, depressBase64(y, "|"), e, {}); 206 | } 207 | 208 | function getChapter(str) 209 | { 210 | return LZString.decompressFromBase64(str); 211 | } 212 | 213 | 214 | -------------------------------------------------------------------------------- /src/manhuagui/DlManhuagui.java: -------------------------------------------------------------------------------- 1 | package manhuagui; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import javax.script.Invocable; 9 | import javax.script.ScriptEngine; 10 | import javax.script.ScriptEngineManager; 11 | import javax.swing.JOptionPane; 12 | 13 | import ComicCore.Chapter; 14 | import ComicCore.ComicInfo; 15 | import Start.LOG; 16 | 17 | public class DlManhuagui extends ComicInfo{ 18 | 19 | @Override 20 | public ArrayList GetChapetr(String ComicId) { 21 | String UrlAddr = "https://www.manhuagui.com/comic/" + ComicId + "/?" +(new Date()).getTime(); 22 | 23 | String HtmlInfo = this.GetHtmlInfo(UrlAddr.toString()); 24 | if(null == HtmlInfo) return null; 25 | 26 | String Title = null; 27 | String Html = null; 28 | ArrayList Chapters = new ArrayList(); 29 | 30 | String BookNameRex = "name: '(.+?)'"; 31 | Pattern pattern = Pattern.compile(BookNameRex); 32 | Matcher matcher = pattern.matcher(HtmlInfo); 33 | 34 | 35 | while(matcher.find()) 36 | { 37 | BookName = matcher.group(1); 38 | } 39 | 40 | //漫画柜在一部分网站上对于章节信息也采用了base64压缩,需要先进行解压缩 41 | if(HtmlInfo.indexOf("__VIEWSTATE") != -1) 42 | { 43 | HtmlInfo = adjustHtmlInfo(HtmlInfo); 44 | } 45 | 46 | //使用正则表达式进行匹配 47 | String Rex = "
  • 0) 85 | { 86 | JOptionPane.showMessageDialog(null, "当前漫画为网站标定为版权受限的漫画,请不要下载过多,可能被封IP,详细见下面的说明", "提示", JOptionPane.CLOSED_OPTION); 87 | HtmlInfo = tmpHtmlInfo; 88 | } 89 | 90 | return HtmlInfo; 91 | } 92 | 93 | @Override 94 | public ArrayList GetPicUrlByChapter(String ChapterUrl) { 95 | String HtmlInfo = this.GetHtmlInfo(ChapterUrl); 96 | if(null == HtmlInfo) return null; 97 | 98 | String PicInfo = GetJsInfo(HtmlInfo); 99 | if(null == PicInfo) 100 | { 101 | LOG.log("JS解析失败"); 102 | return null; 103 | } 104 | 105 | ArrayList PicturePath = new ArrayList(); 106 | String path = null; 107 | String[] files = null; 108 | String cid = null; 109 | String md5 = null; 110 | String MatchString = "cid\":(.+?),\"cname(.+?)files\":\\[(.+?)\\],(.+?)path\":\"(.+?)\"(.+?)md5\":\"(.+?)\""; 111 | Pattern pattern = Pattern.compile(MatchString); 112 | Matcher match = pattern.matcher(PicInfo); 113 | 114 | while(match.find()) 115 | { 116 | cid = match.group(1); 117 | path = match.group(5); 118 | files = match.group(3).replaceAll("\"","").replaceAll(".webp", "").split(","); 119 | md5 = match.group(7); 120 | } 121 | for(String file:files) 122 | { 123 | PicturePath.add("https://i.hamreus.com" + path + file + "?cid="+ cid + "&md5=" + md5); 124 | } 125 | 126 | return PicturePath; 127 | } 128 | 129 | /*从网页内容中获取我们需要用于解码的JS,这里漫画柜使用了2层压缩: 130 | * 1.eval函数本身执行的就是一个parker压缩; 131 | * 2.对于parker压缩的第四个入参也就是k,需要先进行base64解压缩; 132 | * 这个函数的目的就是为了获取解码需要的相关参数*/ 133 | private String GetJsInfo(String HtmlInfo) 134 | { 135 | if(null == HtmlInfo) return null; 136 | 137 | String Para1 = null,Para2 = null,Para3 = null,Para4 = null,Para5 = null; 138 | String Base64StringMatch = "\\(function\\(p,a,c,k,e,d\\)\\{(.+?)\\}\\('(.+?)',(.+?),(.+?),'(.+?)'(.+?),(.+?),"; 139 | 140 | Pattern pattern = Pattern.compile(Base64StringMatch); 141 | Matcher match = pattern.matcher(HtmlInfo); 142 | 143 | while(match.find()) 144 | { 145 | Para1 = match.group(2); 146 | Para2 = match.group(3); 147 | Para3 = match.group(4); 148 | Para4 = match.group(5); 149 | Para5 = match.group(7); 150 | } 151 | 152 | if(null == Para1) 153 | { 154 | LOG.log("Parked压缩解码失败"); 155 | return null; 156 | } 157 | 158 | return paraseInfo(Para1,Integer.parseInt(Para2), Integer.parseInt(Para3), Para4,Integer.parseInt(Para5)); 159 | } 160 | //此处直接调用修改自源HTML的脚本来进行解码,另外,此处采用eval(String)而不用eval(FileReader)是因为打包成JAR后无法使用后者 161 | private String paraseInfo(String para1, int para2, int para3, String para4, int para5) 162 | { 163 | ScriptEngineManager engineManager = new ScriptEngineManager(); 164 | ScriptEngine engine = engineManager.getEngineByName("js"); 165 | try { 166 | engine.eval(manhuaguiCfg.JSFile); 167 | Invocable inv = (Invocable)engine; 168 | String ParkerInfo = (String)inv.invokeFunction("parase", para1,para2,para3,para4,para5); 169 | return ParkerInfo; 170 | } catch (Exception e) { 171 | LOG.log(e.getMessage()); 172 | e.printStackTrace(); 173 | return null; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/manhuagui/manhuaguiCfg.java: -------------------------------------------------------------------------------- 1 | package manhuagui; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStreamReader; 5 | 6 | import Start.LOG; 7 | 8 | public class manhuaguiCfg { 9 | public static String JSFile = ""; 10 | 11 | static 12 | { 13 | BufferedReader br = new BufferedReader(new InputStreamReader(Start.Start.class.getResourceAsStream("/config.js"))); 14 | String line = null; 15 | 16 | try { 17 | try{ 18 | while((line = br.readLine()) != null) 19 | { 20 | JSFile = JSFile.concat(line); 21 | } 22 | } 23 | catch(Exception e) 24 | { 25 | LOG.log(e.getMessage()); 26 | e.printStackTrace(); 27 | } 28 | finally 29 | { 30 | br.close(); 31 | } 32 | } catch (Exception e) { 33 | LOG.log(e.getMessage()); 34 | e.printStackTrace(); 35 | } 36 | } 37 | } 38 | --------------------------------------------------------------------------------