├── .gitignore ├── .idea ├── .gitignore ├── encodings.xml ├── markdown.xml ├── misc.xml ├── uiDesigner.xml └── vcs.xml ├── LICENSE ├── README.en.md ├── README.md ├── pom.xml └── src └── main ├── java ├── module-info.java └── xyz │ └── zcraft │ └── acgpicdownload │ ├── Main.java │ ├── commands │ ├── Fetch.java │ └── Schedule.java │ ├── exceptions │ ├── SourceConfigException.java │ ├── SourceNotFoundException.java │ └── UnsupportedReturnTypeException.java │ ├── gui │ ├── ConfigManager.java │ ├── GUI.java │ ├── Notice.java │ ├── ResourceLoader.java │ ├── base │ │ ├── MyPane.java │ │ ├── PixivFetchPane.java │ │ └── argpanes │ │ │ ├── ArgumentPane.java │ │ │ ├── IntegerArgumentPane.java │ │ │ ├── LimitedIntegerArgumentPane.java │ │ │ ├── LimitedStringArgumentPane.java │ │ │ └── StringArgumentPane.java │ └── controllers │ │ ├── ErrorPaneController.java │ │ ├── FetchPaneController.java │ │ ├── MainPaneController.java │ │ ├── MenuPaneController.java │ │ ├── PixivAccountPaneController.java │ │ ├── PixivDiscoveryPaneController.java │ │ ├── PixivDownloadPaneController.java │ │ ├── PixivMenuPaneController.java │ │ ├── PixivPaneController.java │ │ ├── PixivRankingPaneController.java │ │ ├── PixivRelatedPaneController.java │ │ ├── PixivSearchPaneController.java │ │ ├── PixivUserPaneController.java │ │ └── SettingsPaneController.java │ └── util │ ├── ExceptionHandler.java │ ├── Logger.java │ ├── ResourceBundleUtil.java │ ├── SSLUtil.java │ ├── downloadutil │ ├── DownloadManager.java │ ├── DownloadResult.java │ ├── DownloadStatus.java │ └── DownloadUtil.java │ ├── fetchutil │ ├── FetchUtil.java │ └── Result.java │ ├── pixivutils │ ├── ArtworkCondition.java │ ├── From.java │ ├── GifData.java │ ├── NamingRule.java │ ├── PixivAccount.java │ ├── PixivArtwork.java │ ├── PixivDownload.java │ ├── PixivDownloadUtil.java │ └── PixivFetchUtil.java │ ├── scheduleutil │ └── Event.java │ └── sourceutil │ ├── Source.java │ ├── SourceFetcher.java │ ├── SourceManager.java │ └── argument │ ├── Argument.java │ ├── IntegerArgument.java │ ├── IntegerLimit.java │ ├── LimitedIntegerArgument.java │ ├── LimitedStringArgument.java │ └── StringArgument.java └── resources ├── log4j.properties └── xyz └── zcraft └── acgpicdownload ├── gui ├── bg.png ├── default.css ├── fxml │ ├── ErrorPane.fxml │ ├── FetchPane.fxml │ ├── LimitedIntegerArgumentPane.fxml │ ├── LimitedStringArgumentPane.fxml │ ├── MainPane.fxml │ ├── MenuPane.fxml │ ├── Notice.fxml │ ├── PixivAccountPane.fxml │ ├── PixivDiscoveryPane.fxml │ ├── PixivDownloadPane.fxml │ ├── PixivMenuPane.fxml │ ├── PixivPane.fxml │ ├── PixivRankingPane.fxml │ ├── PixivRelatedPane.fxml │ ├── PixivSearchPane.fxml │ ├── PixivUserPane.fxml │ ├── SettingsPane.fxml │ └── StringArgumentPane.fxml └── icon │ └── save.png ├── languages ├── String.properties ├── String_en.properties └── String_zh_CN.properties └── util └── sourceutil └── sources.json /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store 39 | 40 | 41 | 42 | /out/ 43 | /error.log 44 | /out.log 45 | /sources.json 46 | /src/main/resources/META-INF/ 47 | /config.json 48 | /ACGPicDownload.exe 49 | /pic/ 50 | /bg/ 51 | /accounts.json 52 | /log/ 53 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | /artifacts/ 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zxzxy 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. 22 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | [中文文档](https://github.com/zxzxy/ACGPicDownload/blob/master/README.md) | English Document 2 | 3 | ------------------ 4 | 5 | # ACGPicDownload 6 | 7 | A convenient tool to download pictures from various pixiv and APIs. 8 | 9 | # Features 10 | 11 | - Support fetching Pixiv(Menu, Discovery, User, Ranking and Related Artworks) 12 | - Easy to use ([Simple tutorial](https://github.com/zxzxy/ACGPicDownload/wiki/How-to-use#to-be-more-specific)) 13 | - Supports custom sources ([Add custom sources](https://github.com/zxzxy/ACGPicDownload/wiki/Add-custom-sources)) 14 | - Supports scheduled 15 | execute ([schedule subcommand](https://github.com/zxzxy/ACGPicDownload/wiki/How-to-use#subcommand-schedule)) 16 | - Highly customizable naming rules and fetching urls 17 | 18 | # Screenshots 19 | ![gif](https://user-images.githubusercontent.com/73475219/206824795-d3332057-41e5-4271-abdd-446a02a9db3c.gif) 20 | ![image](https://user-images.githubusercontent.com/73475219/206455033-dc49237b-b8f1-4a39-bfa5-3f64b6f71fb1.png) 21 | 22 | # Usage 23 | 24 | - Download the latest Release in [Releases](https://github.com/zxzxy/ACGPicDownload/releases) 25 | 26 | > If you already have Java17+ installed, then you can download `ACGPicDownload.version.jar` and run it through command 27 | > line. Or 28 | > download `ACGPicDownload.version.win.exe` on Windows and run. 29 | 30 | > If you don't, then you can download JDK17+ [here](https://adoptium.net/en-US/temurin/archive), or 31 | > download `ACGPicDownload.version.win.7z`, which has JDK17 included 32 | 33 | 34 | Please go to [wiki](https://github.com/zxzxy/ACGPicDownload/wiki) for usage... 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 中文文档 | [English Document](https://github.com/zxzxy/ACGPicDownload/blob/master/README.en.md) 2 | 3 | -------------------- 4 | 5 | # ACGPicDownload 6 | 7 | 一个从P站和图片API爬 ~~二次元涩图~~ 图片的工具 8 | 9 | # 特性 10 | 11 | - 支持爬取P站的主页,发现,用户,榜单和相关作品 12 | - 支持插画,漫画,动图的下载 13 | - 简单易用 14 | - 支持自定义下载源 ([添加自定义下载源](https://github.com/zxzxy/ACGPicDownload/wiki/%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%8B%E8%BD%BD%E6%BA%90)) 15 | - 支持定时执行 ([子指令 schedule](https://github.com/zxzxy/ACGPicDownload/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B#%E5%AD%90%E6%8C%87%E4%BB%A4-schedule)) 16 | - 高度可自定义的下载连接与文件名 17 | 18 | # 截图 19 | GUI: 20 | ![动画](https://user-images.githubusercontent.com/73475219/207077096-f7910d21-19cc-4ffe-95c7-81081a72b921.gif) 21 | ![image](https://user-images.githubusercontent.com/73475219/206438395-89e1eec5-f734-484f-94d9-2b82a6c63f69.png) 22 | 23 | 命令行: 24 | ![2022-11-18 19-42-43_2](https://user-images.githubusercontent.com/73475219/202701179-2b8439ac-0f0a-4f12-9fbc-9574a1620411.gif) 25 | 26 | # 如何使用 27 | 28 | - 在[Releases](https://github.com/zxzxy/ACGPicDownload/releases)下载最新版本 29 | 30 | > 如果你的设备已经安装了Java17+,那么可以下载 `ACGPicDownload.版本号.jar` 并直接使用命令行运行,或在 Windows 31 | > 上下载 `ACGPicDownload.版本号.win.exe` 并直接运行 32 | 33 | > 如果并没有Java17+安装,那么可以在[这里](https://adoptium.net/zh-CN/temurin/archive) 34 | > 下载安装Java17以上的版本,或者如果你是Windows,则可以下载 `ACGPicDownload.版本号.win.jdk.7z`, 里面自带JDK17 35 | 36 | - 请在[wiki](https://github.com/zxzxy/ACGPicDownload/wiki)查看详细使用教程~ 37 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | xyz.zcraft 8 | ACGPicDownload 9 | 3.1.0-alpha.1 10 | 11 | 12 | 17 13 | 17 14 | UTF-8 15 | 16 | 17 | 18 | 19 | org.jsoup 20 | jsoup 21 | 1.15.3 22 | 23 | 24 | com.alibaba 25 | fastjson 26 | 2.0.21 27 | 28 | 29 | org.openjfx 30 | javafx-base 31 | 19 32 | 33 | 34 | org.openjfx 35 | javafx-controls 36 | 19 37 | 38 | 39 | org.openjfx 40 | javafx-fxml 41 | 19 42 | 43 | 44 | io.github.palexdev 45 | materialfx 46 | 11.13.5 47 | 48 | 49 | com.madgag 50 | animated-gif-lib 51 | 1.4 52 | 53 | 54 | org.jetbrains 55 | annotations 56 | 23.1.0 57 | 58 | 59 | org.projectlombok 60 | lombok 61 | 1.18.24 62 | 63 | 64 | log4j 65 | log4j 66 | 1.2.12 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module xyz.zcraft.acgpicdownload { 2 | requires transitive javafx.controls; 3 | requires transitive com.alibaba.fastjson2; 4 | requires transitive javafx.base; 5 | requires transitive javafx.graphics; 6 | requires transitive MaterialFX; 7 | requires transitive org.jetbrains.annotations; 8 | requires transitive VirtualizedFX; 9 | requires transitive lombok; 10 | requires log4j; 11 | requires animated.gif.lib; 12 | requires org.jsoup; 13 | 14 | opens xyz.zcraft.acgpicdownload to javafx.controls, javafx.base, javafx.graphics, javafx.fxml, com.alibaba.fastjson2; 15 | opens xyz.zcraft.acgpicdownload.gui to javafx.base, javafx.controls, javafx.graphics, javafx.fxml; 16 | opens xyz.zcraft.acgpicdownload.gui.base to javafx.base, javafx.controls, javafx.graphics, javafx.fxml; 17 | opens xyz.zcraft.acgpicdownload.gui.base.argpanes to javafx.base, javafx.controls, javafx.graphics, javafx.fxml; 18 | opens xyz.zcraft.acgpicdownload.gui.controllers to javafx.base, javafx.controls, javafx.graphics, javafx.fxml; 19 | opens xyz.zcraft.acgpicdownload.util.sourceutil to com.alibaba.fastjson2; 20 | opens xyz.zcraft.acgpicdownload.util.pixivutils to javafx.base, javafx.controls, javafx.graphics, javafx.fxml, com.alibaba.fastjson2; 21 | opens xyz.zcraft.acgpicdownload.util.sourceutil.argument to javafx.base, javafx.controls, javafx.graphics, javafx.fxml, com.alibaba.fastjson2; 22 | exports xyz.zcraft.acgpicdownload; 23 | exports xyz.zcraft.acgpicdownload.exceptions; 24 | exports xyz.zcraft.acgpicdownload.gui; 25 | exports xyz.zcraft.acgpicdownload.gui.controllers; 26 | exports xyz.zcraft.acgpicdownload.gui.base; 27 | exports xyz.zcraft.acgpicdownload.gui.base.argpanes; 28 | exports xyz.zcraft.acgpicdownload.util; 29 | exports xyz.zcraft.acgpicdownload.util.pixivutils; 30 | exports xyz.zcraft.acgpicdownload.util.fetchutil; 31 | exports xyz.zcraft.acgpicdownload.util.sourceutil; 32 | exports xyz.zcraft.acgpicdownload.util.sourceutil.argument; 33 | exports xyz.zcraft.acgpicdownload.util.downloadutil; 34 | } -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/Main.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload; 2 | 3 | import xyz.zcraft.acgpicdownload.commands.Fetch; 4 | import xyz.zcraft.acgpicdownload.commands.Schedule; 5 | import xyz.zcraft.acgpicdownload.gui.GUI; 6 | import xyz.zcraft.acgpicdownload.util.Logger; 7 | import xyz.zcraft.acgpicdownload.util.SSLUtil; 8 | 9 | import java.io.FileNotFoundException; 10 | import java.io.PrintStream; 11 | import java.io.PrintWriter; 12 | import java.text.SimpleDateFormat; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | public class Main { 18 | public static PrintWriter debugOut; 19 | public static PrintStream log; 20 | public static PrintWriter err; 21 | private static boolean debug = false; 22 | 23 | public static boolean isDebug() { 24 | return debug; 25 | } 26 | 27 | public static void debugOn() { 28 | Main.debug = true; 29 | try { 30 | debugOut = new PrintWriter("debug.log"); 31 | } catch (FileNotFoundException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | public static void logError(String message) { 37 | if (err == null) { 38 | try { 39 | err = new PrintWriter("error.log"); 40 | } catch (FileNotFoundException ignored) { 41 | } 42 | } 43 | err.print("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "]"); 44 | err.println(message); 45 | err.flush(); 46 | } 47 | 48 | public static void logError(Exception e) { 49 | if (err == null) { 50 | try { 51 | err = new PrintWriter("error.log"); 52 | } catch (FileNotFoundException ignored) { 53 | } 54 | } 55 | e.printStackTrace(); 56 | err.print("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "]"); 57 | e.printStackTrace(err); 58 | err.flush(); 59 | } 60 | 61 | public static void main(String[] args) { 62 | try { 63 | SSLUtil.ignoreSsl(); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | ArrayList argList = new ArrayList<>(List.of(args)); 68 | try { 69 | log = new PrintStream("out.log"); 70 | } catch (FileNotFoundException ignored) { 71 | } 72 | if (argList.size() == 0) { 73 | GUI.start(args); 74 | } else if (argList.get(0).equalsIgnoreCase("fetch")) { 75 | argList.remove(0); 76 | Fetch f = new Fetch(); 77 | f.enableConsoleProgressBar = true; 78 | f.main(argList, new Logger("Fetch", System.out)); 79 | } else if (argList.get(0).equalsIgnoreCase("schedule")) { 80 | argList.remove(0); 81 | Schedule s = new Schedule(); 82 | s.main(argList); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/commands/Schedule.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.commands; 2 | 3 | import xyz.zcraft.acgpicdownload.util.Logger; 4 | import xyz.zcraft.acgpicdownload.util.scheduleutil.Event; 5 | 6 | import java.time.Duration; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Scanner; 10 | 11 | import static xyz.zcraft.acgpicdownload.util.sourceutil.SourceManager.isEmpty; 12 | 13 | public class Schedule { 14 | private final ArrayList events = new ArrayList<>(); 15 | Logger l; 16 | 17 | public void main(ArrayList args) { 18 | if (!parseEvent(args)) { 19 | return; 20 | } 21 | 22 | l = new Logger("Schedule", System.out); 23 | 24 | Scanner s = new Scanner(System.in); 25 | 26 | INPUT: 27 | while (true) { 28 | System.out.print("Schedule>"); 29 | String in = s.nextLine(); 30 | 31 | if (isEmpty(in)) { 32 | continue; 33 | } 34 | 35 | ArrayList t = new ArrayList<>(List.of(in.toLowerCase().split(" "))); 36 | 37 | switch (t.get(0)) { 38 | case "exit" -> { 39 | return; 40 | } 41 | case "add" -> { 42 | if (t.size() > 2) { 43 | t.remove(0); 44 | parseEvent(t); 45 | } 46 | } 47 | case "del" -> { 48 | if (t.size() == 2) { 49 | try { 50 | int i = Integer.parseInt(t.get(1)); 51 | events.get(i).setActive(false); 52 | events.remove(i); 53 | break; 54 | } catch (NumberFormatException ignored) { 55 | } 56 | } 57 | System.err.println("Please enter a valid event id"); 58 | } 59 | case "start" -> { 60 | break INPUT; 61 | } 62 | case "list" -> { 63 | int a = "Command".length(); 64 | int b = "Max Times".length(); 65 | int c = "Interval".length(); 66 | for (Event e : events) { 67 | a = Math.max(a, e.getCommandString().length()); 68 | b = Math.max(b, String.valueOf(e.getMaxTimes()).length()); 69 | c = Math.max(c, e.getInterval().toString().length()); 70 | } 71 | l.printlnf("%-" + a + "s %s %-" + b + "s %s %-" + c + "s", "Command", " | ", "Max Times", " | ", "Interval"); 72 | for (Event event : events) { 73 | l.printlnf("%-" + a + "s %s %-" + b + "s %s %-" + c + "s", event.getCommandString(), " | ", String.valueOf(event.getMaxTimes()), " | ", event.getInterval().toString()); 74 | } 75 | } 76 | } 77 | } 78 | 79 | s.close(); 80 | 81 | while (events.size() > 0) { 82 | for (int i = 0; i < events.size(); i++) { 83 | Event e = events.get(i); 84 | if (e.isActive()) { 85 | if (System.currentTimeMillis() - e.getLastTimeRan() >= e.getInterval().toMillis() && (e.getMaxTimes() == -1 || e.getTimesRan() <= e.getMaxTimes())) { 86 | e.addTimesRan(); 87 | e.setLastTimeRan(System.currentTimeMillis()); 88 | Logger logger = new Logger(String.valueOf(i), l, System.out); 89 | Thread t = new Thread(new Runnable() { 90 | @Override 91 | public void run() { 92 | Fetch f = new Fetch(); 93 | Logger t = new Logger(String.valueOf(this.hashCode()), logger, System.out); 94 | f.main(e.getCommands(), t); 95 | t.info("[Event End]"); 96 | } 97 | }); 98 | t.start(); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | public boolean parseEvent(ArrayList args) { 106 | if (args.size() != 0) { 107 | int maxTime = -1; 108 | Duration interval = null; 109 | for (int i = 0; i < args.size(); i++) { 110 | switch (args.get(i)) { 111 | case "--max-times", "-m" -> { 112 | if (args.size() > i + 1) { 113 | try { 114 | maxTime = Integer.parseInt(args.get(i + 1)); 115 | args.remove(i); 116 | args.remove(i); 117 | i -= 1; 118 | break; 119 | } catch (NumberFormatException ignored) { 120 | } 121 | } 122 | l.err("Please enter a valid number for the max time"); 123 | return false; 124 | } 125 | case "--interval", "-i" -> { 126 | if (args.size() > i + 1) { 127 | try { 128 | interval = Duration.parse("PT" + args.get(i + 1)); 129 | args.remove(i); 130 | args.remove(i); 131 | i -= 1; 132 | break; 133 | } catch (NumberFormatException ignored) { 134 | } 135 | } 136 | l.err("Please enter a valid number for the interval"); 137 | return false; 138 | } 139 | } 140 | } 141 | 142 | if (interval != null) { 143 | if (args.size() > 0) { 144 | if (args.get(0).equalsIgnoreCase("schedule")) { 145 | l.err("Could not use schedule command in a schedule!"); 146 | return false; 147 | } 148 | } 149 | events.add(new Event(args, interval, maxTime)); 150 | l.info("Event added. ID = " + (events.size() - 1)); 151 | return true; 152 | } else { 153 | l.err("Please provide interval for the event"); 154 | return false; 155 | } 156 | } else { 157 | return true; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/exceptions/SourceConfigException.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.exceptions; 2 | 3 | public class SourceConfigException extends RuntimeException { 4 | public SourceConfigException() { 5 | } 6 | 7 | public SourceConfigException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/exceptions/SourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.exceptions; 2 | 3 | public class SourceNotFoundException extends RuntimeException { 4 | public SourceNotFoundException(String arg0) { 5 | super(arg0); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/exceptions/UnsupportedReturnTypeException.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.exceptions; 2 | 3 | public class UnsupportedReturnTypeException extends RuntimeException { 4 | public UnsupportedReturnTypeException() { 5 | super("Unsupported return type"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/ConfigManager.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import com.alibaba.fastjson2.JSONObject; 5 | import com.alibaba.fastjson2.JSONWriter; 6 | import org.apache.log4j.Logger; 7 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 8 | 9 | import java.io.*; 10 | import java.util.HashSet; 11 | import java.util.Objects; 12 | import java.util.Set; 13 | 14 | public class ConfigManager { 15 | private static final HashSet accounts = new HashSet<>(); 16 | private static JSONObject config; 17 | public static final Logger logger = Logger.getLogger(GUI.class); 18 | private static PixivAccount selectedAccount; 19 | 20 | public static PixivAccount getSelectedAccount() { 21 | return selectedAccount; 22 | } 23 | 24 | public static void setSelectedAccount(PixivAccount selectedAccount) { 25 | ConfigManager.selectedAccount = selectedAccount; 26 | } 27 | 28 | public static JSONObject getConfig() { 29 | return Objects.requireNonNull(config); 30 | } 31 | 32 | public static Object getValue(String key, Object defaultValue) { 33 | return Objects.requireNonNullElse(config.get(key), defaultValue); 34 | } 35 | 36 | public static double getDoubleIfExist(String key, double defaultValue) { 37 | if (config.containsKey(key)) return config.getDouble(key); 38 | return defaultValue; 39 | } 40 | 41 | public static void readConfig() throws IOException { 42 | File f = new File("config.json"); 43 | if (!f.exists()) { 44 | f.createNewFile(); 45 | config = new JSONObject(); 46 | } else { 47 | StringBuilder sb = new StringBuilder(); 48 | BufferedReader reader = new BufferedReader(new FileReader(f)); 49 | String line; 50 | while ((line = reader.readLine()) != null) sb.append(line); 51 | reader.close(); 52 | config = Objects.requireNonNullElse(JSONObject.parse(sb.toString()), new JSONObject()); 53 | logger.info("Loaded configuration from config.json"); 54 | } 55 | 56 | f = new File("accounts.json"); 57 | if (!f.exists()) { 58 | f.createNewFile(); 59 | } else { 60 | StringBuilder sb = new StringBuilder(); 61 | BufferedReader reader = new BufferedReader(new FileReader(f)); 62 | String line; 63 | while ((line = reader.readLine()) != null) sb.append(line); 64 | reader.close(); 65 | JSONArray acc = Objects.requireNonNullElse(JSONArray.parse(sb.toString()), new JSONArray()); 66 | for (int i = 0; i < acc.size(); i++) { 67 | accounts.add(acc.getJSONObject(i).to(PixivAccount.class)); 68 | } 69 | 70 | logger.info("Read " + accounts.size() + " accounts from accounts.json"); 71 | } 72 | } 73 | 74 | public static void saveConfig() throws IOException { 75 | BufferedWriter bw = new BufferedWriter(new FileWriter("config.json")); 76 | bw.write(config.toString(JSONWriter.Feature.PrettyFormat)); 77 | bw.close(); 78 | 79 | JSONArray arr = new JSONArray(); 80 | bw = new BufferedWriter(new FileWriter("accounts.json")); 81 | arr.addAll(accounts); 82 | bw.write(arr.toString(JSONWriter.Feature.PrettyFormat)); 83 | bw.close(); 84 | 85 | logger.info("Configuration saved"); 86 | } 87 | 88 | public static Set getAccounts() { 89 | return accounts; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/Notice.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui; 2 | 3 | import javafx.animation.Interpolator; 4 | import javafx.animation.KeyFrame; 5 | import javafx.animation.KeyValue; 6 | import javafx.animation.Timeline; 7 | import javafx.application.Platform; 8 | import javafx.fxml.FXMLLoader; 9 | import javafx.scene.control.Label; 10 | import javafx.scene.layout.AnchorPane; 11 | import javafx.scene.layout.Background; 12 | import javafx.scene.layout.BackgroundFill; 13 | import javafx.scene.layout.Pane; 14 | import javafx.scene.paint.Color; 15 | import javafx.util.Duration; 16 | 17 | import java.io.IOException; 18 | import java.util.Objects; 19 | 20 | public class Notice { 21 | private static final Color SU_BG = Color.rgb(212, 237, 218); 22 | private static final Color SU_TEXT = Color.rgb(46, 107, 60); 23 | private static final Color ERR_BG = Color.rgb(248, 215, 218); 24 | private static final Color ERR_TEXT = Color.rgb(127, 46, 53); 25 | @javafx.fxml.FXML 26 | AnchorPane pane; 27 | @javafx.fxml.FXML 28 | Label msgLbl; 29 | 30 | public static Notice getInstance(String message, Pane parent) { 31 | FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(ResourceLoader.loadURL("fxml/Notice.fxml"))); 32 | try { 33 | loader.load(); 34 | } catch (IOException e) { 35 | throw new RuntimeException(e); 36 | } 37 | Notice controller = loader.getController(); 38 | double d = (parent.getWidth() / 2) - message.length() * 20; 39 | 40 | controller.getLabel().setText(message); 41 | 42 | AnchorPane.setTopAnchor(controller.getPane(), 10.0); 43 | AnchorPane.setLeftAnchor(controller.getPane(), d); 44 | AnchorPane.setRightAnchor(controller.getPane(), d); 45 | parent.getChildren().addAll(controller.getPane()); 46 | 47 | controller.getPane().setVisible(false); 48 | 49 | return controller; 50 | } 51 | 52 | public static void showSuccess(String message, Pane parent) { 53 | FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(ResourceLoader.loadURL("fxml/Notice.fxml"))); 54 | try { 55 | loader.load(); 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | Notice controller = loader.getController(); 60 | controller.setColors(SU_BG, SU_TEXT); 61 | AnchorPane p = controller.getPane(); 62 | double d = (parent.getWidth() / 2) - message.length() * 20; 63 | controller.getLabel().setText(message); 64 | AnchorPane.setTopAnchor(p, 10.0); 65 | AnchorPane.setLeftAnchor(p, d); 66 | AnchorPane.setRightAnchor(p, d); 67 | Platform.runLater(() -> { 68 | parent.getChildren().addAll(p); 69 | p.setVisible(true); 70 | p.setTranslateY(-100); 71 | p.setOpacity(0); 72 | }); 73 | p.setVisible(true); 74 | p.setTranslateY(-100); 75 | p.setOpacity(0); 76 | KeyValue kv1 = new KeyValue(p.translateYProperty(), 0, Interpolator.EASE_OUT); 77 | KeyValue kv2 = new KeyValue(p.opacityProperty(), 1, Interpolator.EASE_OUT); 78 | KeyFrame kf1 = new KeyFrame(Duration.millis(200), kv1, kv2); 79 | KeyFrame kf12 = new KeyFrame(Duration.millis(1600), kv1, kv2); 80 | 81 | KeyValue kv12 = new KeyValue(p.opacityProperty(), 0, Interpolator.EASE_OUT); 82 | KeyFrame kf11 = new KeyFrame(Duration.millis(1800), kv12); 83 | 84 | Timeline tl = new Timeline(kf1, kf12, kf11); 85 | tl.setOnFinished((e) -> parent.getChildren().remove(p)); 86 | tl.play(); 87 | } 88 | 89 | public static void showError(String message, Pane parent) { 90 | FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(ResourceLoader.loadURL("fxml/Notice.fxml"))); 91 | try { 92 | loader.load(); 93 | } catch (IOException e) { 94 | throw new RuntimeException(e); 95 | } 96 | Notice controller = loader.getController(); 97 | controller.setColors(ERR_BG, ERR_TEXT); 98 | AnchorPane p = controller.getPane(); 99 | double d = (parent.getWidth() / 2) - message.length() * 20; 100 | controller.getLabel().setText(message); 101 | AnchorPane.setTopAnchor(p, 10.0); 102 | AnchorPane.setLeftAnchor(p, d); 103 | AnchorPane.setRightAnchor(p, d); 104 | 105 | Platform.runLater(() -> { 106 | parent.getChildren().addAll(p); 107 | p.setVisible(true); 108 | p.setTranslateY(-100); 109 | p.setOpacity(0); 110 | }); 111 | 112 | KeyValue kv1 = new KeyValue(p.translateYProperty(), 0, Interpolator.EASE_OUT); 113 | KeyValue kv2 = new KeyValue(p.opacityProperty(), 1, Interpolator.EASE_OUT); 114 | KeyFrame kf1 = new KeyFrame(Duration.millis(200), kv1, kv2); 115 | KeyFrame kf12 = new KeyFrame(Duration.millis(1600), kv1, kv2); 116 | 117 | KeyValue kv12 = new KeyValue(p.opacityProperty(), 0, Interpolator.EASE_OUT); 118 | KeyFrame kf11 = new KeyFrame(Duration.millis(1800), kv12); 119 | 120 | Timeline tl = new Timeline(kf1, kf12, kf11); 121 | tl.setOnFinished((e) -> parent.getChildren().remove(p)); 122 | tl.play(); 123 | } 124 | 125 | private AnchorPane getPane() { 126 | return pane; 127 | } 128 | 129 | private Label getLabel() { 130 | return msgLbl; 131 | } 132 | 133 | public void setColors(Color backColor, Color textColor) { 134 | pane.setBackground(new Background(new BackgroundFill(backColor, null, null))); 135 | msgLbl.setTextFill(textColor); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/ResourceLoader.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui; 2 | 3 | import java.io.InputStream; 4 | import java.net.URL; 5 | 6 | public class ResourceLoader { 7 | public static URL loadURL(String fileName) { 8 | return ResourceLoader.class.getResource(fileName); 9 | } 10 | 11 | public static InputStream loadStream(String fileName) { 12 | return ResourceLoader.class.getResourceAsStream(fileName); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/MyPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base; 2 | 3 | import javafx.animation.Interpolator; 4 | import javafx.animation.TranslateTransition; 5 | import javafx.application.Platform; 6 | import javafx.fxml.FXML; 7 | import javafx.fxml.Initializable; 8 | import javafx.scene.layout.AnchorPane; 9 | import javafx.util.Duration; 10 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 11 | import xyz.zcraft.acgpicdownload.gui.GUI; 12 | 13 | import java.net.URL; 14 | import java.util.ResourceBundle; 15 | 16 | public class MyPane implements Initializable { 17 | private final TranslateTransition tt = new TranslateTransition(); 18 | private final TranslateTransition tt2 = new TranslateTransition(); 19 | protected GUI gui; 20 | @FXML 21 | protected AnchorPane mainPane; 22 | 23 | public void show() { 24 | tt.stop(); 25 | tt.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 26 | tt.setFromY(mainPane.getHeight()); 27 | tt.setOnFinished((e) -> tt2.play()); 28 | tt.setToY(-10); 29 | tt2.setFromY(-10); 30 | tt2.setOnFinished(null); 31 | tt2.setToY(0); 32 | mainPane.setVisible(true); 33 | tt.play(); 34 | } 35 | 36 | public void hide() { 37 | tt.stop(); 38 | tt.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 39 | tt.setFromY(0); 40 | tt.setToY(mainPane.getHeight()); 41 | mainPane.setVisible(true); 42 | tt.setOnFinished((e) -> Platform.runLater(() -> mainPane.setVisible(false))); 43 | tt.play(); 44 | } 45 | 46 | @Override 47 | public void initialize(URL location, ResourceBundle resources) { 48 | AnchorPane.setTopAnchor(mainPane, 0d); 49 | AnchorPane.setBottomAnchor(mainPane, 0d); 50 | AnchorPane.setLeftAnchor(mainPane, 0d); 51 | AnchorPane.setRightAnchor(mainPane, 0d); 52 | mainPane.setVisible(false); 53 | 54 | tt.setNode(mainPane); 55 | tt.setAutoReverse(true); 56 | tt.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 57 | tt.setDuration(Duration.millis(5)); 58 | tt.setInterpolator(Interpolator.EASE_BOTH); 59 | 60 | tt2.setNode(mainPane); 61 | tt2.setAutoReverse(true); 62 | tt2.setRate(0.05 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 63 | tt2.setDuration(Duration.millis(5)); 64 | tt2.setInterpolator(Interpolator.EASE_BOTH); 65 | } 66 | 67 | public GUI getGui() { 68 | return gui; 69 | } 70 | 71 | public void setGui(GUI gui) { 72 | this.gui = gui; 73 | mainPane.maxWidthProperty().bind(gui.mainStage.widthProperty()); 74 | mainPane.maxHeightProperty().bind(gui.mainStage.heightProperty()); 75 | } 76 | 77 | public AnchorPane getMainPane() { 78 | return mainPane; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/argpanes/ArgumentPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base.argpanes; 2 | 3 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 4 | 5 | public interface ArgumentPane { 6 | T getValueString(); 7 | 8 | String getName(); 9 | 10 | Argument getArgument(); 11 | 12 | void update(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/argpanes/IntegerArgumentPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base.argpanes; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXSpinner; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.control.Label; 6 | import javafx.scene.layout.HBox; 7 | import xyz.zcraft.acgpicdownload.gui.ResourceLoader; 8 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 9 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.IntegerArgument; 10 | 11 | import java.io.IOException; 12 | 13 | public class IntegerArgumentPane implements ArgumentPane { 14 | HBox pane; 15 | IntegerArgument arg; 16 | @javafx.fxml.FXML 17 | private Label argNameLabel; 18 | @javafx.fxml.FXML 19 | private MFXSpinner argSpinner; 20 | 21 | public IntegerArgumentPane() { 22 | } 23 | 24 | // TODO Finish this 25 | public static IntegerArgumentPane getInstance(IntegerArgument arg) throws IOException { 26 | FXMLLoader lo = new FXMLLoader(ResourceLoader.loadURL("fxml/StringArgumentPane.fxml")); 27 | lo.load(); 28 | IntegerArgumentPane a = lo.getController(); 29 | a.setArg(arg); 30 | // a.getArgSpinner().getSpinnerModel(). 31 | return a; 32 | } 33 | 34 | public HBox getPane() { 35 | return pane; 36 | } 37 | 38 | public void setPane(HBox pane) { 39 | this.pane = pane; 40 | } 41 | 42 | public Label getArgNameLabel() { 43 | return argNameLabel; 44 | } 45 | 46 | public void setArgNameLabel(Label argNameLabel) { 47 | this.argNameLabel = argNameLabel; 48 | } 49 | 50 | public MFXSpinner getArgSpinner() { 51 | return argSpinner; 52 | } 53 | 54 | public void setArgSpinner(MFXSpinner argSpinner) { 55 | this.argSpinner = argSpinner; 56 | } 57 | 58 | public IntegerArgument getArg() { 59 | return arg; 60 | } 61 | 62 | public void setArg(IntegerArgument arg) { 63 | this.arg = arg; 64 | } 65 | 66 | @Override 67 | public Integer getValueString() { 68 | return null; 69 | } 70 | 71 | @Override 72 | public String getName() { 73 | return null; 74 | } 75 | 76 | @Override 77 | public Argument getArgument() { 78 | return null; 79 | } 80 | 81 | @Override 82 | public void update() { 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/argpanes/LimitedIntegerArgumentPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base.argpanes; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXSlider; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.control.Label; 6 | import javafx.scene.layout.HBox; 7 | import xyz.zcraft.acgpicdownload.gui.ResourceLoader; 8 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 9 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.LimitedIntegerArgument; 10 | 11 | import java.io.IOException; 12 | 13 | public class LimitedIntegerArgumentPane implements ArgumentPane { 14 | LimitedIntegerArgument arg; 15 | 16 | @javafx.fxml.FXML 17 | private Label argNameLabel; 18 | @javafx.fxml.FXML 19 | private MFXSlider argSlider; 20 | @javafx.fxml.FXML 21 | private HBox pane; 22 | 23 | public LimitedIntegerArgumentPane() { 24 | } 25 | 26 | public static LimitedIntegerArgumentPane getInstance(LimitedIntegerArgument arg) throws IOException { 27 | FXMLLoader lo = new FXMLLoader(ResourceLoader.loadURL("fxml/LimitedIntegerArgumentPane.fxml")); 28 | lo.load(); 29 | LimitedIntegerArgumentPane a = lo.getController(); 30 | a.setArg(arg); 31 | a.getArgNameLabel().setText(arg.getName()); 32 | if (arg.getLimit().isHasMax()) { 33 | a.getArgSlider().setMax(arg.getLimit().getMaxValue()); 34 | } 35 | if (arg.getLimit().isHasMin()) { 36 | a.getArgSlider().setMin(arg.getLimit().getMinValue()); 37 | } 38 | if (arg.getLimit().isHasStep()) { 39 | a.getArgSlider().setTickUnit(arg.getLimit().getStep()); 40 | } 41 | a.getArgSlider().setValue(arg.getValue()); 42 | return a; 43 | } 44 | 45 | public LimitedIntegerArgument getArg() { 46 | return arg; 47 | } 48 | 49 | public void setArg(LimitedIntegerArgument arg) { 50 | this.arg = arg; 51 | } 52 | 53 | public HBox getPane() { 54 | return pane; 55 | } 56 | 57 | public void setPane(HBox pane) { 58 | this.pane = pane; 59 | } 60 | 61 | public Label getArgNameLabel() { 62 | return argNameLabel; 63 | } 64 | 65 | public void setArgNameLabel(Label argNameLabel) { 66 | this.argNameLabel = argNameLabel; 67 | } 68 | 69 | public MFXSlider getArgSlider() { 70 | return argSlider; 71 | } 72 | 73 | public void setArgSlider(MFXSlider argSlider) { 74 | this.argSlider = argSlider; 75 | } 76 | 77 | @Override 78 | public Integer getValueString() { 79 | return (int) argSlider.getValue(); 80 | } 81 | 82 | @Override 83 | public String getName() { 84 | return arg.getName(); 85 | } 86 | 87 | @Override 88 | public Argument getArgument() { 89 | arg.set((int) argSlider.getValue()); 90 | return arg; 91 | } 92 | 93 | public void update() { 94 | argSlider.setValue(arg.getValue()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/argpanes/LimitedStringArgumentPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base.argpanes; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXComboBox; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.layout.VBox; 6 | import xyz.zcraft.acgpicdownload.gui.ResourceLoader; 7 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 8 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.LimitedStringArgument; 9 | 10 | import java.io.IOException; 11 | 12 | public class LimitedStringArgumentPane implements ArgumentPane { 13 | protected LimitedStringArgument arg; 14 | @javafx.fxml.FXML 15 | protected VBox pane; 16 | @javafx.fxml.FXML 17 | protected MFXComboBox argCombo; 18 | 19 | public LimitedStringArgumentPane() { 20 | } 21 | 22 | public static LimitedStringArgumentPane getInstance(LimitedStringArgument arg) throws IOException { 23 | FXMLLoader lo = new FXMLLoader(ResourceLoader.loadURL("fxml/LimitedStringArgumentPane.fxml")); 24 | lo.load(); 25 | LimitedStringArgumentPane a = lo.getController(); 26 | a.setArg(arg); 27 | a.getArgCombo().setFloatingText(arg.getName()); 28 | a.getArgCombo().getItems().addAll(arg.getValidValues()); 29 | a.getArgCombo().getSelectionModel().selectItem(arg.getValue()); 30 | return a; 31 | } 32 | 33 | public LimitedStringArgument getArg() { 34 | return arg; 35 | } 36 | 37 | protected void setArg(LimitedStringArgument arg) { 38 | this.arg = arg; 39 | } 40 | 41 | public VBox getPane() { 42 | return pane; 43 | } 44 | 45 | protected void setPane(VBox pane) { 46 | this.pane = pane; 47 | } 48 | 49 | public MFXComboBox getArgCombo() { 50 | return argCombo; 51 | } 52 | 53 | protected void setArgCombo(MFXComboBox argCombo) { 54 | this.argCombo = argCombo; 55 | } 56 | 57 | @Override 58 | public String getValueString() { 59 | return argCombo.getValue(); 60 | } 61 | 62 | @Override 63 | public String getName() { 64 | return arg.getName(); 65 | } 66 | 67 | @Override 68 | public Argument getArgument() { 69 | if (argCombo.getValue() == null) { 70 | arg.set(arg.getValue()); 71 | } else { 72 | arg.set(argCombo.getValue()); 73 | } 74 | 75 | return arg; 76 | } 77 | 78 | public void update() { 79 | argCombo.getSelectionModel().selectItem(arg.getValue()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/base/argpanes/StringArgumentPane.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.base.argpanes; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXTextField; 4 | import javafx.fxml.FXMLLoader; 5 | import javafx.scene.layout.VBox; 6 | import xyz.zcraft.acgpicdownload.gui.ResourceLoader; 7 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 8 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.StringArgument; 9 | 10 | import java.io.IOException; 11 | 12 | public class StringArgumentPane implements ArgumentPane { 13 | protected StringArgument arg; 14 | @javafx.fxml.FXML 15 | protected MFXTextField argField; 16 | @javafx.fxml.FXML 17 | protected VBox pane; 18 | 19 | public StringArgumentPane() { 20 | } 21 | 22 | public static StringArgumentPane getInstance(StringArgument arg) throws IOException { 23 | FXMLLoader lo = new FXMLLoader(ResourceLoader.loadURL("fxml/StringArgumentPane.fxml")); 24 | lo.load(); 25 | StringArgumentPane a = lo.getController(); 26 | a.setArg(arg); 27 | a.getArgField().setFloatingText(arg.getName()); 28 | return a; 29 | } 30 | 31 | protected StringArgument getArg() { 32 | return arg; 33 | } 34 | 35 | protected void setArg(StringArgument arg) { 36 | this.arg = arg; 37 | } 38 | 39 | public VBox getPane() { 40 | return pane; 41 | } 42 | 43 | protected void setPane(VBox pane) { 44 | this.pane = pane; 45 | } 46 | 47 | public MFXTextField getArgField() { 48 | return argField; 49 | } 50 | 51 | protected void setArgField(MFXTextField argField) { 52 | this.argField = argField; 53 | } 54 | 55 | @Override 56 | public String getValueString() { 57 | return argField.getText(); 58 | } 59 | 60 | @Override 61 | public String getName() { 62 | return arg.getName(); 63 | } 64 | 65 | @Override 66 | public Argument getArgument() { 67 | arg.set(argField.getText()); 68 | return arg; 69 | } 70 | 71 | public void update() { 72 | argField.setText(arg.getValue()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/ErrorPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXButton; 4 | import javafx.animation.FadeTransition; 5 | import javafx.application.Platform; 6 | import javafx.fxml.FXML; 7 | import javafx.fxml.FXMLLoader; 8 | import javafx.geometry.Rectangle2D; 9 | import javafx.scene.control.Label; 10 | import javafx.scene.image.Image; 11 | import javafx.scene.image.ImageView; 12 | import javafx.scene.layout.AnchorPane; 13 | import javafx.scene.layout.Pane; 14 | import javafx.util.Duration; 15 | import xyz.zcraft.acgpicdownload.gui.Notice; 16 | import xyz.zcraft.acgpicdownload.gui.ResourceLoader; 17 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 18 | 19 | import java.awt.*; 20 | import java.awt.datatransfer.StringSelection; 21 | import java.io.IOException; 22 | import java.net.URI; 23 | import java.net.URISyntaxException; 24 | import java.util.Locale; 25 | import java.util.Objects; 26 | 27 | public class ErrorPaneController { 28 | @javafx.fxml.FXML 29 | private AnchorPane errorPane; 30 | @javafx.fxml.FXML 31 | private Label errorLabel; 32 | @javafx.fxml.FXML 33 | private MFXButton errorOkBtn; 34 | @javafx.fxml.FXML 35 | private ImageView bg; 36 | private Pane parent; 37 | 38 | public static ErrorPaneController getInstance(Pane parent) { 39 | FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(ResourceLoader.loadURL("fxml/ErrorPane.fxml")), ResourceBundleUtil.getResource()); 40 | try { 41 | loader.load(); 42 | } catch (IOException e) { 43 | throw new RuntimeException(e); 44 | } 45 | return ((ErrorPaneController) loader.getController()).setParent(parent); 46 | } 47 | 48 | public ErrorPaneController setParent(Pane parent) { 49 | this.parent = parent; 50 | return this; 51 | } 52 | 53 | public AnchorPane getErrorPane() { 54 | return errorPane; 55 | } 56 | 57 | @javafx.fxml.FXML 58 | private void copyErrorMsg() { 59 | Toolkit.getDefaultToolkit().getSystemClipboard() 60 | .setContents(new StringSelection(errorLabel.getText()), null); 61 | Notice.showSuccess(ResourceBundleUtil.getString("gui.fetch.table.copy"), parent); 62 | } 63 | 64 | public void hide() { 65 | FadeTransition ft = new FadeTransition(); 66 | ft.setNode(errorPane); 67 | ft.setFromValue(1); 68 | ft.setToValue(0); 69 | ft.setAutoReverse(false); 70 | ft.setRate(0.05); 71 | ft.setDuration(Duration.millis(5)); 72 | ft.setOnFinished(actionEvent -> Platform.runLater(() -> errorPane.setVisible(false))); 73 | 74 | ft.play(); 75 | } 76 | 77 | public void setBlur(Image img) { 78 | bg.setImage(img); 79 | } 80 | 81 | public void show() { 82 | bg.fitWidthProperty().bind(errorPane.widthProperty()); 83 | bg.fitHeightProperty().bind(errorPane.heightProperty()); 84 | bg.setViewport(new Rectangle2D(0, 0, errorPane.getWidth(), errorPane.getHeight())); 85 | 86 | FadeTransition ft = new FadeTransition(); 87 | ft.setNode(errorPane); 88 | ft.setFromValue(0); 89 | ft.setToValue(1); 90 | ft.setAutoReverse(false); 91 | ft.setRate(0.05); 92 | ft.setDuration(Duration.millis(5)); 93 | 94 | errorPane.setVisible(true); 95 | ft.play(); 96 | } 97 | 98 | public void setErrorMessage(String message) { 99 | errorLabel.setText(message); 100 | } 101 | 102 | public void errorOkBtnOnAction() { 103 | hide(); 104 | } 105 | 106 | @FXML 107 | public void openFAQ() throws IOException, URISyntaxException { 108 | if (Locale.getDefault().equals(Locale.CHINA) || Locale.getDefault().equals(Locale.TAIWAN)) { 109 | java.awt.Desktop.getDesktop().browse(new URI("https://github.com/zxzxy/ACGPicDownload/wiki/常见问题")); 110 | } else { 111 | java.awt.Desktop.getDesktop().browse(new URI("https://github.com/zxzxy/ACGPicDownload/wiki/FAQ")); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/MenuPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXButton; 4 | import javafx.animation.Interpolator; 5 | import javafx.animation.TranslateTransition; 6 | import javafx.fxml.Initializable; 7 | import javafx.scene.control.Label; 8 | import javafx.scene.layout.AnchorPane; 9 | import javafx.scene.layout.VBox; 10 | import javafx.util.Duration; 11 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 12 | import xyz.zcraft.acgpicdownload.gui.GUI; 13 | 14 | import java.net.URL; 15 | import java.util.Calendar; 16 | import java.util.ResourceBundle; 17 | 18 | public class MenuPaneController implements Initializable { 19 | final TranslateTransition tt = new TranslateTransition(); 20 | @javafx.fxml.FXML 21 | private AnchorPane mainPane; 22 | 23 | public AnchorPane getMainPane() { 24 | return mainPane; 25 | } 26 | 27 | @javafx.fxml.FXML 28 | private VBox controls; 29 | @javafx.fxml.FXML 30 | private MFXButton fetchBtn; 31 | private GUI gui; 32 | @javafx.fxml.FXML 33 | private Label welcomeLabel; 34 | 35 | public GUI getGui() { 36 | return gui; 37 | } 38 | public void setGui(GUI gui) { 39 | this.gui = gui; 40 | } 41 | public void showMain() { 42 | tt.stop(); 43 | tt.setFromX(0 - controls.getWidth()); 44 | tt.setToX(0); 45 | tt.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 46 | tt.setOnFinished(null); 47 | mainPane.setVisible(true); 48 | controls.setVisible(true); 49 | tt.play(); 50 | } 51 | 52 | public void hideMain() { 53 | tt.stop(); 54 | tt.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 55 | tt.setFromX(0); 56 | tt.setToX(0 - controls.getWidth()); 57 | tt.setOnFinished((e) -> mainPane.setVisible(false)); 58 | tt.play(); 59 | } 60 | 61 | @javafx.fxml.FXML 62 | public void fetchBtnOnAction() { 63 | hideMain(); 64 | gui.openFetchPane(); 65 | } 66 | 67 | @Override 68 | public void initialize(URL url, ResourceBundle resourceBundle) { 69 | controls.setVisible(false); 70 | mainPane.setVisible(false); 71 | 72 | tt.setNode(controls); 73 | tt.setAutoReverse(false); 74 | tt.setRate(0.008 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 75 | tt.setDuration(Duration.millis(3)); 76 | tt.setInterpolator(Interpolator.EASE_BOTH); 77 | 78 | Calendar c = Calendar.getInstance(); 79 | int i = c.get(Calendar.HOUR_OF_DAY); 80 | if (7 < i && i <= 12) { 81 | welcomeLabel.setText(resourceBundle.getString("gui.welcome.greet.morn")); 82 | } else if (12 < i && i <= 15) { 83 | welcomeLabel.setText(resourceBundle.getString("gui.welcome.greet.noon")); 84 | } else if (15 < i && i <= 20) { 85 | welcomeLabel.setText(resourceBundle.getString("gui.welcome.greet.afternoon")); 86 | } else if (20 < i || i < 7) { 87 | welcomeLabel.setText(resourceBundle.getString("gui.welcome.greet.night")); 88 | } 89 | } 90 | 91 | public void openSettingsPane() { 92 | hideMain(); 93 | gui.openSettingsPane(); 94 | } 95 | 96 | public void openPixivPane() { 97 | hideMain(); 98 | gui.pixivPaneController.openPixivPane(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivDiscoveryPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXComboBox; 4 | import io.github.palexdev.materialfx.controls.MFXSlider; 5 | import io.github.palexdev.materialfx.font.MFXFontIcon; 6 | import javafx.application.Platform; 7 | import javafx.fxml.FXML; 8 | import org.apache.log4j.Logger; 9 | import xyz.zcraft.acgpicdownload.Main; 10 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 11 | import xyz.zcraft.acgpicdownload.gui.Notice; 12 | import xyz.zcraft.acgpicdownload.gui.base.PixivFetchPane; 13 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 14 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 15 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivArtwork; 16 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivFetchUtil; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.ResourceBundle; 23 | 24 | public class PixivDiscoveryPaneController extends PixivFetchPane { 25 | @javafx.fxml.FXML 26 | private MFXSlider maxCountSlider; 27 | @javafx.fxml.FXML 28 | private MFXSlider relatedDepthSlider; 29 | @javafx.fxml.FXML 30 | private MFXComboBox modeCombo; 31 | @Override 32 | public void initialize(URL url, ResourceBundle resourceBundle) { 33 | super.initialize(url, resourceBundle); 34 | 35 | backBtn.setText(""); 36 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 37 | 38 | modeCombo.getItems().addAll( 39 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.all"), 40 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.safe"), 41 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.adult") 42 | ); 43 | 44 | modeCombo.getSelectionModel().selectFirst(); 45 | } 46 | 47 | public static final Logger logger = Logger.getLogger(PixivDiscoveryPaneController.class); 48 | 49 | @FXML 50 | @Override 51 | public void fetchBtnOnAction() { 52 | loadingPane.setVisible(true); 53 | operationLabel.setText(ResourceBundleUtil.getString("")); 54 | ft.stop(); 55 | ft.setFromValue(0); 56 | ft.setToValue(1); 57 | ft.setOnFinished(null); 58 | ft.play(); 59 | 60 | new Thread(() -> { 61 | try { 62 | long start = System.currentTimeMillis(); 63 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 64 | logger.info("Fetching discovery with account " + selectedAccount.getName()); 65 | 66 | Platform.runLater(() -> { 67 | operationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 68 | subOperationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 69 | }); 70 | 71 | List pixivArtworks = PixivFetchUtil.getDiscovery( 72 | modeCombo.getSelectedIndex(), 73 | (int) maxCountSlider.getValue(), 74 | selectedAccount.getCookie(), 75 | ConfigManager.getConfig().getString("proxyHost"), 76 | ConfigManager.getConfig().getInteger("proxyPort") 77 | ); 78 | 79 | logger.info("Fetched " + pixivArtworks.size() + " artworks, getting related artworks for " + relatedDepthSlider.getValue() + " times"); 80 | 81 | getRelated(pixivArtworks, (int) relatedDepthSlider.getValue(), getCookie(), subOperationLabel); 82 | 83 | Platform.runLater(() -> data.addAll(pixivArtworks)); 84 | Notice.showSuccess(String.format( 85 | Objects.requireNonNull(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetched")), 86 | pixivArtworks.size()), gui.mainPane); 87 | 88 | logger.info("Fetch completed successfully within " + (System.currentTimeMillis() - start) + " ms"); 89 | } catch (IOException e) { 90 | logger.error("Fetch discovery failed", e); 91 | Main.logError(e); 92 | gui.showError(e); 93 | } finally { 94 | ft.stop(); 95 | ft.setFromValue(1); 96 | ft.setToValue(0); 97 | ft.setOnFinished((e) -> loadingPane.setVisible(false)); 98 | ft.play(); 99 | } 100 | }).start(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivMenuPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXSlider; 4 | import io.github.palexdev.materialfx.controls.MFXToggleButton; 5 | import io.github.palexdev.materialfx.font.MFXFontIcon; 6 | import javafx.application.Platform; 7 | import javafx.fxml.FXML; 8 | import org.apache.log4j.Logger; 9 | import xyz.zcraft.acgpicdownload.Main; 10 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 11 | import xyz.zcraft.acgpicdownload.gui.Notice; 12 | import xyz.zcraft.acgpicdownload.gui.base.PixivFetchPane; 13 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 14 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 15 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivArtwork; 16 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivFetchUtil; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.ResourceBundle; 23 | 24 | public class PixivMenuPaneController extends PixivFetchPane { 25 | @javafx.fxml.FXML 26 | private MFXSlider maxCountSlider; 27 | @javafx.fxml.FXML 28 | private MFXSlider relatedDepthSlider; 29 | @javafx.fxml.FXML 30 | private MFXToggleButton fromFollowToggle; 31 | @javafx.fxml.FXML 32 | private MFXToggleButton fromRecToggle; 33 | @javafx.fxml.FXML 34 | private MFXToggleButton fromRecTagToggle; 35 | @javafx.fxml.FXML 36 | private MFXToggleButton fromRecUserToggle; 37 | @javafx.fxml.FXML 38 | private MFXToggleButton fromOtherToggle; 39 | 40 | @Override 41 | public void initialize(URL url, ResourceBundle resourceBundle) { 42 | super.initialize(url, resourceBundle); 43 | 44 | backBtn.setText(""); 45 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 46 | } 47 | 48 | public static final Logger logger = Logger.getLogger(PixivMenuPaneController.class); 49 | 50 | @FXML 51 | @Override 52 | public void fetchBtnOnAction() { 53 | loadingPane.setVisible(true); 54 | operationLabel.setText(ResourceBundleUtil.getString("")); 55 | ft.stop(); 56 | ft.setFromValue(0); 57 | ft.setToValue(1); 58 | ft.setOnFinished(null); 59 | ft.play(); 60 | 61 | new Thread(() -> { 62 | try { 63 | long start = System.currentTimeMillis(); 64 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 65 | logger.info("Fetching menu with account " + selectedAccount.getName()); 66 | 67 | Platform.runLater(() -> { 68 | operationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 69 | subOperationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 70 | }); 71 | 72 | List pixivArtworks = PixivFetchUtil.selectArtworks( 73 | PixivFetchUtil.fetchMenu( 74 | getCookie(), 75 | ConfigManager.getConfig().getString("proxyHost"), 76 | ConfigManager.getConfig().getInteger("proxyPort") 77 | ), 78 | (int) maxCountSlider.getValue(), 79 | fromFollowToggle.isSelected(), 80 | fromRecToggle.isSelected(), 81 | fromRecTagToggle.isSelected(), 82 | fromRecUserToggle.isSelected(), 83 | fromOtherToggle.isSelected() 84 | ); 85 | 86 | logger.info("Fetched " + pixivArtworks.size() + " artworks, getting related artworks for " + relatedDepthSlider.getValue() + " times"); 87 | 88 | getRelated(pixivArtworks, (int) relatedDepthSlider.getValue(), getCookie(), subOperationLabel); 89 | 90 | Platform.runLater(() -> data.addAll(pixivArtworks)); 91 | Notice.showSuccess(String.format(Objects.requireNonNull(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetched")), pixivArtworks.size()), gui.mainPane); 92 | 93 | logger.info("Fetch completed successfully within " + (System.currentTimeMillis() - start) + " ms"); 94 | } catch (IOException e) { 95 | logger.error("Fetch menu failed", e); 96 | Main.logError(e); 97 | gui.showError(e); 98 | } finally { 99 | ft.stop(); 100 | ft.setFromValue(1); 101 | ft.setToValue(0); 102 | ft.setOnFinished((e) -> loadingPane.setVisible(false)); 103 | ft.play(); 104 | } 105 | }).start(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import javafx.animation.Interpolator; 4 | import javafx.animation.TranslateTransition; 5 | import javafx.application.Platform; 6 | import javafx.fxml.FXML; 7 | import javafx.fxml.Initializable; 8 | import javafx.scene.control.Label; 9 | import javafx.scene.image.Image; 10 | import javafx.scene.image.ImageView; 11 | import javafx.scene.layout.AnchorPane; 12 | import javafx.scene.layout.VBox; 13 | import javafx.util.Duration; 14 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 15 | import xyz.zcraft.acgpicdownload.gui.GUI; 16 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 17 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 18 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivDownloadUtil; 19 | 20 | import java.io.IOException; 21 | import java.net.URL; 22 | import java.net.URLConnection; 23 | import java.util.ResourceBundle; 24 | 25 | public class PixivPaneController implements Initializable { 26 | final TranslateTransition ttP = new TranslateTransition(); 27 | @FXML 28 | private VBox fetchBtns; 29 | @FXML 30 | private ImageView userImg; 31 | @FXML 32 | private Label userNameLabel; 33 | @FXML 34 | private VBox controls; 35 | @javafx.fxml.FXML 36 | private AnchorPane mainPane; 37 | 38 | private GUI gui; 39 | 40 | public AnchorPane getMainPane() { 41 | return mainPane; 42 | } 43 | 44 | public GUI getGui() { 45 | return gui; 46 | } 47 | 48 | public void setGui(GUI gui) { 49 | this.gui = gui; 50 | } 51 | 52 | public void pixivMenuBtnOnAction() { 53 | closePixivPane(); 54 | gui.openPixivMenuPane(); 55 | } 56 | 57 | public void pixivDownloadBtnOnAction() { 58 | closePixivPane(); 59 | gui.openPixivDownloadPane(); 60 | } 61 | 62 | public void pixivBackBtnOnAction() { 63 | gui.menuPaneController.showMain(); 64 | closePixivPane(); 65 | } 66 | 67 | private void closePixivPane() { 68 | ttP.stop(); 69 | ttP.setFromX(0); 70 | ttP.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 71 | ttP.setToX(0 - controls.getWidth()); 72 | ttP.setOnFinished((e) -> mainPane.setVisible(false)); 73 | ttP.play(); 74 | } 75 | 76 | @javafx.fxml.FXML 77 | private void pixivDiscBtnOnAction() { 78 | closePixivPane(); 79 | gui.openPixivDiscPane(); 80 | } 81 | 82 | @FXML 83 | public void pixivUserBtnOnAction() { 84 | closePixivPane(); 85 | gui.openPixivUserPane(); 86 | } 87 | 88 | @FXML 89 | public void pixivRelatedBtnOnAction() { 90 | closePixivPane(); 91 | gui.openPixivRelatedPane(); 92 | } 93 | 94 | public void pixivRankingBtnOnAction() { 95 | closePixivPane(); 96 | gui.openPixivRankingPane(); 97 | } 98 | 99 | public void pixivSearchBtnOnAction() { 100 | closePixivPane(); 101 | gui.openPixivSearchPane(); 102 | } 103 | 104 | @Override 105 | public void initialize(URL location, ResourceBundle resources) { 106 | controls.setVisible(false); 107 | mainPane.setVisible(false); 108 | 109 | ttP.setNode(controls); 110 | ttP.setAutoReverse(false); 111 | ttP.setRate(0.008 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 112 | ttP.setDuration(Duration.millis(3)); 113 | ttP.setInterpolator(Interpolator.EASE_BOTH); 114 | } 115 | 116 | public void openPixivPane() { 117 | reloadAccount(); 118 | ttP.stop(); 119 | ttP.setFromX(0 - controls.getWidth()); 120 | ttP.setRate(0.01 * ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 121 | ttP.setToX(0); 122 | ttP.setOnFinished(null); 123 | mainPane.setVisible(true); 124 | controls.setVisible(true); 125 | ttP.play(); 126 | } 127 | 128 | public void openAccountManager() { 129 | gui.openPixivAccountPane(); 130 | } 131 | 132 | public void reloadAccount() { 133 | new Thread(() -> { 134 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 135 | if (selectedAccount != null) { 136 | Platform.runLater(() -> { 137 | userNameLabel.setText(selectedAccount.getName()); 138 | fetchBtns.setDisable(false); 139 | }); 140 | try { 141 | URLConnection urlConnection = new URL(selectedAccount.getProfileImg()).openConnection(); 142 | urlConnection.setRequestProperty("Referer", PixivDownloadUtil.REFERER); 143 | Image image = new Image(urlConnection.getInputStream()); 144 | Platform.runLater(() -> userImg.setImage(image)); 145 | } catch (IOException e) { 146 | e.printStackTrace(); 147 | } 148 | } else { 149 | Platform.runLater(() -> { 150 | userNameLabel.setText(ResourceBundleUtil.getString("gui.pixiv.account.notLogin")); 151 | userImg.setImage(null); 152 | fetchBtns.setDisable(true); 153 | }); 154 | } 155 | }).start(); 156 | 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivRelatedPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXSlider; 4 | import io.github.palexdev.materialfx.controls.MFXTextField; 5 | import io.github.palexdev.materialfx.font.MFXFontIcon; 6 | import javafx.application.Platform; 7 | import javafx.fxml.FXML; 8 | import org.apache.log4j.Logger; 9 | import xyz.zcraft.acgpicdownload.Main; 10 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 11 | import xyz.zcraft.acgpicdownload.gui.Notice; 12 | import xyz.zcraft.acgpicdownload.gui.base.PixivFetchPane; 13 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 14 | import xyz.zcraft.acgpicdownload.util.pixivutils.From; 15 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 16 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivArtwork; 17 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivFetchUtil; 18 | 19 | import java.io.IOException; 20 | import java.net.URL; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.Objects; 24 | import java.util.ResourceBundle; 25 | 26 | public class PixivRelatedPaneController extends PixivFetchPane { 27 | @javafx.fxml.FXML 28 | private MFXTextField idField; 29 | @javafx.fxml.FXML 30 | private MFXSlider relatedDepthSlider; 31 | 32 | @Override 33 | public void initialize(URL url, ResourceBundle resourceBundle) { 34 | super.initialize(url, resourceBundle); 35 | 36 | backBtn.setText(""); 37 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 38 | } 39 | 40 | public static final Logger logger = Logger.getLogger(PixivRelatedPaneController.class); 41 | 42 | @FXML 43 | @Override 44 | public void fetchBtnOnAction() { 45 | if (idField.getText().startsWith("https://www.pixiv.net/artworks/")) 46 | idField.setText(idField.getText().substring(idField.getText().lastIndexOf("/") + 1)); 47 | loadingPane.setVisible(true); 48 | operationLabel.setText(ResourceBundleUtil.getString("")); 49 | ft.stop(); 50 | ft.setFromValue(0); 51 | ft.setToValue(1); 52 | ft.setOnFinished(null); 53 | ft.play(); 54 | 55 | new Thread(() -> { 56 | try { 57 | long start = System.currentTimeMillis(); 58 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 59 | logger.info("Fetching related with account " + selectedAccount.getName()); 60 | 61 | Platform.runLater(() -> { 62 | operationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 63 | subOperationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 64 | }); 65 | 66 | PixivArtwork artwork = PixivFetchUtil.getArtwork( 67 | idField.getText(), 68 | getCookie(), 69 | ConfigManager.getConfig().getString("proxyHost"), 70 | ConfigManager.getConfig().getInteger("proxyPort") 71 | ); 72 | artwork.setFrom(From.Spec); 73 | LinkedList pixivArtworks = new LinkedList<>(List.of(artwork)); 74 | 75 | getRelated(pixivArtworks, (int) relatedDepthSlider.getValue(), getCookie(), subOperationLabel); 76 | Platform.runLater(() -> data.addAll(pixivArtworks)); 77 | Notice.showSuccess(String.format( 78 | Objects.requireNonNull(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetched")), 79 | pixivArtworks.size()), gui.mainPane); 80 | logger.info("Fetch completed successfully within " + (System.currentTimeMillis() - start) + " ms"); 81 | } catch (IOException e) { 82 | Main.logError(e); 83 | gui.showError(e); 84 | } finally { 85 | ft.stop(); 86 | ft.setFromValue(1); 87 | ft.setToValue(0); 88 | ft.setOnFinished((e) -> loadingPane.setVisible(false)); 89 | ft.play(); 90 | } 91 | }).start(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivSearchPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXComboBox; 4 | import io.github.palexdev.materialfx.controls.MFXSlider; 5 | import io.github.palexdev.materialfx.controls.MFXTextField; 6 | import io.github.palexdev.materialfx.font.MFXFontIcon; 7 | import javafx.application.Platform; 8 | import javafx.fxml.FXML; 9 | import org.apache.log4j.Logger; 10 | import xyz.zcraft.acgpicdownload.Main; 11 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 12 | import xyz.zcraft.acgpicdownload.gui.Notice; 13 | import xyz.zcraft.acgpicdownload.gui.base.PixivFetchPane; 14 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 15 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 16 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivArtwork; 17 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivFetchUtil; 18 | 19 | import java.io.IOException; 20 | import java.net.URL; 21 | import java.util.LinkedList; 22 | import java.util.Objects; 23 | import java.util.ResourceBundle; 24 | 25 | public class PixivSearchPaneController extends PixivFetchPane { 26 | private static final String[] SUFFIX = { 27 | "", 28 | "30000users入り", 29 | "20000users入り", 30 | "10000users入り", 31 | "5000users入り", 32 | "1000users入り", 33 | "500users入り", 34 | "300users入り", 35 | "100users入り", 36 | "50users入り" 37 | }; 38 | @javafx.fxml.FXML 39 | public MFXComboBox typeCombo; 40 | @javafx.fxml.FXML 41 | public MFXComboBox modeCombo; 42 | @javafx.fxml.FXML 43 | public MFXSlider pageSlider; 44 | public MFXTextField keywordField; 45 | @javafx.fxml.FXML 46 | private MFXSlider relatedDepthSlider; 47 | @FXML 48 | private MFXComboBox suffixCombo; 49 | public static final Logger logger = Logger.getLogger(PixivSearchPaneController.class); 50 | @Override 51 | public void initialize(URL url, ResourceBundle resourceBundle) { 52 | super.initialize(url, resourceBundle); 53 | 54 | backBtn.setText(""); 55 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 56 | 57 | typeCombo.getItems().addAll( 58 | ResourceBundleUtil.getString("gui.pixiv.search.mode.top"), 59 | ResourceBundleUtil.getString("gui.pixiv.search.mode.illust"), 60 | ResourceBundleUtil.getString("gui.pixiv.search.mode.manga") 61 | ); 62 | 63 | modeCombo.getItems().addAll( 64 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.all"), 65 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.safe"), 66 | ResourceBundleUtil.getString("gui.pixiv.disc.mode.adult") 67 | ); 68 | 69 | typeCombo.selectedIndexProperty().addListener((observableValue, number, t1) -> { 70 | int i = t1.intValue(); 71 | modeCombo.setDisable(i == 0); 72 | pageSlider.setDisable(i == 0); 73 | }); 74 | 75 | typeCombo.selectFirst(); 76 | modeCombo.selectFirst(); 77 | 78 | suffixCombo.getItems().addAll(SUFFIX); 79 | suffixCombo.selectFirst(); 80 | } 81 | 82 | @FXML 83 | @Override 84 | public void fetchBtnOnAction() { 85 | loadingPane.setVisible(true); 86 | operationLabel.setText(ResourceBundleUtil.getString("")); 87 | ft.stop(); 88 | ft.setFromValue(0); 89 | ft.setToValue(1); 90 | ft.setOnFinished(null); 91 | ft.play(); 92 | 93 | new Thread(() -> { 94 | try { 95 | String keyword = keywordField.getText().concat(suffixCombo.getSelectedItem()); 96 | long start = System.currentTimeMillis(); 97 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 98 | logger.info("Fetching for keyword" + keyword + "with account " + selectedAccount.getName()); 99 | 100 | Platform.runLater(() -> { 101 | operationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 102 | subOperationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 103 | }); 104 | LinkedList pixivArtworks = new LinkedList<>(); 105 | 106 | if (typeCombo.getSelectedIndex() == 0) { 107 | pixivArtworks.addAll(PixivFetchUtil.searchTopArtworks( 108 | keyword, 109 | getCookie(), 110 | ConfigManager.getConfig().getString("proxyHost"), 111 | ConfigManager.getConfig().getInteger("proxyPort") 112 | )); 113 | } else if (typeCombo.getSelectedIndex() == 1) { 114 | pixivArtworks.addAll(PixivFetchUtil.searchIllustArtworks( 115 | keyword, 116 | modeCombo.getSelectedIndex(), 117 | ((int) pageSlider.getValue()), 118 | getCookie(), 119 | ConfigManager.getConfig().getString("proxyHost"), 120 | ConfigManager.getConfig().getInteger("proxyPort") 121 | )); 122 | } else if (typeCombo.getSelectedIndex() == 2) { 123 | pixivArtworks.addAll(PixivFetchUtil.searchMangaArtworks( 124 | keyword, 125 | modeCombo.getSelectedIndex(), 126 | ((int) pageSlider.getValue()), 127 | getCookie(), 128 | ConfigManager.getConfig().getString("proxyHost"), 129 | ConfigManager.getConfig().getInteger("proxyPort") 130 | )); 131 | } 132 | 133 | logger.info("Fetched " + pixivArtworks.size() + " artworks, getting related artworks for " + relatedDepthSlider.getValue() + " times"); 134 | getRelated(pixivArtworks, (int) relatedDepthSlider.getValue(), getCookie(), subOperationLabel); 135 | 136 | Platform.runLater(() -> data.addAll(pixivArtworks)); 137 | Notice.showSuccess(String.format( 138 | Objects.requireNonNull(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetched")), 139 | pixivArtworks.size()), gui.mainPane); 140 | logger.info("Fetch completed successfully within " + (System.currentTimeMillis() - start) + " ms"); 141 | } catch (IOException e) { 142 | logger.error("Fetch keyword failed", e); 143 | Main.logError(e); 144 | gui.showError(e); 145 | } finally { 146 | ft.stop(); 147 | ft.setFromValue(1); 148 | ft.setToValue(0); 149 | ft.setOnFinished((e) -> loadingPane.setVisible(false)); 150 | ft.play(); 151 | } 152 | }).start(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/PixivUserPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import io.github.palexdev.materialfx.controls.MFXSlider; 4 | import io.github.palexdev.materialfx.controls.MFXTextField; 5 | import io.github.palexdev.materialfx.font.MFXFontIcon; 6 | import javafx.application.Platform; 7 | import javafx.fxml.FXML; 8 | import org.apache.log4j.Logger; 9 | import xyz.zcraft.acgpicdownload.Main; 10 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 11 | import xyz.zcraft.acgpicdownload.gui.Notice; 12 | import xyz.zcraft.acgpicdownload.gui.base.PixivFetchPane; 13 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 14 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivAccount; 15 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivArtwork; 16 | import xyz.zcraft.acgpicdownload.util.pixivutils.PixivFetchUtil; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | import java.util.*; 21 | 22 | public class PixivUserPaneController extends PixivFetchPane { 23 | @javafx.fxml.FXML 24 | private MFXTextField uidField; 25 | @javafx.fxml.FXML 26 | private MFXSlider relatedDepthSlider; 27 | 28 | @Override 29 | public void initialize(URL url, ResourceBundle resourceBundle) { 30 | super.initialize(url, resourceBundle); 31 | 32 | backBtn.setText(""); 33 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 34 | } 35 | 36 | public static final Logger logger = Logger.getLogger(PixivUserPaneController.class); 37 | 38 | @FXML 39 | @Override 40 | public void fetchBtnOnAction() { 41 | if (uidField.getText().startsWith("https://www.pixiv.net/users/")) 42 | uidField.setText(uidField.getText().substring(uidField.getText().lastIndexOf("/") + 1)); 43 | loadingPane.setVisible(true); 44 | operationLabel.setText(ResourceBundleUtil.getString("")); 45 | ft.stop(); 46 | ft.setFromValue(0); 47 | ft.setToValue(1); 48 | ft.setOnFinished(null); 49 | ft.play(); 50 | 51 | new Thread(() -> { 52 | try { 53 | long start = System.currentTimeMillis(); 54 | PixivAccount selectedAccount = ConfigManager.getSelectedAccount(); 55 | logger.info("Fetching uid " + uidField.getText() + " with account " + selectedAccount.getName()); 56 | 57 | Platform.runLater(() -> { 58 | operationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 59 | subOperationLabel.setText(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetchMain")); 60 | }); 61 | 62 | Set artIDs = PixivFetchUtil.fetchUser( 63 | uidField.getText(), 64 | ConfigManager.getConfig().getString("proxyHost"), 65 | ConfigManager.getConfig().getInteger("proxyPort") 66 | ); 67 | List queryString = PixivFetchUtil.buildQueryString(artIDs); 68 | 69 | LinkedList pixivArtworks = new LinkedList<>(); 70 | 71 | for (String s : queryString) { 72 | pixivArtworks.addAll( 73 | PixivFetchUtil.getUserArtworks(s, uidField.getText(), ConfigManager.getConfig().getString("proxyHost"), 74 | ConfigManager.getConfig().getInteger("proxyPort"))); 75 | } 76 | logger.info("Fetched " + pixivArtworks.size() + " artworks, getting related artworks for " + relatedDepthSlider.getValue() + " times"); 77 | 78 | getRelated(pixivArtworks, (int) relatedDepthSlider.getValue(), getCookie(), subOperationLabel); 79 | 80 | Platform.runLater(() -> data.addAll(pixivArtworks)); 81 | Notice.showSuccess( 82 | String.format( 83 | Objects.requireNonNull(ResourceBundleUtil.getString("gui.pixiv.menu.notice.fetched")) 84 | , pixivArtworks.size() 85 | ), 86 | gui.mainPane 87 | ); 88 | logger.info("Fetch completed successfully within " + (System.currentTimeMillis() - start) + " ms"); 89 | } catch (IOException e) { 90 | logger.error("Fetch user failed", e); 91 | Main.logError(e); 92 | gui.showError(e); 93 | } finally { 94 | ft.stop(); 95 | ft.setFromValue(1); 96 | ft.setToValue(0); 97 | ft.setOnFinished((e) -> loadingPane.setVisible(false)); 98 | ft.play(); 99 | } 100 | }).start(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/gui/controllers/SettingsPaneController.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.gui.controllers; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import io.github.palexdev.materialfx.controls.*; 5 | import io.github.palexdev.materialfx.font.MFXFontIcon; 6 | import javafx.beans.value.ObservableValue; 7 | import javafx.fxml.FXML; 8 | import javafx.scene.control.ToggleGroup; 9 | import javafx.scene.layout.AnchorPane; 10 | import javafx.scene.paint.Color; 11 | import javafx.stage.DirectoryChooser; 12 | import javafx.util.StringConverter; 13 | import xyz.zcraft.acgpicdownload.Main; 14 | import xyz.zcraft.acgpicdownload.gui.ConfigManager; 15 | import xyz.zcraft.acgpicdownload.gui.Notice; 16 | import xyz.zcraft.acgpicdownload.gui.base.MyPane; 17 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.URL; 22 | import java.util.Locale; 23 | import java.util.ResourceBundle; 24 | 25 | public class SettingsPaneController extends MyPane { 26 | @javafx.fxml.FXML 27 | private AnchorPane mainPane; 28 | @javafx.fxml.FXML 29 | private MFXButton backBtn; 30 | @javafx.fxml.FXML 31 | private MFXTextField proxyField; 32 | @javafx.fxml.FXML 33 | private MFXSlider aniSpeedSlider; 34 | @FXML 35 | private MFXComboBox languageCombo; 36 | private String proxyHost; 37 | private int proxyPort; 38 | 39 | @javafx.fxml.FXML 40 | private MFXRadioButton fetchPLCOCopy; 41 | @javafx.fxml.FXML 42 | private ToggleGroup fetchPaneLClickOperation; 43 | @javafx.fxml.FXML 44 | private MFXRadioButton fetchPLCOOpen; 45 | @FXML 46 | private MFXRadioButton bgFromDefault; 47 | @FXML 48 | private ToggleGroup bgFrom; 49 | @FXML 50 | private MFXRadioButton bgFromFolder; 51 | @FXML 52 | private MFXRadioButton bgTransparent; 53 | @FXML 54 | private MFXTextField bgFolderField; 55 | @FXML 56 | private MFXButton bgChooseFolderBtn; 57 | 58 | public void hide() { 59 | super.hide(); 60 | gui.menuPaneController.showMain(); 61 | } 62 | 63 | @javafx.fxml.FXML 64 | public void backBtnOnAction() { 65 | hide(); 66 | } 67 | 68 | @javafx.fxml.FXML 69 | public void saveConfigBtnOnAction() { 70 | saveConfig(); 71 | } 72 | 73 | @FXML 74 | private void bgChooseFolder() { 75 | DirectoryChooser fc = new DirectoryChooser(); 76 | fc.setTitle("..."); 77 | File showDialog = fc.showDialog(gui.mainStage); 78 | if (showDialog != null) { 79 | bgFolderField.setText(showDialog.getAbsolutePath()); 80 | } 81 | } 82 | 83 | @Override 84 | public void initialize(URL location, ResourceBundle resources) { 85 | super.initialize(location, resources); 86 | 87 | proxyField.textProperty().addListener(this::verifyProxy); 88 | 89 | languageCombo.setConverter(new StringConverter<>() { 90 | @Override 91 | public String toString(Locale l) { 92 | if (l == null) return null; 93 | if (l.equals(Locale.CHINA)) return "中文"; 94 | if (l.equals(Locale.ENGLISH)) return "English"; 95 | return l.getDisplayLanguage(); 96 | } 97 | 98 | @Override 99 | public Locale fromString(String s) { 100 | return Locale.forLanguageTag(s); 101 | } 102 | }); 103 | 104 | languageCombo.getItems().clear(); 105 | languageCombo.getItems().addAll(Locale.CHINA, Locale.ENGLISH); 106 | 107 | restoreConfig(); 108 | 109 | backBtn.setText(""); 110 | backBtn.setGraphic(new MFXFontIcon("mfx-angle-down")); 111 | bgChooseFolderBtn.setText(""); 112 | bgChooseFolderBtn.setGraphic(new MFXFontIcon("mfx-folder")); 113 | } 114 | 115 | private void verifyProxy(ObservableValue observable, String oldValue, String newValue) { 116 | proxyHost = null; 117 | proxyPort = 0; 118 | System.getProperties().put("proxySet", "false"); 119 | if (!newValue.isEmpty()) { 120 | String[] str = newValue.split(":"); 121 | if (str.length == 2) { 122 | proxyHost = str[0]; 123 | try { 124 | proxyPort = Integer.parseInt(str[1]); 125 | } catch (NumberFormatException ignored) { 126 | proxyField.setTextFill(Color.RED); 127 | return; 128 | } 129 | } 130 | if (proxyHost != null && proxyPort != 0) { 131 | System.getProperties().put("proxySet", "true"); 132 | System.getProperties().put("proxyHost", proxyHost); 133 | System.getProperties().put("proxyPort", String.valueOf(proxyPort)); 134 | proxyField.setTextFill(MFXTextField.DEFAULT_TEXT_COLOR); 135 | } else { 136 | proxyField.setTextFill(Color.RED); 137 | } 138 | } else { 139 | proxyField.setTextFill(MFXTextField.DEFAULT_TEXT_COLOR); 140 | } 141 | } 142 | 143 | public void saveConfig() { 144 | JSONObject obj = ConfigManager.getConfig(); 145 | 146 | if (proxyPort != 0 && proxyHost != null) { 147 | obj.put("proxyHost", proxyHost); 148 | obj.put("proxyPort", proxyPort); 149 | } else { 150 | obj.remove("proxyHost"); 151 | obj.remove("proxyPort"); 152 | } 153 | obj.put("aniSpeed", aniSpeedSlider.getValue()); 154 | obj.put("fetchPLCOCopy", fetchPLCOCopy.isSelected()); 155 | obj.put("lang", languageCombo.getSelectedItem().toLanguageTag()); 156 | 157 | if (bgFromFolder.isSelected()) { 158 | obj.put("bg", bgFolderField.getText()); 159 | } else if (bgTransparent.isSelected()) { 160 | obj.put("bg", "transparent"); 161 | } else { 162 | obj.remove("bg"); 163 | } 164 | 165 | try { 166 | ConfigManager.saveConfig(); 167 | Notice.showSuccess(ResourceBundleUtil.getString("gui.fetch.notice.saved"), gui.mainPane); 168 | } catch (IOException e) { 169 | gui.showError(e); 170 | Main.logError(e); 171 | } 172 | } 173 | 174 | public void restoreConfig() { 175 | JSONObject json = ConfigManager.getConfig(); 176 | if (json.getString("proxyHost") != null && json.getInteger("proxyPort") != 0) { 177 | proxyField.setText(json.getString("proxyHost") + ":" + json.getInteger("proxyPort")); 178 | } 179 | aniSpeedSlider.setValue(ConfigManager.getDoubleIfExist("aniSpeed", 1.0)); 180 | fetchPLCOCopy.setSelected(ConfigManager.getConfig().getBooleanValue("fetchPLCOCopy")); 181 | fetchPLCOOpen.setSelected(!ConfigManager.getConfig().getBooleanValue("fetchPLCOCopy")); 182 | 183 | String lang = ConfigManager.getConfig().getString("lang"); 184 | Locale locale; 185 | if (lang != null) locale = Locale.forLanguageTag(lang); 186 | else locale = Locale.getDefault(); 187 | if (languageCombo.getItems().contains(locale)) languageCombo.getSelectionModel().selectItem(locale); 188 | else languageCombo.getSelectionModel().selectFirst(); 189 | 190 | if (json.containsKey("bg")) { 191 | if (json.getString("bg").equals("transparent")) { 192 | bgTransparent.setSelected(true); 193 | } else { 194 | bgFolderField.setText(json.getString("bg")); 195 | bgFromFolder.setSelected(true); 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util; 2 | 3 | public abstract class ExceptionHandler { 4 | public abstract void handle(Exception e); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/Logger.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util; 2 | 3 | import java.io.PrintStream; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class Logger { 8 | private final String name; 9 | private final Logger parentLogger; 10 | 11 | private final PrintStream[] out; 12 | 13 | public Logger(String name, Logger parentLogger, PrintStream... out) { 14 | this.name = name; 15 | this.parentLogger = parentLogger; 16 | this.out = out; 17 | } 18 | 19 | public Logger(String name, PrintStream... out) { 20 | this.name = name; 21 | this.out = out; 22 | parentLogger = null; 23 | } 24 | 25 | public String getName() { 26 | if (parentLogger != null) { 27 | return parentLogger.getName() + "|" + name; 28 | } 29 | return name; 30 | } 31 | 32 | public Logger getParentLogger() { 33 | return parentLogger; 34 | } 35 | 36 | public String getOutputName() { 37 | return "[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "][" + getName() + "] "; 38 | } 39 | 40 | public void info(String message) { 41 | for (PrintStream t : out) { 42 | t.println(getOutputName() + message); 43 | t.flush(); 44 | } 45 | } 46 | 47 | public void printr(String str) { 48 | for (PrintStream t : out) { 49 | t.print("\r"); 50 | t.print(getOutputName()); 51 | t.print(str); 52 | t.flush(); 53 | } 54 | } 55 | 56 | public void err(String message) { 57 | System.err.println(getOutputName() + message); 58 | } 59 | 60 | public void printf(String format, String... arg) { 61 | for (PrintStream t : out) { 62 | t.print(getOutputName()); 63 | t.printf(format, (Object[]) arg); 64 | t.flush(); 65 | } 66 | } 67 | 68 | public void printlnf(String format, String... arg) { 69 | printf(format, arg); 70 | for (PrintStream t : out) { 71 | t.println(); 72 | t.flush(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/ResourceBundleUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util; 2 | 3 | import java.util.Locale; 4 | import java.util.ResourceBundle; 5 | 6 | public class ResourceBundleUtil { 7 | private static ResourceBundle BUNDLE; 8 | 9 | static { 10 | loadDefault(); 11 | } 12 | 13 | public static void load(String language) { 14 | BUNDLE = ResourceBundle.getBundle("xyz.zcraft.acgpicdownload.languages.String", Locale.forLanguageTag(language)); 15 | Locale.setDefault(Locale.forLanguageTag(language)); 16 | } 17 | 18 | public static void loadDefault() { 19 | BUNDLE = ResourceBundle.getBundle("xyz.zcraft.acgpicdownload.languages.String", Locale.getDefault()); 20 | } 21 | 22 | public static String getString(String key) { 23 | if (BUNDLE.containsKey(key)) { 24 | return BUNDLE.getString(key); 25 | } else { 26 | return null; 27 | } 28 | } 29 | 30 | public static ResourceBundle getResource() { 31 | return BUNDLE; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/SSLUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util; 2 | 3 | import javax.net.ssl.*; 4 | import java.security.cert.X509Certificate; 5 | 6 | public class SSLUtil { 7 | 8 | public static void trustAllHttpsCertificates() throws Exception { 9 | TrustManager[] trustAllCerts = new TrustManager[1]; 10 | TrustManager tm = new miTM(); 11 | trustAllCerts[0] = tm; 12 | SSLContext sc = SSLContext.getInstance("SSL"); 13 | sc.init(null, trustAllCerts, null); 14 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 15 | } 16 | 17 | public static void ignoreSsl() throws Exception { 18 | HostnameVerifier hv = (urlHostName, session) -> true; 19 | trustAllHttpsCertificates(); 20 | HttpsURLConnection.setDefaultHostnameVerifier(hv); 21 | } 22 | 23 | static class miTM implements X509TrustManager { 24 | public X509Certificate[] getAcceptedIssuers() { 25 | return null; 26 | } 27 | 28 | public boolean isServerTrusted(X509Certificate[] certs) { 29 | return true; 30 | } 31 | 32 | public boolean isClientTrusted(X509Certificate[] certs) { 33 | return true; 34 | } 35 | 36 | public void checkServerTrusted(X509Certificate[] certs, String authType) { 37 | } 38 | 39 | public void checkClientTrusted(X509Certificate[] certs, String authType) { 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/downloadutil/DownloadManager.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.downloadutil; 2 | 3 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 4 | 5 | import java.text.DecimalFormat; 6 | import java.util.LinkedList; 7 | import java.util.Objects; 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | 10 | public class DownloadManager { 11 | private static final DecimalFormat df = new DecimalFormat("##.#%"); 12 | private static final DecimalFormat df2 = new DecimalFormat("#.##"); 13 | private static final int PROGRESS_BAR_SIZE = 25; 14 | private final DownloadResult[] process; 15 | private final long startTime; 16 | private final LinkedList error = new LinkedList<>(); 17 | long total = 0; 18 | long downloaded = 0; 19 | int failed = 0; 20 | int completed = 0; 21 | int started = 0; 22 | ThreadPoolExecutor tpe; 23 | 24 | int created = 0; 25 | String speed; 26 | private boolean done = false; 27 | private long lastDownloaded = 0; 28 | private long lastTime = 0; 29 | private int timesGot = 0; 30 | private double p = 0; 31 | 32 | public DownloadManager(DownloadResult[] process, ThreadPoolExecutor tpe) { 33 | this.tpe = tpe; 34 | startTime = System.currentTimeMillis(); 35 | this.process = process; 36 | } 37 | 38 | public DownloadManager(DownloadResult[] process) { 39 | startTime = System.currentTimeMillis(); 40 | this.process = process; 41 | } 42 | 43 | public ThreadPoolExecutor getTpe() { 44 | return tpe; 45 | } 46 | 47 | public void setTpe(ThreadPoolExecutor tpe) { 48 | this.tpe = tpe; 49 | } 50 | 51 | public DownloadResult[] getProcess() { 52 | return process; 53 | } 54 | 55 | public double getPercentComplete() { 56 | return p; 57 | } 58 | 59 | public void update() { 60 | total = 0; 61 | downloaded = 0; 62 | failed = 0; 63 | completed = 0; 64 | started = 0; 65 | created = 0; 66 | 67 | 68 | for (DownloadResult r : process) { 69 | if (r.getStatus() == DownloadStatus.CREATED) { 70 | created++; 71 | } else if (r.getStatus() == DownloadStatus.STARTED) { 72 | started++; 73 | total += r.getTotalSize(); 74 | downloaded += r.getSizeDownloaded(); 75 | } else if (r.getStatus() == DownloadStatus.COMPLETED) { 76 | completed++; 77 | total += r.getTotalSize(); 78 | downloaded += r.getTotalSize(); 79 | } else if (r.getStatus() == DownloadStatus.FAILED) { 80 | failed++; 81 | if (!Objects.equals("", r.getErrorMessage())) { 82 | error.add(r.getErrorMessage()); 83 | r.setErrorMessage(""); 84 | } 85 | } 86 | } 87 | 88 | if (total != 0) { 89 | p = (double) downloaded / (double) total; 90 | if (p < 0) p = 0; 91 | if (p > 1) p = 1; 92 | } 93 | 94 | double speed = Math.max((((double) (downloaded - lastDownloaded)) / 1024.0) / (((double) (System.currentTimeMillis() - lastTime)) / 1000.0), 0); 95 | if (speed > 1024.0) { 96 | this.speed = df2.format(speed / 1024.0).concat("mb/s"); 97 | } else { 98 | this.speed = df2.format(speed).concat("kb/s"); 99 | } 100 | 101 | lastDownloaded = downloaded; 102 | lastTime = System.currentTimeMillis(); 103 | } 104 | 105 | public long getTotal() { 106 | return total; 107 | } 108 | 109 | public long getDownloaded() { 110 | return downloaded; 111 | } 112 | 113 | public int getFailed() { 114 | return failed; 115 | } 116 | 117 | public int getCompleted() { 118 | return completed; 119 | } 120 | 121 | public int getStarted() { 122 | return started; 123 | } 124 | 125 | public int getCreated() { 126 | return created; 127 | } 128 | 129 | public boolean isDone() { 130 | if (tpe == null) { 131 | return done; 132 | } else { 133 | return tpe.getCompletedTaskCount() == tpe.getTaskCount(); 134 | } 135 | } 136 | 137 | public long getLastDownloaded() { 138 | return lastDownloaded; 139 | } 140 | 141 | public long getLastTime() { 142 | return lastTime; 143 | } 144 | 145 | public long getStartTime() { 146 | return startTime; 147 | } 148 | 149 | public int getTimesGot() { 150 | return timesGot; 151 | } 152 | 153 | public double getP() { 154 | return p; 155 | } 156 | 157 | public String getSpeed() { 158 | return speed; 159 | } 160 | 161 | public LinkedList getError() { 162 | return error; 163 | } 164 | 165 | @Override 166 | public String toString() { 167 | update(); 168 | 169 | timesGot++; 170 | StringBuilder sb = new StringBuilder(); 171 | 172 | for (String error2 : error) { 173 | sb.append(ResourceBundleUtil.getString("cli.fetch.err")).append(":").append(error2).append("\n"); 174 | } 175 | 176 | error.clear(); 177 | 178 | int mtf = 8; 179 | sb.append(timesGot > mtf ? "W" : ResourceBundleUtil.getString("cli.download.status.created")).append(":").append(created).append(" ").append(timesGot > mtf ? "S" : ResourceBundleUtil.getString("cli.download.status.started")) 180 | .append(":").append(started).append(" ").append(timesGot > mtf ? "D" : ResourceBundleUtil.getString("cli.download.status.completed")).append(":").append(completed) 181 | .append(" ").append(timesGot > mtf ? "F" : ResourceBundleUtil.getString("cli.download.status.failed")) 182 | .append(":").append(failed).append(" |"); 183 | 184 | int a = (int) (PROGRESS_BAR_SIZE * p); 185 | if (a < 0) { 186 | a = 0; 187 | } else if (a > PROGRESS_BAR_SIZE) { 188 | a = PROGRESS_BAR_SIZE; 189 | } 190 | int b = PROGRESS_BAR_SIZE - a; 191 | sb.append("=".repeat(a)).append(" ".repeat(b)).append("|"); 192 | 193 | if (p > 1) { 194 | sb.append("..."); 195 | } else { 196 | sb.append(df.format(p)); 197 | } 198 | 199 | if (lastTime != 0) { 200 | sb.append(" ").append(speed); 201 | 202 | double avg = Math.max(((double) (total) / 1024.0) / ((double) (System.currentTimeMillis() - startTime) / 1000.0), 0); 203 | if (avg > 1024.0) { 204 | sb.append(" AVG:").append(df2.format(avg / 1024.0).concat("mb/s")); 205 | } else { 206 | sb.append(" AVG:").append(df2.format(avg).concat("kb/s")); 207 | } 208 | 209 | double eta = (double) (total - downloaded) / 1024 / avg; 210 | 211 | if (eta >= 0) { 212 | sb.append(" ETA:").append(df2.format(eta)).append("s"); 213 | } 214 | } 215 | 216 | if (created == 0 && started == 0) { 217 | done = true; 218 | } 219 | 220 | return sb.toString(); 221 | } 222 | 223 | public boolean done() { 224 | return done; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/downloadutil/DownloadResult.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.downloadutil; 2 | 3 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 4 | import xyz.zcraft.acgpicdownload.util.fetchutil.Result; 5 | 6 | public class DownloadResult { 7 | protected long totalSize; 8 | protected DownloadStatus status = DownloadStatus.CREATED; 9 | protected long sizeDownloaded = 0; 10 | protected String errorMessage; 11 | protected Result result; 12 | 13 | public DownloadResult() { 14 | } 15 | 16 | public DownloadResult(Result result) { 17 | this.result = result; 18 | } 19 | 20 | public Result getResult() { 21 | return result; 22 | } 23 | 24 | public void setResult(Result result) { 25 | this.result = result; 26 | } 27 | 28 | public long getTotalSize() { 29 | return totalSize; 30 | } 31 | 32 | public void setTotalSize(long totalSize) { 33 | this.totalSize = totalSize; 34 | } 35 | 36 | public DownloadStatus getStatus() { 37 | return status; 38 | } 39 | 40 | public void setStatus(DownloadStatus status) { 41 | this.status = status; 42 | } 43 | 44 | public long getSizeDownloaded() { 45 | return sizeDownloaded; 46 | } 47 | 48 | public void setSizeDownloaded(long sizeDownloaded) { 49 | this.sizeDownloaded = sizeDownloaded; 50 | } 51 | 52 | public void addSizeDownloaded(long size) { 53 | sizeDownloaded += size; 54 | } 55 | 56 | public String getErrorMessage() { 57 | return errorMessage; 58 | } 59 | 60 | public void setErrorMessage(String errorMessage) { 61 | this.errorMessage = errorMessage; 62 | } 63 | 64 | public String getStatusString() { 65 | if (status == DownloadStatus.CREATED) { 66 | return ResourceBundleUtil.getString("cli.download.status.created"); 67 | } else if (status == DownloadStatus.COMPLETED) { 68 | return ResourceBundleUtil.getString("cli.download.status.completed"); 69 | } else if (status == DownloadStatus.FAILED) { 70 | return ResourceBundleUtil.getString("cli.download.status.failed"); 71 | } else if (status == DownloadStatus.STARTED) { 72 | return ResourceBundleUtil.getString("cli.download.status.started"); 73 | } else { 74 | return "?"; 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/downloadutil/DownloadStatus.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.downloadutil; 2 | 3 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 4 | 5 | public enum DownloadStatus { 6 | FAILED, STARTED, CREATED, COMPLETED, INITIALIZE, FILTERED; 7 | 8 | @Override 9 | public String toString() { 10 | if (this == DownloadStatus.CREATED) { 11 | return ResourceBundleUtil.getString("cli.download.status.created"); 12 | } else if (this == DownloadStatus.COMPLETED) { 13 | return ResourceBundleUtil.getString("cli.download.status.completed"); 14 | } else if (this == DownloadStatus.FILTERED) { 15 | return ResourceBundleUtil.getString("cli.download.status.filtered"); 16 | } else if (this == DownloadStatus.INITIALIZE) { 17 | return ResourceBundleUtil.getString("cli.download.status.init"); 18 | } else if (this == DownloadStatus.FAILED) { 19 | return ResourceBundleUtil.getString("cli.download.status.failed"); 20 | } else if (this == DownloadStatus.STARTED) { 21 | return ResourceBundleUtil.getString("cli.download.status.started"); 22 | } else { 23 | return "?"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/fetchutil/Result.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.fetchutil; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | 5 | public class Result { 6 | private String fileName; 7 | private String url; 8 | private JSONObject json; 9 | 10 | public Result(String fileName, String url, JSONObject json) { 11 | this.fileName = fileName; 12 | this.url = url; 13 | this.json = json; 14 | } 15 | 16 | public Result() { 17 | } 18 | 19 | 20 | public String getFileName() { 21 | return fileName; 22 | } 23 | 24 | public void setFileName(String fileName) { 25 | this.fileName = fileName; 26 | } 27 | 28 | public String getUrl() { 29 | return url; 30 | } 31 | 32 | public void setUrl(String url) { 33 | this.url = url; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Result{fileName=" + fileName + ", url=" + url + "}"; 39 | } 40 | 41 | public JSONObject getJson() { 42 | return json; 43 | } 44 | 45 | public void setJson(JSONObject json) { 46 | this.json = json; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/ArtworkCondition.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | import lombok.NoArgsConstructor; 3 | @SuppressWarnings({"UnusedReturnValue","BooleanMethodIsAlwaysInverted"}) 4 | @NoArgsConstructor 5 | public class ArtworkCondition { 6 | private int bookmarkCount = -1; 7 | private int likeCount = -1; 8 | 9 | public boolean test(PixivArtwork artwork){ 10 | return artwork.getBookmarkCount() >= bookmarkCount && artwork.getLikeCount() >= likeCount; 11 | } 12 | 13 | public ArtworkCondition bookmark(int count){ 14 | this.bookmarkCount = count; 15 | return this; 16 | } 17 | 18 | 19 | public ArtworkCondition like(int count){ 20 | this.likeCount = count; 21 | return this; 22 | } 23 | 24 | public static ArtworkCondition always(){ 25 | return new ArtworkCondition(); 26 | } 27 | public boolean isAlways(){ 28 | return bookmarkCount == -1 && likeCount == -1; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/From.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 4 | 5 | public enum From { 6 | Follow, 7 | Recommend, 8 | RecommendUser, 9 | RecommendTag, 10 | Related, 11 | Spec, 12 | Discovery, 13 | Ranking, 14 | Search, 15 | User, 16 | Other; 17 | 18 | @Override 19 | public String toString() { 20 | if (this == Follow) { 21 | return ResourceBundleUtil.getString("fetch.pixiv.from.follow"); 22 | } else if (this == Recommend) { 23 | return ResourceBundleUtil.getString("fetch.pixiv.from.recommend"); 24 | } else if (this == RecommendUser) { 25 | return ResourceBundleUtil.getString("fetch.pixiv.from.recommendUser"); 26 | } else if (this == RecommendTag) { 27 | return ResourceBundleUtil.getString("fetch.pixiv.from.recommendTag"); 28 | } else if (this == Search) { 29 | return ResourceBundleUtil.getString("fetch.pixiv.from.search"); 30 | } else if (this == Spec) { 31 | return ResourceBundleUtil.getString("fetch.pixiv.from.spec"); 32 | } else if (this == User) { 33 | return ResourceBundleUtil.getString("fetch.pixiv.from.user"); 34 | } else if (this == Discovery) { 35 | return ResourceBundleUtil.getString("fetch.pixiv.from.disc"); 36 | } else if (this == Ranking) { 37 | return ResourceBundleUtil.getString("fetch.pixiv.from.ranking"); 38 | } else if (this == Related) { 39 | return ResourceBundleUtil.getString("fetch.pixiv.from.related"); 40 | } else { 41 | return ResourceBundleUtil.getString("fetch.pixiv.from.other"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/GifData.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import com.alibaba.fastjson2.annotation.JSONField; 5 | 6 | public class GifData { 7 | @JSONField(name = "src") 8 | private String src; 9 | @JSONField(name = "originalSrc") 10 | private String originalSrc; 11 | @JSONField(name = "mime_type") 12 | private String mime_type; 13 | @JSONField(name = "frames") 14 | private JSONArray origFrame; 15 | 16 | public GifData() { 17 | } 18 | 19 | public String getSrc() { 20 | return src; 21 | } 22 | 23 | public void setSrc(String src) { 24 | this.src = src; 25 | } 26 | 27 | public String getOriginalSrc() { 28 | return originalSrc; 29 | } 30 | 31 | public void setOriginalSrc(String originalSrc) { 32 | this.originalSrc = originalSrc; 33 | } 34 | 35 | public String getMime_type() { 36 | return mime_type; 37 | } 38 | 39 | public void setMime_type(String mime_type) { 40 | this.mime_type = mime_type; 41 | } 42 | 43 | public JSONArray getOrigFrame() { 44 | return origFrame; 45 | } 46 | 47 | public void setOrigFrame(JSONArray origFrame) { 48 | this.origFrame = origFrame; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "GifData{" + 54 | "src='" + src + '\'' + 55 | ", originalSrc='" + originalSrc + '\'' + 56 | ", mime_type='" + mime_type + '\'' + 57 | ", origFrame=" + origFrame + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/NamingRule.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import xyz.zcraft.acgpicdownload.util.fetchutil.FetchUtil; 5 | 6 | public record NamingRule(String rule, int multiP, String folderRule) { 7 | public String name(PixivArtwork artwork, int p) { 8 | JSONObject clone = artwork.getOrigJson().clone(); 9 | if (p != -1) { 10 | clone.put("p", p); 11 | } 12 | return FetchUtil.replaceArgument(rule, clone); 13 | } 14 | 15 | public String name(PixivArtwork artwork) { 16 | return FetchUtil.replaceArgument(rule, artwork.getOrigJson()); 17 | } 18 | 19 | public String nameFolder(PixivArtwork artwork) { 20 | return FetchUtil.replaceArgument(folderRule, artwork.getOrigJson()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/PixivAccount.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import com.alibaba.fastjson2.annotation.JSONField; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | public class PixivAccount { 10 | @JSONField(name = "name") 11 | private String name; 12 | @JSONField(name = "id") 13 | private String id; 14 | @JSONField(name = "profileImg") 15 | private String profileImg; 16 | @JSONField(name = "cookie") 17 | private String cookie; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/PixivArtwork.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import com.alibaba.fastjson2.JSONObject; 5 | import com.alibaba.fastjson2.annotation.JSONField; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 10 | 11 | import java.util.LinkedHashSet; 12 | import java.util.Objects; 13 | 14 | @Data 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class PixivArtwork { 18 | @JSONField(name = "id") 19 | private String id; 20 | @JSONField(name = "title") 21 | private String title; 22 | @JSONField(name = "illustType") 23 | private int illustType; 24 | @JSONField(name = "xRestrict") 25 | private int xRestrict; 26 | @JSONField(name = "restrict") 27 | private int restrict; 28 | @JSONField(name = "sl") 29 | private int sl; 30 | @JSONField(name = "url") 31 | private String url; 32 | @JSONField(name = "description") 33 | private String description; 34 | @JSONField(name = "tags") 35 | private JSONArray originalTags; 36 | @JSONField(name = "userId") 37 | private String userId; 38 | @JSONField(name = "userName") 39 | private String userName; 40 | @JSONField(name = "width") 41 | private int width; 42 | @JSONField(name = "height") 43 | private int height; 44 | @JSONField(name = "pageCount") 45 | private int pageCount; 46 | @JSONField(name = "isBookmarkable") 47 | private boolean bookmarkable; 48 | @JSONField(name = "bookmarkData") 49 | private Object bookmarkData; 50 | @JSONField(name = "alt") 51 | private String alt; 52 | @JSONField(name = "titleCaptionTranslation") 53 | private JSONObject titleCaptionTranslation; 54 | @JSONField(name = "createDate") 55 | private String createDate; 56 | @JSONField(name = "updateDate") 57 | private String updateDate; 58 | @JSONField(name = "isUnlisted") 59 | private boolean unlisted; 60 | @JSONField(name = "isMasked") 61 | private boolean masked; 62 | @JSONField(name = "urls") 63 | private JSONObject urls; 64 | @JSONField(name = "profileImageUrl") 65 | private String profileImageUrl; 66 | @JSONField(name = "aiType") 67 | private int aiType; 68 | @JSONField(name = "bookmarkCount") 69 | private int bookmarkCount; 70 | @JSONField(name = "likeCount") 71 | private int likeCount; 72 | 73 | private LinkedHashSet translatedTags = new LinkedHashSet<>(); 74 | private String imageUrl; 75 | private GifData gifData; 76 | private From from; 77 | 78 | private JSONObject origJson; 79 | private String ranking; 80 | private String search; 81 | 82 | public String getTypeString() { 83 | if (illustType == 2) return ResourceBundleUtil.getString("fetch.pixiv.type.gif"); 84 | else if (illustType == 1 || illustType == 0) 85 | return Objects.requireNonNull(ResourceBundleUtil.getString("fetch.pixiv.type.illust")).concat("-" + pageCount); 86 | else return "?"; 87 | } 88 | 89 | public String getFromString() { 90 | if(from == null) return null; 91 | if (from.equals(From.Ranking)) { 92 | return from.toString().concat(" ").concat(ranking); 93 | } else if (from.equals(From.Search)) { 94 | return from.toString().concat(" ").concat(search); 95 | } else { 96 | return from.toString(); 97 | } 98 | } 99 | 100 | public String getTagsString() { 101 | if (translatedTags == null || translatedTags.size() == 0) { 102 | if (originalTags == null) return null; 103 | StringBuilder sb = new StringBuilder(); 104 | for (Object tag : originalTags) { 105 | sb.append(tag); 106 | sb.append(","); 107 | } 108 | return sb.toString(); 109 | } else { 110 | StringBuilder sb = new StringBuilder(); 111 | for (Object tag : translatedTags) { 112 | sb.append(tag); 113 | sb.append(","); 114 | } 115 | return sb.toString(); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/PixivDownload.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import lombok.Data; 4 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 5 | import xyz.zcraft.acgpicdownload.util.downloadutil.DownloadStatus; 6 | 7 | @Data 8 | public class PixivDownload { 9 | private PixivArtwork artwork; 10 | private DownloadStatus status = DownloadStatus.CREATED; 11 | 12 | public PixivDownload(PixivArtwork artwork) { 13 | this.artwork = artwork; 14 | } 15 | 16 | public String getStatusString() { 17 | if (status == DownloadStatus.FILTERED) { 18 | return ResourceBundleUtil.getString("cli.download.status.filtered") 19 | + " " + artwork.getBookmarkCount() + "|" + artwork.getLikeCount(); 20 | } else { 21 | return status.toString(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/pixivutils/PixivDownloadUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.pixivutils; 2 | 3 | import xyz.zcraft.acgpicdownload.Main; 4 | import xyz.zcraft.acgpicdownload.util.Logger; 5 | import xyz.zcraft.acgpicdownload.util.ResourceBundleUtil; 6 | import xyz.zcraft.acgpicdownload.util.downloadutil.DownloadStatus; 7 | import xyz.zcraft.acgpicdownload.util.downloadutil.DownloadUtil; 8 | 9 | import java.io.File; 10 | import java.util.List; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | 14 | public class PixivDownloadUtil { 15 | public static final String REFERER = "https://www.pixiv.net"; 16 | 17 | public synchronized static void startDownload(List artworksDownloads, String outputDir, 18 | Logger logger, int maxThread, String cookieString, 19 | NamingRule namingRule, boolean fullResult, 20 | String proxyHost, Integer proxyPort, 21 | ArtworkCondition condition) { 22 | File outDir = new File(outputDir); 23 | if (!outDir.exists() && !outDir.mkdirs()) { 24 | logger.err(ResourceBundleUtil.getString("cli.fetch.err.cannotCreatDir")); 25 | return; 26 | } 27 | 28 | ThreadPoolExecutor tpe; 29 | 30 | if (maxThread == -1) tpe = (ThreadPoolExecutor) Executors.newCachedThreadPool(); 31 | else tpe = (ThreadPoolExecutor) Executors.newFixedThreadPool(maxThread); 32 | 33 | for (var a : artworksDownloads) { 34 | if (a.getStatus().equals(DownloadStatus.CREATED) || a.getStatus().equals(DownloadStatus.FAILED)) { 35 | a.setStatus(DownloadStatus.INITIALIZE); 36 | tpe.execute(() -> { 37 | try { 38 | new DownloadUtil(1).downloadPixiv( 39 | a, outDir, cookieString, 40 | namingRule, fullResult, 41 | proxyHost, proxyPort, condition 42 | ); 43 | } catch (Exception e) { 44 | Main.logError(e); 45 | a.setStatus(DownloadStatus.FAILED); 46 | } 47 | }); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/scheduleutil/Event.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.scheduleutil; 2 | 3 | import java.time.Duration; 4 | import java.util.ArrayList; 5 | 6 | public class Event { 7 | private final ArrayList commands; 8 | private final Duration interval; 9 | private final int maxTimes; 10 | 11 | private boolean isActive = true; 12 | 13 | private int timesRan = 0; 14 | private long lastTimeRan; 15 | 16 | public Event(ArrayList commands, Duration interval, int maxTimes) { 17 | this.commands = commands; 18 | this.interval = interval; 19 | this.maxTimes = maxTimes; 20 | } 21 | 22 | public ArrayList getCommands() { 23 | return commands; 24 | } 25 | 26 | public Duration getInterval() { 27 | return interval; 28 | } 29 | 30 | public int getMaxTimes() { 31 | return maxTimes; 32 | } 33 | 34 | public boolean isActive() { 35 | return isActive; 36 | } 37 | 38 | public void setActive(boolean active) { 39 | isActive = active; 40 | } 41 | 42 | public long getTimesRan() { 43 | return timesRan; 44 | } 45 | 46 | public void setTimesRan(int timeRan) { 47 | this.timesRan = timeRan; 48 | } 49 | 50 | public void addTimesRan() { 51 | this.timesRan++; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "Event{" + "commands=" + commands + ", interval=" + interval + ", maxTimes=" + maxTimes + ", isActive=" + isActive + ", timesRan=" + timesRan + ", lastTimeRan=" + lastTimeRan + '}'; 57 | } 58 | 59 | public long getLastTimeRan() { 60 | return lastTimeRan; 61 | } 62 | 63 | public void setLastTimeRan(long lastTimeRan) { 64 | this.lastTimeRan = lastTimeRan; 65 | } 66 | 67 | public String getCommandString() { 68 | StringBuilder sb = new StringBuilder(); 69 | for (String command : commands) { 70 | sb.append(command.concat(" ")); 71 | } 72 | return sb.toString(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/Source.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import com.alibaba.fastjson2.annotation.JSONField; 5 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.Argument; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class Source implements Cloneable { 10 | @JSONField(name = "name") 11 | private String name; 12 | @JSONField(name = "description") 13 | private String description = ""; 14 | @JSONField(name = "url") 15 | private String url; 16 | @JSONField(name = "nameRule") 17 | private String nameRule = ""; 18 | @JSONField(name = "picUrl") 19 | private String picUrl; 20 | @JSONField(name = "sourceKey") 21 | private String sourceKey = ""; 22 | 23 | private ArrayList> arguments; 24 | 25 | @JSONField(name = "defaultArgs") 26 | private JSONObject defaultArgs = new JSONObject(); 27 | 28 | @JSONField(name = "returnType") 29 | private String returnType; 30 | 31 | public Source() { 32 | } 33 | 34 | public Source(String name, String description, String url, String nameRule, String picUrl, String sourceKey, JSONObject defaultArgs, String returnType) { 35 | this.name = name; 36 | this.description = description; 37 | this.url = url; 38 | this.nameRule = nameRule; 39 | this.picUrl = picUrl; 40 | this.sourceKey = sourceKey; 41 | this.defaultArgs = defaultArgs; 42 | this.returnType = returnType; 43 | } 44 | 45 | @Override 46 | public Object clone() throws CloneNotSupportedException { 47 | return super.clone(); 48 | } 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public String getDescription() { 59 | return description; 60 | } 61 | 62 | public void setDescription(String description) { 63 | this.description = description; 64 | } 65 | 66 | public String getUrl() { 67 | return url; 68 | } 69 | 70 | public void setUrl(String url) { 71 | this.url = url; 72 | } 73 | 74 | public String getNameRule() { 75 | return nameRule; 76 | } 77 | 78 | public void setNameRule(String nameRule) { 79 | this.nameRule = nameRule; 80 | } 81 | 82 | public String getPicUrl() { 83 | return picUrl; 84 | } 85 | 86 | public void setPicUrl(String picUrl) { 87 | this.picUrl = picUrl; 88 | } 89 | 90 | public String getSourceKey() { 91 | return sourceKey; 92 | } 93 | 94 | public void setSourceKey(String sourceKey) { 95 | this.sourceKey = sourceKey; 96 | } 97 | 98 | public JSONObject getDefaultArgs() { 99 | return defaultArgs; 100 | } 101 | 102 | public void setDefaultArgs(JSONObject defaultArgs) { 103 | this.defaultArgs = defaultArgs; 104 | } 105 | 106 | public String getReturnType() { 107 | return returnType; 108 | } 109 | 110 | public void setReturnType(String returnType) { 111 | this.returnType = returnType; 112 | } 113 | 114 | public ArrayList> getArguments() { 115 | return arguments; 116 | } 117 | 118 | public void setArguments(ArrayList> arguments) { 119 | this.arguments = arguments; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/SourceFetcher.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.alibaba.fastjson2.JSONArray; 5 | import com.alibaba.fastjson2.JSONException; 6 | import com.alibaba.fastjson2.JSONObject; 7 | import org.jsoup.Connection; 8 | import org.jsoup.Jsoup; 9 | import xyz.zcraft.acgpicdownload.exceptions.UnsupportedReturnTypeException; 10 | import xyz.zcraft.acgpicdownload.util.fetchutil.FetchUtil; 11 | import xyz.zcraft.acgpicdownload.util.fetchutil.Result; 12 | 13 | import java.io.IOException; 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.ArrayList; 16 | import java.util.LinkedList; 17 | import java.util.List; 18 | import java.util.Objects; 19 | 20 | public class SourceFetcher { 21 | private final static String[] ILLEGAL_STRINGS = {"\\\\", "/", ":", "\\*", "\\?", "\"", "<", ">", "\\|"}; 22 | 23 | public static ArrayList fetch(Source source, String proxyHost, int proxyPort) throws UnsupportedReturnTypeException, IOException { 24 | Connection conn = Jsoup.connect(source.getUrl().replaceAll("\\|", "%7C")).followRedirects(true).ignoreContentType(true); 25 | 26 | if (proxyHost != null && proxyPort != 0) { 27 | conn.proxy(proxyHost, proxyPort); 28 | } 29 | 30 | Connection.Response response = conn.timeout(10000).execute(); 31 | if (SourceManager.isEmpty(source.getReturnType())) { 32 | if (response.body().startsWith("{") && response.body().endsWith("}")) { 33 | source.setReturnType("json"); 34 | } else if (Objects.equals(response.url().toString(), source.getUrl())) { 35 | source.setReturnType("redirect"); 36 | } else { 37 | throw new UnsupportedReturnTypeException(); 38 | } 39 | } 40 | if (Objects.equals("json", source.getReturnType().toLowerCase())) { 41 | return parseJson(response.body(), source); 42 | } else if (Objects.equals("redirect", source.getReturnType().toLowerCase())) { 43 | String s = response.url().toString(); 44 | String t; 45 | int a = s.lastIndexOf("?"); 46 | int b = s.lastIndexOf("/"); 47 | if (a > b) { 48 | t = s.substring(b + 1, a); 49 | } else { 50 | t = s.substring(b + 1); 51 | } 52 | return new ArrayList<>(List.of(new Result(t, s, null))); 53 | } else { 54 | return new ArrayList<>(List.of()); 55 | } 56 | } 57 | 58 | public static ArrayList parseJson(String jsonString, Source source) throws JSONException { 59 | Object tmpObj = JSON.parse(jsonString); 60 | 61 | Object data; 62 | if (tmpObj instanceof JSONObject obj) { 63 | 64 | // Follow the pathToSource 65 | if (source.getSourceKey() != null && !source.getSourceKey().trim().equals("")) { 66 | data = followPath(obj, source.getSourceKey()); 67 | } else { 68 | data = obj; 69 | } 70 | } else { 71 | data = tmpObj; 72 | } 73 | 74 | // Judge if to parse as array 75 | ArrayList pics = new ArrayList<>(); 76 | if (data instanceof JSONArray array) { 77 | array.forEach(o -> pics.add((JSONObject) o)); 78 | } else if (data instanceof JSONObject) { 79 | pics.add((JSONObject) data); 80 | } else { 81 | throw new JSONException("Could not parse json"); 82 | } 83 | 84 | ArrayList results = new ArrayList<>(); 85 | 86 | // For each of the json objects to parse 87 | pics.forEach(jsonObject -> { 88 | List r = new LinkedList<>(); 89 | Object t = followPath(jsonObject, source.getPicUrl()); 90 | if (t instanceof JSONArray) { 91 | ((JSONArray) t).forEach(arg0 -> { 92 | Result result = new Result(); 93 | result.setUrl(String.valueOf(arg0)); 94 | r.add(result); 95 | }); 96 | } else { 97 | Result result = new Result(); 98 | result.setUrl(String.valueOf(t)); 99 | result.setJson(jsonObject); 100 | r.add(result); 101 | } 102 | 103 | r.forEach(arg0 -> { 104 | if (source.getNameRule() != null && !source.getNameRule().trim().equals("")) { 105 | arg0.setFileName(FetchUtil.replaceArgument(source.getNameRule(), jsonObject)); 106 | } else { 107 | int a = arg0.getUrl().lastIndexOf("?"); 108 | int b = arg0.getUrl().lastIndexOf("/"); 109 | if (a > b) { 110 | arg0.setFileName(arg0.getUrl().substring(b + 1, a)); 111 | } else { 112 | arg0.setFileName(arg0.getUrl().substring(b + 1)); 113 | } 114 | } 115 | for (String l : ILLEGAL_STRINGS) { 116 | arg0.setFileName(arg0.getFileName().replaceAll(l, "_")); 117 | } 118 | 119 | arg0.setFileName(new String(arg0.getFileName().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)); 120 | results.add(arg0); 121 | }); 122 | }); 123 | 124 | return results; 125 | } 126 | 127 | // Follow the given path. 128 | private static Object followPath(JSONObject obj, String path) { 129 | if (path == null || path.trim().equals("")) { 130 | return obj; 131 | } 132 | 133 | Object result = obj.clone(); 134 | 135 | String[] pathToSource = path.split("/"); 136 | for (String s : pathToSource) { 137 | if (result instanceof JSONObject) { 138 | result = ((JSONObject) result).get(s); 139 | } else if (result instanceof JSONArray) { 140 | if (s.endsWith("]") && s.lastIndexOf("[") != -1) { 141 | String prob = s.substring(s.lastIndexOf("[") + 1, s.length() - 1); 142 | try { 143 | int index = Integer.parseInt(prob); 144 | result = ((JSONArray) result).get(index); 145 | } catch (NumberFormatException e) { 146 | result = ((JSONArray) result).get(0); 147 | } 148 | } 149 | } else { 150 | throw new JSONException("Could not parse path " + path); 151 | } 152 | } 153 | 154 | return result; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/SourceManager.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.alibaba.fastjson2.JSONArray; 5 | import com.alibaba.fastjson2.JSONException; 6 | import com.alibaba.fastjson2.JSONObject; 7 | import xyz.zcraft.acgpicdownload.Main; 8 | import xyz.zcraft.acgpicdownload.exceptions.SourceConfigException; 9 | import xyz.zcraft.acgpicdownload.util.downloadutil.DownloadUtil; 10 | import xyz.zcraft.acgpicdownload.util.sourceutil.argument.*; 11 | 12 | import java.io.*; 13 | import java.util.*; 14 | 15 | public class SourceManager { 16 | private static final ArrayList returnTypes = new ArrayList<>(Arrays.asList("json", "redirect")); 17 | private static final String GITHUB_LINK = "https://raw.githubusercontent.com/zxzxy/ACGPicDownload/master/src/main/resources/xyz/zcraft/acgpicdownload/util/sourceutil/sources.json"; 18 | private static ArrayList sources; 19 | 20 | public static void readConfig() throws IOException, JSONException { 21 | sources = parse(readStringFromConfig()); 22 | } 23 | 24 | public static void updateFromGithub() throws IOException { 25 | File f = new File("sources.json"); 26 | if (f.exists()) f.delete(); 27 | DownloadUtil.download(f, GITHUB_LINK, null); 28 | } 29 | 30 | private static String readStringFromConfig() throws IOException { 31 | StringBuilder sb = new StringBuilder(); 32 | File f = new File("sources.json"); 33 | if (!f.exists()) { 34 | if (!f.createNewFile()) { 35 | throw new IOException("Could not create " + f); 36 | } 37 | BufferedWriter bw = new BufferedWriter(new FileWriter(f)); 38 | BufferedReader reader = new BufferedReader(new InputStreamReader( 39 | Objects.requireNonNull(SourceManager.class.getResource("sources.json")).openStream())); 40 | 41 | String line; 42 | while ((line = reader.readLine()) != null) { 43 | sb.append(line); 44 | bw.write(line); 45 | bw.newLine(); 46 | } 47 | 48 | bw.close(); 49 | reader.close(); 50 | } else { 51 | BufferedReader reader = new BufferedReader(new FileReader(f)); 52 | 53 | String line; 54 | while ((line = reader.readLine()) != null) { 55 | sb.append(line); 56 | } 57 | 58 | reader.close(); 59 | } 60 | 61 | return sb.toString(); 62 | } 63 | 64 | private static ArrayList parse(String configString) throws JSONException { 65 | JSONArray config = JSON.parseArray(configString); 66 | if (config == null) { 67 | return new ArrayList<>(); 68 | } 69 | ArrayList sources = new ArrayList<>(); 70 | config.forEach(o -> { 71 | Source s = JSONObject.parseObject(String.valueOf(o), Source.class); 72 | try { 73 | verifySource(s); 74 | s.setArguments(parseArguments(s.getDefaultArgs())); 75 | sources.add(s); 76 | } catch (SourceConfigException | JSONException e) { 77 | System.err.println("Failed to parse source " + s.getName() + " : " + e); 78 | Main.logError(e); 79 | } 80 | }); 81 | return sources; 82 | } 83 | 84 | private static void verifySource(Source source) throws SourceConfigException { 85 | if (isEmpty(source.getName())) { 86 | throw new SourceConfigException("Source name must not be empty"); 87 | } 88 | if (isEmpty(source.getUrl())) { 89 | throw new SourceConfigException("Source url must not be empty"); 90 | } 91 | if (!isEmpty(source.getReturnType()) && !returnTypes.contains(source.getReturnType())) { 92 | throw new SourceConfigException("Unknown return type:" + source.getReturnType()); 93 | } 94 | } 95 | 96 | public static boolean isEmpty(String str) { 97 | return str == null || str.trim().isEmpty(); 98 | } 99 | 100 | public static ArrayList getSources() { 101 | return sources; 102 | } 103 | 104 | public static Source getSourceByName(List sources, String name) { 105 | for (Source source : sources) { 106 | if (source.getName().equals(name)) { 107 | return source; 108 | } 109 | } 110 | return null; 111 | } 112 | 113 | public static ArrayList> parseArguments(JSONObject args) throws SourceConfigException, JSONException { 114 | ArrayList> t = new ArrayList<>(); 115 | if (args == null) { 116 | return t; 117 | } 118 | 119 | for (String key : args.keySet()) { 120 | t.add(parseArgument(key, args.getJSONObject(key))); 121 | } 122 | 123 | return t; 124 | } 125 | 126 | public static Argument parseArgument(String name, JSONObject arg) throws SourceConfigException { 127 | Argument t = null; 128 | 129 | if (arg.containsKey("type")) { 130 | if (arg.get("type").equals("int")) { 131 | if (arg.containsKey("min") || arg.containsKey("max") || arg.containsKey("step")) { 132 | IntegerLimit l = new IntegerLimit(arg.getInteger("min"), arg.getInteger("max"), arg.getInteger("step")); 133 | LimitedIntegerArgument lia = new LimitedIntegerArgument(name, l); 134 | if (arg.containsKey("value")) { 135 | lia.set(arg.getInteger("value")); 136 | } 137 | t = lia; 138 | } else { 139 | t = new IntegerArgument(name); 140 | } 141 | } else if (arg.get("type").equals("string")) { 142 | if (arg.containsKey("from")) { 143 | Object obj = arg.get("from"); 144 | if (obj instanceof JSONArray arr) { 145 | HashSet tmp = new HashSet<>(); 146 | arr.forEach(arg0 -> tmp.add(String.valueOf(arg0))); 147 | LimitedStringArgument lsa = new LimitedStringArgument(name, tmp); 148 | if (arg.containsKey("value")) { 149 | lsa.set(arg.getString("value")); 150 | } 151 | t = lsa; 152 | } 153 | } else { 154 | t = new StringArgument(name); 155 | } 156 | } 157 | } 158 | 159 | if (t == null) { 160 | throw new SourceConfigException(); 161 | } else { 162 | return t; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/Argument.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | public abstract class Argument { 4 | protected final String name; 5 | protected T value; 6 | 7 | public Argument(String name) { 8 | this.name = name; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return "Argument {name=" + name + ", value=" + value + "}"; 14 | } 15 | 16 | public abstract void set(T value); 17 | 18 | public abstract boolean isValid(T value); 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public T getValue() { 25 | return value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/IntegerArgument.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | public class IntegerArgument extends Argument { 4 | 5 | public IntegerArgument(String name) { 6 | super(name); 7 | } 8 | 9 | @Override 10 | public void set(Integer value) { 11 | this.value = value; 12 | } 13 | 14 | 15 | @Override 16 | public boolean isValid(Integer value) { 17 | return true; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "IntegerArgument {name=" + name + ", value=" + value + "}"; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/IntegerLimit.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | public class IntegerLimit { 4 | private Integer minValue = null; 5 | private Integer maxValue = null; 6 | private Integer step = null; 7 | private boolean hasMax = false; 8 | private boolean hasMin = false; 9 | private boolean hasStep = false; 10 | 11 | private IntegerLimit() { 12 | } 13 | 14 | public IntegerLimit(Integer minValue, Integer maxValue) { 15 | hasMax = true; 16 | hasMin = true; 17 | hasStep = false; 18 | this.minValue = minValue; 19 | this.maxValue = maxValue; 20 | this.step = 1; 21 | } 22 | 23 | public IntegerLimit(Integer minValue, Integer maxValue, Integer step) { 24 | if (minValue != null) { 25 | hasMin = true; 26 | this.minValue = minValue; 27 | } 28 | if (maxValue != null) { 29 | hasMax = true; 30 | this.maxValue = maxValue; 31 | } 32 | if (step != null) { 33 | hasStep = true; 34 | this.step = step; 35 | } 36 | } 37 | 38 | public static IntegerLimit biggerThan(Integer minValue, Integer step) { 39 | IntegerLimit t = new IntegerLimit(); 40 | t.setMaxValue(minValue); 41 | t.setStep(step); 42 | t.hasMax = false; 43 | t.hasMin = true; 44 | t.hasStep = true; 45 | return t; 46 | } 47 | 48 | public static IntegerLimit biggerThan(Integer minValue) { 49 | IntegerLimit t = new IntegerLimit(); 50 | t.setMaxValue(minValue); 51 | t.setStep(1); 52 | t.hasMax = false; 53 | t.hasMin = true; 54 | t.hasStep = false; 55 | return t; 56 | } 57 | 58 | public static IntegerLimit smallThan(Integer maxValue) { 59 | IntegerLimit t = new IntegerLimit(); 60 | t.setMaxValue(maxValue); 61 | t.setStep(1); 62 | t.hasMax = true; 63 | t.hasMin = false; 64 | t.hasStep = false; 65 | return t; 66 | } 67 | 68 | public static IntegerLimit smallThan(Integer maxValue, Integer step) { 69 | IntegerLimit t = new IntegerLimit(); 70 | t.setMaxValue(maxValue); 71 | t.setStep(step); 72 | t.hasMax = true; 73 | t.hasMin = false; 74 | t.hasStep = true; 75 | return t; 76 | } 77 | 78 | public static IntegerLimit range(Integer minValue, Integer maxValue, Integer step) { 79 | return new IntegerLimit(minValue, maxValue, step); 80 | } 81 | 82 | public static IntegerLimit range(Integer minValue, Integer maxValue) { 83 | return new IntegerLimit(minValue, maxValue); 84 | } 85 | 86 | public boolean isValid(Integer value) { 87 | if (hasMax && value > maxValue) { 88 | return false; 89 | } 90 | if (hasMin && value < minValue) { 91 | return false; 92 | } 93 | if (hasStep) { 94 | if (hasMax && (value - maxValue) % step != 0) { 95 | return false; 96 | } else if (hasMin && (value - minValue) % step != 0) { 97 | return false; 98 | } else { 99 | return value % step == 0; 100 | } 101 | } 102 | return true; 103 | } 104 | 105 | public boolean isHasMax() { 106 | return hasMax; 107 | } 108 | 109 | public boolean isHasMin() { 110 | return hasMin; 111 | } 112 | 113 | public boolean isHasStep() { 114 | return hasStep; 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return "IntegerLimit [minValue=" + minValue + ", maxValue=" + maxValue + ", step=" + step + "]"; 120 | } 121 | 122 | public Integer getMinValue() { 123 | return minValue; 124 | } 125 | 126 | public void setMinValue(Integer minValue) { 127 | this.minValue = minValue; 128 | } 129 | 130 | public Integer getMaxValue() { 131 | return maxValue; 132 | } 133 | 134 | public void setMaxValue(Integer maxValue) { 135 | this.maxValue = maxValue; 136 | } 137 | 138 | public Integer getStep() { 139 | return step; 140 | } 141 | 142 | public void setStep(Integer step) { 143 | this.step = step; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/LimitedIntegerArgument.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | public class LimitedIntegerArgument extends IntegerArgument { 4 | private final IntegerLimit limit; 5 | 6 | public LimitedIntegerArgument(String name, IntegerLimit limit) { 7 | super(name); 8 | this.limit = limit; 9 | } 10 | 11 | public IntegerLimit getLimit() { 12 | return limit; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "LimitedIntegerArgument {limit=" + limit + ", name" + name + ", value" + value + "}"; 18 | } 19 | 20 | @Override 21 | public void set(Integer value) { 22 | if (limit.isValid(value)) { 23 | this.value = value; 24 | } else { 25 | throw new IllegalArgumentException("Illegal value:" + value); 26 | } 27 | } 28 | 29 | @Override 30 | public boolean isValid(Integer value) { 31 | return limit.isValid(value); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/LimitedStringArgument.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | import java.util.Set; 4 | 5 | public class LimitedStringArgument extends StringArgument { 6 | private final Set validValues; 7 | 8 | public LimitedStringArgument(String name, Set vaildValues) { 9 | super(name); 10 | this.validValues = vaildValues; 11 | } 12 | 13 | public Set getValidValues() { 14 | return validValues; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "LimitedStringArgument {name=" + name + ", value=" + value + ", vaildValues=" + validValues + "}"; 20 | } 21 | 22 | @Override 23 | public boolean isValid(String value) { 24 | return validValues.contains(value); 25 | } 26 | 27 | @Override 28 | public void set(String value) { 29 | if (isValid(value)) { 30 | this.value = value; 31 | } else { 32 | throw new IllegalArgumentException("Unsupported value: " + value); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/zcraft/acgpicdownload/util/sourceutil/argument/StringArgument.java: -------------------------------------------------------------------------------- 1 | package xyz.zcraft.acgpicdownload.util.sourceutil.argument; 2 | 3 | public class StringArgument extends Argument { 4 | public StringArgument(String name) { 5 | super(name); 6 | } 7 | 8 | public StringArgument(String name, String value) { 9 | super(name); 10 | set(value); 11 | } 12 | 13 | @Override 14 | public boolean isValid(String value) { 15 | return true; 16 | } 17 | 18 | @Override 19 | public void set(String value) { 20 | this.value = value; 21 | } 22 | 23 | 24 | @Override 25 | public String toString() { 26 | return "StringArgument {" + "name=" + name + ", value=" + value + "}"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=CONSOLE,LOGFILE 2 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 3 | log4j.appender.CONSOLE.Target=System.out 4 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c %x - %m%n 6 | log4j.appender.LOGFILE=org.apache.log4j.DailyRollingFileAppender 7 | log4j.appender.LOGFILE.File=./log/log.log 8 | log4j.appender.LOGFILE.Append=true 9 | log4j.appender.LOGFILE.ImmediateFlush=true 10 | log4j.appender.LOGFILE.Encoding=UTF-8 11 | log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.LOGFILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c %x - %m%n 13 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZayrexDev/ACGPicDownload/34254dd9af6edad9ab47d3f3311f283e5a8ff69f/src/main/resources/xyz/zcraft/acgpicdownload/gui/bg.png -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/default.css: -------------------------------------------------------------------------------- 1 | *{ 2 | -main: black; 3 | -mfx-main: -main; 4 | -mfx-main-color: -main; 5 | -mfx-main-color-hover: rgb(0, 0, 0, 0.2); 6 | -mfx-main-color-pressed: rgb(0, 0, 0, 0.3); 7 | -mfx-base-color: -main; 8 | -mfx-main-light: #707980; 9 | -mfx-main-lighter: #e7e9ea; 10 | } 11 | 12 | .mfx-text-field .text-field { 13 | -fx-highlight-fill: -mfx-main-lighter; 14 | } 15 | 16 | .mfx-progress-bar .bar1, 17 | .mfx-progress-bar .bar2 { 18 | -fx-fill: -main; 19 | } 20 | 21 | .mfx-ripple-generator { 22 | -mfx-ripple-color: rgba(0, 0, 0, 0.5); 23 | } 24 | 25 | .mfx-combo-box-cell { 26 | -mfx-hover: rgb(0, 0, 0, 0.1); 27 | -mfx-selected: rgb(0, 0, 0, 0.3); 28 | } 29 | 30 | .mfx-filter-pane .mfx-combo-box { 31 | -fx-background-color: -mfx-main-lighter; 32 | -fx-background-radius: 15; 33 | -fx-border-color: #ebebeb; 34 | -fx-border-radius: 15; 35 | 36 | -fx-font-family: "Open Sans SemiBold"; 37 | -fx-font-size: 14; 38 | -fx-text-fill: -mfx-gray; 39 | } 40 | 41 | .mfx-filter-pane { 42 | -mfx-main: -main; 43 | -mfx-main-light: rgb(0, 0, 0, 0.3); 44 | -mfx-main-lighter: rgb(0, 0, 0, 0.2); 45 | -mfx-gray: #6b6b6b; 46 | 47 | -fx-background-color: white; 48 | -fx-background-radius: 10; 49 | -fx-padding: 7 14 7 14; 50 | } -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/ErrorPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/LimitedIntegerArgumentPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/LimitedStringArgumentPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/MenuPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/Notice.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivAccountPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivDiscoveryPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 69 | 71 | 72 | 74 | 76 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 86 | 87 | 90 | 91 | 93 | 94 | 100 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivDownloadPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 52 | 53 | 54 | 55 | 59 | 61 | 62 | 66 | 70 | 71 | 72 | 74 | 76 | 77 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 117 | 118 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 40 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 53 | 56 | 59 | 62 | 65 | 68 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivRankingPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | 72 | 74 | 75 | 77 | 79 | 81 | 82 | 83 | 85 | 86 | 87 | 88 | 89 | 90 | 93 | 94 | 96 | 97 | 103 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivRelatedPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 71 | 73 | 74 | 76 | 78 | 80 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 95 | 96 | 102 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/PixivUserPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 71 | 73 | 74 | 76 | 78 | 80 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 95 | 96 | 102 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/fxml/StringArgumentPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/gui/icon/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZayrexDev/ACGPicDownload/34254dd9af6edad9ab47d3f3311f283e5a8ff69f/src/main/resources/xyz/zcraft/acgpicdownload/gui/icon/save.png -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/languages/String.properties: -------------------------------------------------------------------------------- 1 | gui.fetch.deleteCompleted=删除已完成 2 | gui.fetch.fetchCount=抓取次数 3 | gui.fetch.fullResult=下载完整结果 4 | gui.fetch.outputDir=下载目录 5 | gui.fetch.saveDefault=保存配置 6 | gui.fetch.sourcePrompt=下载源 7 | gui.fetch.startDownload=开始下载 8 | gui.fetch.startFetch=开始抓取 9 | gui.fetch.threadCount=线程数 10 | gui.fetch.title=抓取图片 11 | gui.welcome.greet.afternoon=下午好! 12 | gui.welcome.greet.morn=早上好! 13 | gui.welcome.greet.night=晚上好! 14 | gui.welcome.greet.noon=中午好! 15 | gui.welcome.greetSub=请问今天要来点什么呢 16 | cli.download.status.created=已创建 17 | cli.download.status.started=下载中 18 | cli.download.status.completed=已完成 19 | cli.download.status.failed=失败 20 | gui.fetch.loading.readSource=正在读取源 21 | gui.fetch.loading.fetch=正在抓取 22 | gui.fetch.table.column.fileName=文件名 23 | gui.fetch.table.column.link=链接 24 | gui.fetch.table.column.status=状态 25 | gui.fetch.notice.removeCompleted=移除了%d个 26 | gui.fetch.notice.saved=已保存 27 | gui.fetch.updateFromGithub=从Github更新下载源 28 | gui.fetch.table.copy=已复制到剪切板 29 | cli.fetch.info.fetch=正在从 %s 抓取... 30 | cli.fetch.err.cannotReadSource=无法读取 sources.json 31 | cli.fetch.err.cannotParseSource=无法解析 sources.json 32 | cli.fetch.err.sourceNotFound=找不到下载源 %s 33 | cli.fetch.err.sourceNotFoundFull=找不到下载源 %s. 请检查 sources.json . 使用 "--list-sources" 以列出所有可用的下载源 34 | cli.fetch.info.noPicGot=没有抓取到任何图片! 35 | cli.fetch.info.gotPic=抓取了 %d 张图! 36 | cli.fetch.err.noSourceFound=没有可用的下载源 37 | cli.fetch.err.cannotParseArgFull=未知的参数 %s . 请使用 -h 查看用法. 38 | cli.fetch.err.proxyInvalid=请提供有效的代理地址 39 | cli.fetch.err.numberInvalid=请提供有效的数字 40 | cli.fetch.err.noArg=请提供参数 41 | cli.fetch.err.pathInvalid=请提供有效的路径 42 | cli.fetch.err.sourceNameInvalid=请提供有效的下载源名称 43 | cli.fetch=抓取 44 | cli.fetch.err.rateLimit=错误:抓取速度太快 45 | cli.fetch.err=错误 46 | cli.fetch.err.cannotCreatDir=无法创建目录 47 | gui.pixiv.menu.title=抓取Pixiv主页 48 | gui.pixiv.menu.cookie=Cookie 49 | gui.pixiv.menu.submitCookie=保存Cookie 50 | gui.pixiv.menu.maxCount=最大返回数量 51 | gui.pixiv.menu.relatedDepth=抓取相关深度 52 | gui.pixiv.menu.typeToFetch=抓取来源 53 | gui.pixiv.menu.from.follow=关注 54 | gui.pixiv.menu.from.recommend=推荐 55 | gui.pixiv.menu.from.other=其它 56 | gui.pixiv.menu.fetch=抓取 57 | gui.pixiv.menu.sendToDownload=发送到下载 58 | gui.welcome.pixiv.menu=抓取主页 59 | gui.welcome.pixiv.download=下载 60 | gui.welcome.pixiv.title=抓取Pixiv 61 | gui.welcome.pixiv.back=返回 62 | gui.settings.title=设置 63 | gui.settings.proxyPrompt=若为空则不使用代理 64 | gui.settings.proxy=代理 65 | gui.settings.save=保存设置 66 | gui.pixiv.download.title=下载 67 | gui.pixiv.download.start=开始下载 68 | gui.pixiv.download.deleteFinished=删除已完成/被过滤 69 | gui.pixiv.download.delSelected=删除选中 70 | gui.pixiv.download.column.title=标题 71 | gui.pixiv.download.column.from=来源 72 | gui.pixiv.download.column.tag=标签 73 | gui.pixiv.download.column.id=ID 74 | gui.pixiv.download.column.status=状态 75 | gui.pixiv.download.copied=已复制连接 76 | gui.pixiv.menu.notice.fetchMain=抓取中 77 | gui.pixiv.menu.notice.fetchRel=抓取相关 78 | gui.pixiv.menu.notice.fetched=抓取到%d个! 79 | gui.pixiv.menu.notice.sent=已发送到下载页 80 | gui.pixiv.download.outputDir=下载路径 81 | gui.pixiv.download.threadCount=线程数 82 | fetch.pixiv.from.follow=关注 83 | fetch.pixiv.from.recommend=推荐 84 | fetch.pixiv.from.recommendUser=推荐用户 85 | fetch.pixiv.from.recommendTag=推荐标签 86 | fetch.pixiv.from.related=相关 87 | fetch.pixiv.from.spec=特定 88 | fetch.pixiv.from.other=其它 89 | gui.pixiv.notice.savedCookie=Cookie已保存 90 | gui.pixiv.menu.deleteSelected=删除已选择 91 | gui.pixiv.menu.sendSelectedToDownload=发送选中到下载 92 | fetch.pixiv.notice.sent=发送了%d个 93 | gui.pixiv.menu.copySelected=复制选中 94 | gui.pixiv.menu.clearSelected=取消选择 95 | fetch.pixiv.notice.clearSelected=取消了%d个 96 | gui.pixiv.menu.column.title=标题 97 | gui.pixiv.menu.column.from=来源 98 | gui.pixiv.menu.column.tag=标签 99 | gui.pixiv.menu.column.id=ID 100 | gui.settings.aniSpeed=动画速度 101 | fetch.pixiv.from.user=用户 102 | gui.welcome.pixiv.disc=抓取发现 103 | gui.pixiv.disc.title=抓取发现 104 | fetch.pixiv.from.disc=发现 105 | gui.pixiv.disc.mode=模式 106 | gui.pixiv.disc.mode.all=全部 107 | gui.pixiv.disc.mode.safe=安全 108 | gui.pixiv.disc.mode.adult=涩涩 109 | gui.settings.fetchPaneLeftClickOperation=抓取结果左键点击操作 110 | gui.settings.fetchPaneLeftClickOperation.copy=复制连接 111 | gui.settings.fetchPaneLeftClickOperation.open=打开 112 | gui.fetch.table.cantOpen=无法打开 113 | gui.pixiv.user.title=抓取用户 114 | gui.pixiv.user.uid=UID 115 | gui.welcome.pixiv.user=抓取用户 116 | gui.pixiv.related.id=作品ID 117 | gui.seriousERR=错误:程序遇到问题,无法启动 118 | gui.welcome.pixiv.related=抓取相关 119 | cli.download.status.init=获取链接 120 | gui.pixiv.menu.column.author=作者 121 | err.status.401=Cookie已过期 (重新获取Cookie) 122 | err.status.404=找不到UID/ID (检查原页面上的ID或UID) 123 | err.status.427=请求太多 (等待一段时间即可) 124 | err.status.timeout=需要使用代理访问\n网络不通畅 125 | err.status.invalidProxy=使用的代理%s不正确 (在程序设置中删除代理设置,或者启动原代理) 126 | err.status.ssl=SSL证书错误 127 | err.conduct=程序出现错误,可能是因为: 128 | gui.err.openFAQ=打开常见问题页面 129 | gui.err.ok=好叭 130 | gui.err=错误 131 | gui.settings.lang=语言 132 | gui.path.home=主页 133 | gui.path.pixiv=Pixiv抓取 134 | gui.settings.bg=背景(需要重启) 135 | gui.settings.bg.folder=来自文件夹 136 | gui.settings.bg.default=默认背景 137 | gui.settings.bg.transparent=透明 138 | fetch.pixiv.type.illust=插画 139 | fetch.pixiv.type.gif=动图 140 | gui.pixiv.download.column.type=类型 141 | gui.pixiv.download.namingRule=命名规则 142 | gui.pixiv.download.multiPageRule=多P作品规则 143 | gui.pixiv.download.multiPageRule.separated=保存至单独的文件夹 144 | gui.pixiv.download.folderNamingRule=文件夹命名规则 145 | gui.pixiv.download.multiPageRule.gathered=一同保存在下载目录 146 | gui.welcome.pixiv.ranking=抓取榜单 147 | gui.pixiv.ranking.title=抓取榜单 148 | gui.pixiv.ranking.major=榜单类型 149 | gui.pixiv.ranking.minor=作品类型 150 | gui.pixiv.ranking.adult=涩涩 151 | gui.pixiv.ranking.daily=每日 152 | gui.pixiv.ranking.weekly=每周 153 | gui.pixiv.ranking.monthly=每月 154 | gui.pixiv.ranking.rookie=新人 155 | gui.pixiv.ranking.original=原创 156 | gui.pixiv.ranking.daily_ai=AI作品 157 | gui.pixiv.ranking.male=受男性欢迎 158 | gui.pixiv.ranking.female=受女性欢迎 159 | gui.pixiv.ranking.minor.illust=插画 160 | gui.pixiv.ranking.minor.ugoira=动图 161 | gui.pixiv.ranking.minor.manga=漫画 162 | gui.pixiv.ranking.retries=尝试次数 163 | gui.pixiv.ranking.minor.all=综合 164 | gui.pixiv.ranking.notice.getting=获取信息 165 | err.status.403=没有权限浏览(检查账户设定) 166 | fetch.pixiv.from.ranking=榜单 167 | gui.pixiv.download.full=下载完整结果 168 | gui.pixiv.search.title=搜索作品 169 | gui.pixiv.search.page=页数 170 | gui.pixiv.search.type=类型 171 | gui.pixiv.search.mode=模式 172 | gui.pixiv.search.mode.top=顶部 173 | gui.pixiv.search.mode.illust=动图/插画 174 | gui.pixiv.search.mode.manga=漫画 175 | gui.pixiv.search.keyword=关键词 176 | gui.welcome.pixiv.search=搜索作品 177 | fetch.pixiv.from.search=搜索 178 | gui.pixiv.ranking.failed=失败 179 | gui.pixiv.search.suffix=搜索后缀(若使用建议用日语搜索) 180 | cli.download.status.filtered=被过滤 181 | gui.pixiv.download.cond.bookmark=Bookmark数不小于 182 | gui.pixiv.download.cond.like=Like数不小于 183 | gui.pixiv.download.cond=条件 184 | gui.welcome.pixiv.loginAs=账号 185 | gui.pixiv.account.title=登录Pixiv账号 186 | gui.pixiv.account.add=添加账号 187 | gui.pixiv.account.del=删除账号 188 | gui.pixiv.account.addFailed=添加失败! 189 | gui.pixiv.account.added=添加成功! 190 | gui.pixiv.account.notLogin=未登录 191 | gui.pixiv.account.refresh=刷新 192 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/languages/String_zh_CN.properties: -------------------------------------------------------------------------------- 1 | gui.fetch.deleteCompleted=删除已完成/被过滤 2 | gui.fetch.fetchCount=抓取次数 3 | gui.fetch.fullResult=下载完整结果 4 | gui.fetch.outputDir=下载目录 5 | gui.fetch.saveDefault=保存配置 6 | gui.fetch.sourcePrompt=下载源 7 | gui.fetch.startDownload=开始下载 8 | gui.fetch.startFetch=开始抓取 9 | gui.fetch.threadCount=线程数 10 | gui.fetch.title=抓取图片 11 | gui.welcome.greet.afternoon=下午好! 12 | gui.welcome.greet.morn=早上好! 13 | gui.welcome.greet.night=晚上好! 14 | gui.welcome.greet.noon=中午好! 15 | gui.welcome.greetSub=请问今天要来点什么呢 16 | cli.download.status.created=已创建 17 | cli.download.status.started=下载中 18 | cli.download.status.completed=已完成 19 | cli.download.status.failed=失败 20 | gui.fetch.loading.readSource=正在读取源 21 | gui.fetch.loading.fetch=正在抓取 22 | gui.fetch.table.column.fileName=文件名 23 | gui.fetch.table.column.link=链接 24 | gui.fetch.table.column.status=状态 25 | gui.fetch.notice.removeCompleted=移除了%d个 26 | gui.fetch.notice.saved=已保存 27 | gui.fetch.updateFromGithub=从Github更新下载源 28 | gui.fetch.table.copy=已复制到剪切板 29 | cli.fetch.info.fetch=正在从 %s 抓取... 30 | cli.fetch.err.cannotReadSource=无法读取 sources.json 31 | cli.fetch.err.cannotParseSource=无法解析 sources.json 32 | cli.fetch.err.sourceNotFound=找不到下载源 %s 33 | cli.fetch.err.sourceNotFoundFull=找不到下载源 %s. 请检查 sources.json . 使用 "--list-sources" 以列出所有可用的下载源 34 | cli.fetch.info.noPicGot=没有抓取到任何图片! 35 | cli.fetch.info.gotPic=抓取了 %d 张图! 36 | cli.fetch.err.noSourceFound=没有可用的下载源 37 | cli.fetch.err.cannotParseArgFull=未知的参数 %s . 请使用 -h 查看用法. 38 | cli.fetch.err.proxyInvalid=请提供有效的代理地址 39 | cli.fetch.err.numberInvalid=请提供有效的数字 40 | cli.fetch.err.noArg=请提供参数 41 | cli.fetch.err.pathInvalid=请提供有效的路径 42 | cli.fetch.err.sourceNameInvalid=请提供有效的下载源名称 43 | cli.fetch=抓取 44 | cli.fetch.err.rateLimit=错误:抓取速度太快 45 | cli.fetch.err=错误 46 | cli.fetch.err.cannotCreatDir=无法创建目录 47 | gui.pixiv.menu.title=抓取Pixiv主页 48 | gui.pixiv.menu.cookie=Cookie 49 | gui.pixiv.menu.submitCookie=保存Cookie 50 | gui.pixiv.menu.maxCount=最大返回数量 51 | gui.pixiv.menu.relatedDepth=抓取相关深度 52 | gui.pixiv.menu.typeToFetch=抓取来源 53 | gui.pixiv.menu.from.follow=关注 54 | gui.pixiv.menu.from.recommend=推荐 55 | gui.pixiv.menu.from.other=其它 56 | gui.pixiv.menu.fetch=抓取 57 | gui.pixiv.menu.sendToDownload=发送到下载 58 | gui.welcome.pixiv.menu=抓取主页 59 | gui.welcome.pixiv.download=下载 60 | gui.welcome.pixiv.title=抓取Pixiv 61 | gui.welcome.pixiv.back=返回 62 | gui.settings.title=设置 63 | gui.settings.proxyPrompt=若为空则不使用代理 64 | gui.settings.proxy=代理 65 | gui.settings.save=保存设置 66 | gui.pixiv.download.title=下载 67 | gui.pixiv.download.start=开始下载 68 | gui.pixiv.download.deleteFinished=删除已完成/被过滤 69 | gui.pixiv.download.delSelected=删除选中 70 | gui.pixiv.download.column.title=标题 71 | gui.pixiv.download.column.from=来源 72 | gui.pixiv.download.column.tag=标签 73 | gui.pixiv.download.column.id=ID 74 | gui.pixiv.download.column.status=状态 75 | gui.pixiv.download.copied=已复制连接 76 | gui.pixiv.menu.notice.fetchMain=抓取中 77 | gui.pixiv.menu.notice.fetchRel=抓取相关 78 | gui.pixiv.menu.notice.fetched=抓取到%d个! 79 | gui.pixiv.menu.notice.sent=已发送到下载页 80 | gui.pixiv.download.outputDir=下载路径 81 | gui.pixiv.download.threadCount=线程数 82 | fetch.pixiv.from.follow=关注 83 | fetch.pixiv.from.recommend=推荐 84 | fetch.pixiv.from.recommendUser=推荐用户 85 | fetch.pixiv.from.recommendTag=推荐标签 86 | fetch.pixiv.from.related=相关 87 | fetch.pixiv.from.other=其它 88 | gui.pixiv.notice.savedCookie=Cookie已保存 89 | gui.pixiv.menu.deleteSelected=删除已选择 90 | gui.pixiv.menu.sendSelectedToDownload=发送选中到下载 91 | fetch.pixiv.notice.sent=发送了%d个 92 | gui.pixiv.menu.copySelected=复制选中 93 | gui.pixiv.menu.clearSelected=取消选择 94 | fetch.pixiv.notice.clearSelected=取消了%d个 95 | gui.pixiv.menu.column.title=标题 96 | gui.pixiv.menu.column.from=来源 97 | gui.pixiv.menu.column.tag=标签 98 | gui.pixiv.menu.column.id=ID 99 | gui.settings.aniSpeed=动画速度 100 | fetch.pixiv.from.user=用户 101 | gui.welcome.pixiv.disc=抓取发现 102 | gui.pixiv.disc.title=抓取发现 103 | fetch.pixiv.from.disc=发现 104 | gui.pixiv.disc.mode=模式 105 | gui.pixiv.disc.mode.all=全部 106 | gui.pixiv.disc.mode.safe=安全 107 | gui.pixiv.disc.mode.adult=涩涩 108 | gui.settings.fetchPaneLeftClickOperation=抓取结果左键点击操作 109 | gui.settings.fetchPaneLeftClickOperation.copy=复制连接 110 | gui.settings.fetchPaneLeftClickOperation.open=打开 111 | gui.fetch.table.cantOpen=无法打开 112 | gui.pixiv.user.title=抓取用户 113 | gui.pixiv.user.uid=UID 114 | gui.welcome.pixiv.user=抓取用户 115 | gui.pixiv.related.id=作品ID 116 | gui.seriousERR=错误:程序遇到问题,无法启动 117 | gui.welcome.pixiv.related=抓取相关 118 | cli.download.status.init=获取链接 119 | gui.pixiv.menu.column.author=作者 120 | err.status.401=Cookie已过期 (重新获取Cookie) 121 | err.status.404=找不到UID/ID (检查原页面上的ID或UID) 122 | err.status.427=请求太多 (等待一段时间即可) 123 | err.status.timeout=需要使用代理访问\n网络不通畅 124 | err.status.invalidProxy=使用的代理%s不正确 (在程序设置中删除代理设置,或者启动原代理) 125 | err.status.ssl=SSL证书错误 126 | err.conduct=程序出现错误,可能是因为: 127 | gui.err.openFAQ=打开常见问题页面 128 | gui.err.ok=好叭 129 | gui.err=错误 130 | gui.settings.lang=语言 131 | gui.path.home=主页 132 | gui.path.pixiv=Pixiv抓取 133 | gui.settings.bg=背景(需要重启) 134 | gui.settings.bg.folder=来自文件夹 135 | gui.settings.bg.default=默认背景 136 | gui.settings.bg.transparent=透明 137 | fetch.pixiv.type.illust=插画 138 | fetch.pixiv.type.gif=动图 139 | gui.pixiv.download.column.type=类型 140 | fetch.pixiv.from.spec=特定 141 | gui.pixiv.download.namingRule=命名规则 142 | gui.pixiv.download.multiPageRule=多P作品规则 143 | gui.pixiv.download.multiPageRule.separated=保存至单独的文件夹 144 | gui.pixiv.download.folderNamingRule=文件夹命名规则 145 | gui.pixiv.download.multiPageRule.gathered=一同保存在下载目录 146 | gui.welcome.pixiv.ranking=抓取榜单 147 | gui.pixiv.ranking.title=抓取榜单 148 | gui.pixiv.ranking.major=榜单类型 149 | gui.pixiv.ranking.minor=作品类型 150 | gui.pixiv.ranking.adult=涩涩 151 | gui.pixiv.ranking.daily=每日 152 | gui.pixiv.ranking.weekly=每周 153 | gui.pixiv.ranking.monthly=每月 154 | gui.pixiv.ranking.rookie=新人 155 | gui.pixiv.ranking.original=原创 156 | gui.pixiv.ranking.daily_ai=AI作品 157 | gui.pixiv.ranking.male=受男性欢迎 158 | gui.pixiv.ranking.female=受女性欢迎 159 | gui.pixiv.ranking.minor.illust=插画 160 | gui.pixiv.ranking.minor.ugoira=动图 161 | gui.pixiv.ranking.minor.manga=漫画 162 | gui.pixiv.ranking.minor.all=综合 163 | gui.pixiv.ranking.notice.getting=获取信息 164 | err.status.403=没有权限浏览(检查账户设定) 165 | fetch.pixiv.from.ranking=榜单 166 | gui.pixiv.ranking.retries=尝试次数 167 | gui.pixiv.download.full=下载完整结果 168 | gui.pixiv.search.title=搜索作品 169 | gui.pixiv.search.page=页数 170 | gui.pixiv.search.type=类型 171 | gui.pixiv.search.mode=模式 172 | gui.pixiv.search.mode.top=顶部 173 | gui.pixiv.search.mode.illust=动图/插画 174 | gui.pixiv.search.mode.manga=漫画 175 | gui.pixiv.search.keyword=关键词 176 | gui.welcome.pixiv.search=搜索作品 177 | fetch.pixiv.from.search=搜索 178 | gui.pixiv.ranking.failed=失败 179 | gui.pixiv.search.suffix=搜索后缀(若使用建议用日语搜索) 180 | cli.download.status.filtered=被过滤 181 | gui.pixiv.download.cond.bookmark=Bookmark数不小于 182 | gui.pixiv.download.cond.like=Like数不小于 183 | gui.pixiv.download.cond=条件 184 | gui.welcome.pixiv.loginAs=账号 185 | gui.pixiv.account.title=登录Pixiv账号 186 | gui.pixiv.account.add=添加账号 187 | gui.pixiv.account.del=删除账号 188 | gui.pixiv.account.addFailed=添加失败! 189 | gui.pixiv.account.added=添加成功! 190 | gui.pixiv.account.notLogin=未登录 191 | gui.pixiv.account.refresh=刷新 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /src/main/resources/xyz/zcraft/acgpicdownload/util/sourceutil/sources.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "lolicon", 4 | "description": "Picture from Lolicon API (api.lolicon.app)", 5 | "url": "https://api.lolicon.app/setu/v2?{r18=$r18}{&num=$num}{&keyword=$keyword}{&tag=$tag}", 6 | "returnType": "json", 7 | "defaultArgs": { 8 | "r18": { 9 | "type": "string", 10 | "from": [ 11 | 0, 12 | 1, 13 | 2 14 | ], 15 | "value": "0" 16 | }, 17 | "num": { 18 | "type": "int", 19 | "min": 1, 20 | "max": 20, 21 | "value": 1 22 | }, 23 | "keyword": { 24 | "type": "string" 25 | }, 26 | "tag": { 27 | "type": "string" 28 | } 29 | }, 30 | "picUrl": "urls/original", 31 | "sourceKey": "data", 32 | "nameRule": "{PID-$pid }{$title}{ by $author}{.$ext}" 33 | }, 34 | { 35 | "name": "dmoe", 36 | "description": "Picture from Dmoe API (dmoe.cc)", 37 | "returnType": "redirect", 38 | "url": "https://www.dmoe.cc/random.php", 39 | "picUrl": "imgurl" 40 | }, 41 | { 42 | "name": "mirlkoi", 43 | "description": "Picture from MirlKoiAPI (api.iw233.cn)", 44 | "returnType": "json", 45 | "url": "https://iw233.cn/api.php?sort=random&type=json{&num=$num}", 46 | "defaultArgs": { 47 | "num": { 48 | "type": "int", 49 | "min": 1, 50 | "max": 100, 51 | "value": 1 52 | } 53 | }, 54 | "picUrl": "pic" 55 | }, 56 | { 57 | "name": "yande", 58 | "description": "Picture from Yande (yande.re)", 59 | "returnType": "json", 60 | "url": "https://yande.re/post.json?{limit=$limit}{&tag=$tag}{&page=$page}", 61 | "defaultArgs": { 62 | "limit": { 63 | "type": "int", 64 | "min": 1, 65 | "max": 100, 66 | "value": 10 67 | }, 68 | "tag": { 69 | "type": "string" 70 | }, 71 | "page": { 72 | "type": "int", 73 | "min": 1, 74 | "max": 100 75 | } 76 | }, 77 | "picUrl": "file_url", 78 | "nameRule": "{$md5}{.$file_ext}" 79 | }, 80 | { 81 | "name": "konachan", 82 | "description": "Picture from Konachan (konachan.com)", 83 | "returnType": "json", 84 | "url": "https://konachan.com/post.json?{limit=$limit}{&tag=$tag}{&page=$page}", 85 | "defaultArgs": { 86 | "limit": { 87 | "type": "int", 88 | "min": 1, 89 | "max": 100, 90 | "value": 10 91 | }, 92 | "tag": { 93 | "type": "string" 94 | }, 95 | "page": { 96 | "type": "int", 97 | "min": 1, 98 | "max": 100 99 | } 100 | }, 101 | "picUrl": "file_url" 102 | }, 103 | { 104 | "name": "btstu", 105 | "description": "Picture from btstu (api.btstu.cn)", 106 | "returnType": "redirect", 107 | "url": "https://api.btstu.cn/sjbz/api.php?lx=dongman&format=images" 108 | }, 109 | { 110 | "name": "r10086", 111 | "description": "Picture from r10086 (img.r10086.com)", 112 | "returnType": "redirect", 113 | "url": "https://api.r10086.com/img-api.php?type=%E5%8A%A8%E6%BC%AB%E7%BB%BC%E5%90%881" 114 | }, 115 | { 116 | "name": "mtyqx", 117 | "description": "Picture from mtyqx (api.mtyqx.cn)", 118 | "returnType": "redirect", 119 | "url": "https://api.mtyqx.cn/tapi/random.php" 120 | }, 121 | { 122 | "name": "eee.dog", 123 | "description": "Picture from eee.dog (www.eee.dog/tech/rand-pic-api.html)", 124 | "returnType": "redirect", 125 | "url": "https://api.yimian.xyz/img?type=moe{&r18=$r18}", 126 | "defaultArgs": { 127 | "r18": { 128 | "type": "string", 129 | "from": [ 130 | "false", 131 | "true" 132 | ] 133 | } 134 | } 135 | }, 136 | { 137 | "name": "xiaowai", 138 | "description": "Picture from xiaowai (api.ixiaowai.cn)", 139 | "returnType": "redirect", 140 | "url": "https://api.ixiaowai.cn/api/api.php" 141 | }, 142 | { 143 | "name": "vvhan", 144 | "description": "Picture from vvhan (api.vvhan.com)", 145 | "returnType": "redirect", 146 | "url": "https://api.vvhan.com/api/acgimg" 147 | }, 148 | { 149 | "name": "tenapi", 150 | "description": "Picture from tenapi (tenapi.cn)", 151 | "returnType": "redirect", 152 | "url": "https://tenapi.cn/acg" 153 | } 154 | ] --------------------------------------------------------------------------------