├── .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 |
5 |
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 | 
20 | 
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 | 
21 | 
22 |
23 | 命令行:
24 | 
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 extends String> 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 |
36 |
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 |
12 |
13 |
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 |
29 |
32 |
34 |
36 |
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 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
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 |
102 |
104 |
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 |
40 |
45 |
46 |
51 |
52 |
53 |
54 |
55 |
59 |
61 |
62 |
66 |
70 |
71 |
72 |
74 |
76 |
77 |
78 |
80 |
82 |
83 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
99 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
114 |
116 |
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 |
42 |
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 |
30 |
35 |
36 |
41 |
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 |
105 |
107 |
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 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
104 |
106 |
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 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
104 |
106 |
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 | ]
--------------------------------------------------------------------------------