From 1b215406eada8e225511c77c69d371b0873cf67b Mon Sep 17 00:00:00 2001 From: Zayrex <478576442@qq.com> Date: Tue, 23 Sep 2025 12:02:27 +0800 Subject: [PATCH 1/4] =?UTF-8?q?-=E5=88=A0=E9=99=A4=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/GifTest.java | 16 -------------- src/test/java/PixivAccountTest.java | 30 -------------------------- src/test/java/PixivBookmarkTest.java | 32 ---------------------------- 3 files changed, 78 deletions(-) delete mode 100644 src/test/java/GifTest.java delete mode 100644 src/test/java/PixivAccountTest.java delete mode 100644 src/test/java/PixivBookmarkTest.java diff --git a/src/test/java/GifTest.java b/src/test/java/GifTest.java deleted file mode 100644 index 56d4cba..0000000 --- a/src/test/java/GifTest.java +++ /dev/null @@ -1,16 +0,0 @@ -import xyz.zcraft.acgpicdownload.util.pixiv.GifData; -import xyz.zcraft.acgpicdownload.util.pixiv.PixivArtwork; -import xyz.zcraft.acgpicdownload.util.pixiv.PixivFetchUtil; - -import java.io.IOException; - -public class GifTest { - private static final String cookieString = "PHPSESSID=53586815_etMGmfiftP72YvjcUPE3qRGwd8xHHAj2"; - - public static void main(String[] args) throws IOException { - final PixivArtwork pixivArtwork = new PixivArtwork(); - pixivArtwork.setId("91945977"); - GifData gifData = PixivFetchUtil.getGifData(pixivArtwork, cookieString, "127.0.0.1", 7890); - System.out.println(gifData.getSrc()); - } -} diff --git a/src/test/java/PixivAccountTest.java b/src/test/java/PixivAccountTest.java deleted file mode 100644 index 4e8faf9..0000000 --- a/src/test/java/PixivAccountTest.java +++ /dev/null @@ -1,30 +0,0 @@ -import com.alibaba.fastjson2.JSONObject; -import org.jsoup.Connection; -import org.jsoup.Jsoup; - -import java.util.HashMap; -import java.util.Objects; - -import static xyz.zcraft.acgpicdownload.util.pixiv.PixivFetchUtil.parseCookie; - -public class PixivAccountTest { - public static void main(String[] args) { - String cookieString = "PHPSESSID=88458885_oPHKLxNYucl77CsueCnlimJbk7jFCnxS;"; - HashMap cookie = parseCookie(cookieString); - Connection c = Jsoup.connect("https://www.pixiv.net") - .ignoreContentType(true) - .method(Connection.Method.GET) - .cookies(cookie) - .timeout(10 * 1000); - - c.proxy("127.0.0.1", 7890); - - try { - String text = Objects.requireNonNull(c.get().getElementById("meta-global-data")).attr("content"); - final JSONObject jsonObject = JSONObject.parseObject(text); - System.out.println(jsonObject.getString("token")); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/src/test/java/PixivBookmarkTest.java b/src/test/java/PixivBookmarkTest.java deleted file mode 100644 index 0095a78..0000000 --- a/src/test/java/PixivBookmarkTest.java +++ /dev/null @@ -1,32 +0,0 @@ -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; -import org.jsoup.Connection; -import org.jsoup.Jsoup; - -import java.io.IOException; - -public class PixivBookmarkTest { - public static void main(String[] args) throws IOException { - JSONObject json = new JSONObject(); - json.put("illust_id", "104278312"); - json.put("restrict", 0); - json.put("comment", ""); - json.put("tags", new JSONArray()); - - System.out.println(json.toJSONString()); - - final String response = Jsoup.connect("https://www.pixiv.net/ajax/illusts/bookmarks/add") - .proxy("127.0.0.1", 7890) - .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.69") - .header("Cookie", "first_visit_datetime_pc=2023-01-17+20:57:49; p_ab_id=9; p_ab_id_2=3; p_ab_d_id=548119463; yuid_b=JUAnEEQ; privacy_policy_agreement=5; _ga=GA1.2.1769619120.1670332183; PHPSESSID=88458885_Ad022PSkkxj7xDaLeiJwS7LzPOyizsJv; device_token=f602092c8dd3ef28d19c163b5e115880; _ga_MZ1NL4PHH0=GS1.1.1673956724.1.1.1673956877.0.0.0; c_type=23; privacy_policy_notification=0; a_type=0; b_type=1; QSI_S_ZN_5hF4My7Ad6VNNAi=v:0:0; login_ever=yes; __cf_bm=q9Y6WtPU.i0hji5yXBtyVXsawJ2QU9qDDFfTkOt4cjI-1674913317-0-ATYCeZT0M3+Lgjh/q4oZo2HJsIDttHtt4ANl7TUFPnyyetZZVcFc8kqjuMUH8CY8RSeoOka/VMmO/51EDXtaGN8XIQGS17YG36f1jF1cEcFNptRSlniAvN6ZhQzkOPyOO4x3j10PVAyRmQt+yak5pitsJfP8nuyFMy36LDnQAvyIA3U/l7dUQRs+tGIUlDO4ciyhxfxq/Tu0vZDYRTFNWXk=; tag_view_ranking=-98s6o2-Rp~r_Jjn6Ua2V~jTSl_ciRq2~EZQqoW9r8g~2R7RYffVfj~SapL8yQw4Y~y8GNntYHsi~i83OPEGrYw~MhBZqc0gyc~Oe_3HJFeBA~O64_t9evHE~O2wfZxfonb~mYDe1eOQZ4~yL80PVO9Um~A7hSoqw-5Z~VTGlg0gWIi~cryvQ5p2Tx~Ged1jLxcdL~01ilCGA69_~SAyihQLaXc~Qq_PJ4rEpq~Ysy4PAF_ss~PwDMGzD6xn~qWFESUmfEs~Nj9Bt-JWdN~_IharlAfPe~jk9IzfjZ6n~n39RQWfHku~0CaTbfGZYk~ePN3h1AXKX") - .header("Content-Type", "application/json;charset=UTF-8") - .header("x-csrf-token", "755a7ccfb5e2770de6c6ddf5f9e1a0f6") - .requestBody(json.toJSONString()) - .method(Connection.Method.POST) - .ignoreHttpErrors(true) - .ignoreContentType(true) - .execute() - .body(); - System.out.println("response = " + response); - } -} From 45c2f7018ca4ea36f809f651e1bfed96664dd705 Mon Sep 17 00:00:00 2001 From: Zayrex <478576442@qq.com> Date: Tue, 23 Sep 2025 13:22:50 +0800 Subject: [PATCH 2/4] =?UTF-8?q?+=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E4=BF=9D=E5=AD=98=E5=8F=91=E7=8E=B0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=20+=E6=B7=BB=E5=8A=A0=E9=80=9A=E8=BF=87=E4=BD=9C=E5=93=81?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=8B=E8=BD=BD=E4=BD=9C=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/module-info.java | 1 + .../commands/pixiv/Discovery.java | 246 +++++++++++------- .../commands/pixiv/Download.java | 185 +++++++++++++ .../acgpicdownload/commands/pixiv/Pixiv.java | 5 + .../util/pixiv/PixivDownload.java | 2 + 5 files changed, 341 insertions(+), 98 deletions(-) create mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 58c094f..c2aa3c7 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -19,6 +19,7 @@ opens xyz.zcraft.acgpicdownload.util.source to com.alibaba.fastjson2; opens xyz.zcraft.acgpicdownload.util.pixiv to javafx.base, javafx.controls, javafx.graphics, javafx.fxml, com.alibaba.fastjson2; opens xyz.zcraft.acgpicdownload.util.source.argument to javafx.base, javafx.controls, javafx.graphics, javafx.fxml, com.alibaba.fastjson2; + exports xyz.zcraft.acgpicdownload; exports xyz.zcraft.acgpicdownload.exceptions; exports xyz.zcraft.acgpicdownload.gui; diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java index 69057a1..e888a8f 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java @@ -1,24 +1,28 @@ package xyz.zcraft.acgpicdownload.commands.pixiv; +import com.alibaba.fastjson2.JSONArray; import lombok.Getter; import xyz.zcraft.acgpicdownload.util.Logger; -import xyz.zcraft.acgpicdownload.util.dl.DownloadUtil; -import xyz.zcraft.acgpicdownload.util.pixiv.*; +import xyz.zcraft.acgpicdownload.util.pixiv.PixivArtwork; +import xyz.zcraft.acgpicdownload.util.pixiv.PixivFetchUtil; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Stream; public class Discovery { + private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Discovery.class); private final int threads = 5; + private int mode = 0; private int count = 1; + private boolean file = false; + private String fileName; + public void revoke(List argList, String cookie, String proxyHost, int proxyPort, Logger logger) { for (int i = 0; i < argList.size(); i++) { switch (argList.get(i).toLowerCase()) { @@ -32,7 +36,7 @@ public void revoke(List argList, String cookie, String proxyHost, int pr mode = argList.indexOf(argList.get(i)); } else { - logger.err("Please specify at the mode"); + logger.err("Please specify a mode"); return; } break; @@ -54,12 +58,30 @@ public void revoke(List argList, String cookie, String proxyHost, int pr } break; } + + case "-f", "-file": { + if (argList.size() > i + 1) { + i++; + fileName = argList.get(i); + file = true; + } else { + logger.err("Please specify a mode"); + return; + } + + break; + } } } logger.info("Ready to get discovery: mode=" + mode + ",count=" + count + ",proxy=" + proxyHost + ":" + proxyPort); + if (file && fileName != null) { + logger.info("Saving to file: " + fileName); + } + var f = new Fetch(mode, count, cookie, proxyHost, proxyPort); + f.run(); System.out.print("\033[?25l"); @@ -95,105 +117,133 @@ public void revoke(List argList, String cookie, String proxyHost, int pr System.out.println("\033[32mGot " + count + " artworks\033[0m"); System.out.println(); - System.out.println("\033[1mDownloading...\033[0m"); - - AtomicInteger completed = new AtomicInteger(0); - final ArrayList cur = new ArrayList<>(count); - for (int i1 = 0; i1 < threads; i1++) cur.add(null); - final ThreadPoolExecutor tpe = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads); - - final LinkedList err = new LinkedList<>(); - - art.forEach(e -> tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort))); - - boolean first = true; - while (true) { - if (!first) - System.out.print("\033[" + (1 + threads) + "F"); - first = false; - System.out.println( - "\033[32mCompleted:" + completed + "/" + art.size() - + (err.isEmpty() ? "" : " \033[31mError:" + err.size()) + "\033[0m" - ); - for (int k = 0; k < threads; k++) { - System.out.print("\033[K"); - if (cur.get(k) == null) { - System.out.println("IDLE"); - } else { - PixivDownload dl = cur.get(k); - System.out.print("["); - int v = (int) (Math.floor(((double) dl.getProgress() / dl.getTotal()) * 16.0)); - for (int j = 0; j < v; j++) { - System.out.print("="); - } - for (int j = 0; j < 16 - v; j++) { - System.out.print(" "); - } - System.out.print("] " + dl.getArtwork().getId()); - System.out.println(); - } - } - - if (completed.get() == art.size()) break; + if (file && fileName != null) { try { - Thread.sleep(500); - } catch (InterruptedException e) { -// throw new RuntimeException(e); + Files.writeString(Path.of(fileName), + JSONArray.toJSONString( + art.stream().flatMap( + (Function>) e -> Stream.of(e.getOrigJson()) + ).toList() + ) + ); + logger.info("File written to " + fileName); + } catch (Exception e) { + e.printStackTrace(); + logger.err("Error writing file: " + fileName); } + } else { + System.out.println("\033[1mDownloading...\033[0m"); + + Download.startDownload(cookie, proxyHost, proxyPort, art, threads, "downloads"); } + System.out.print("\n\nDONE fetching discovery."); System.out.print("\033[?25h"); - tpe.shutdown(); - } - - private record DownloadTask(int threads, PixivArtwork e, ArrayList cur, - LinkedList err, ThreadPoolExecutor tpe, - AtomicInteger completed, String cookie, String proxyHost, - int proxyPort) implements Runnable { - @Override - public void run() { - int I = -1; - PixivDownload dl = null; - synchronized (cur) { - for (int i = 0; i < threads; i++) { - if (cur.get(i) == null) { - dl = new PixivDownload(e); - cur.set(i, dl); - I = i; - break; - } - } - } - if (dl == null) { - tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort)); - return; - } + } - try { - new DownloadUtil(1).downloadPixiv( - dl, - Path.of("downloads").toFile(), - cookie, - new NamingRule("{$id}{_p$p}", 0, "{$id}"), - false, - proxyHost, - proxyPort, - ArtworkCondition.always() - ); +// private void startDownload(String cookie, String proxyHost, int proxyPort, List art) { +// AtomicInteger completed = new AtomicInteger(0); +// final ArrayList cur = new ArrayList<>(count); +// for (int i1 = 0; i1 < threads; i1++) cur.add(null); +// final ThreadPoolExecutor tpe = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads); +// +// final LinkedList err = new LinkedList<>(); +// +// art.forEach(e -> tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort))); +// +// boolean first = true; +// while (true) { +// if (!first) +// System.out.print("\033[" + (1 + threads) + "F"); +// first = false; +// System.out.println( +// "\033[32mCompleted:" + completed + "/" + art.size() +// + (err.isEmpty() ? "" : " \033[31mError:" + err.size()) + "\033[0m" +// ); +// for (int k = 0; k < threads; k++) { +// System.out.print("\033[K"); +// if (cur.get(k) == null) { +// System.out.println("IDLE"); +// } else { +// PixivDownload dl = cur.get(k); +// System.out.print("["); +// int v = (int) (Math.floor(((double) dl.getProgress() / dl.getTotal()) * 16.0)); +// for (int j = 0; j < v; j++) { +// System.out.print("="); +// } +// for (int j = 0; j < 16 - v; j++) { +// System.out.print(" "); +// } +// System.out.print("] " + +// dl.getArtwork().getId() + " \t" + +// dl.getProgress() + "/" + dl.getTotal() + +// (dl.getArtwork().getIllustType() == 2 ? " GIF" : "") +// ); +// System.out.println(); +// } +// } +// +// if (completed.get() == art.size()) break; +// +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { - synchronized (cur) { - cur.set(I, null); - } - - completed.incrementAndGet(); - } catch (IOException ex) { - dl.setException(ex); - err.add(dl); - } - } - } + /// / throw new RuntimeException(e); +// } +// } +// tpe.shutdown(); +// } +// +// private record DownloadTask(int threads, PixivArtwork e, ArrayList cur, +// LinkedList err, ThreadPoolExecutor tpe, +// AtomicInteger completed, String cookie, String proxyHost, +// int proxyPort) implements Runnable { +// @Override +// public void run() { +// int I = -1; +// PixivDownload dl = null; +// synchronized (cur) { +// for (int i = 0; i < threads; i++) { +// if (cur.get(i) == null) { +// dl = new PixivDownload(e); +// cur.set(i, dl); +// I = i; +// break; +// } +// } +// } +// +// if (dl == null) { +// tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort)); +// return; +// } +// +// try { +// new DownloadUtil(1).downloadPixiv( +// dl, +// Path.of("downloads").toFile(), +// cookie, +// new NamingRule("{$id}{_p$p}", 0, "{$id}"), +// false, +// proxyHost, +// proxyPort, +// ArtworkCondition.always() +// ); +// +// synchronized (cur) { +// cur.set(I, null); +// } +// +// completed.incrementAndGet(); +// } catch (IOException ex) { +// dl.setException(ex); +// err.add(dl); +// } +// } +// } private static class Fetch { private final String cookie; diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java new file mode 100644 index 0000000..da6e84c --- /dev/null +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java @@ -0,0 +1,185 @@ +package xyz.zcraft.acgpicdownload.commands.pixiv; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import xyz.zcraft.acgpicdownload.util.Logger; +import xyz.zcraft.acgpicdownload.util.dl.DownloadUtil; +import xyz.zcraft.acgpicdownload.util.pixiv.ArtworkCondition; +import xyz.zcraft.acgpicdownload.util.pixiv.NamingRule; +import xyz.zcraft.acgpicdownload.util.pixiv.PixivArtwork; +import xyz.zcraft.acgpicdownload.util.pixiv.PixivDownload; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +public class Download { + private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Download.class); + public int threads = 5; + + private String fileName; + private String target = "downloads"; + + private List art; + + public static void startDownload(String cookie, String proxyHost, int proxyPort, List art, int threads, String target) { + AtomicInteger completed = new AtomicInteger(0); + final ArrayList cur = new ArrayList<>(art.size()); + for (int i1 = 0; i1 < threads; i1++) cur.add(null); + final ThreadPoolExecutor tpe = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads); + + final LinkedList err = new LinkedList<>(); + + art.forEach(e -> tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort, target))); + + boolean first = true; + while (true) { + if (!first) + System.out.print("\033[" + (1 + threads) + "F"); + first = false; + System.out.println( + "\033[32mCompleted:" + completed + "/" + art.size() + + (err.isEmpty() ? "" : " \033[31mError:" + err.size()) + "\033[0m" + ); + for (int k = 0; k < threads; k++) { + System.out.print("\033[K"); + if (cur.get(k) == null) { + System.out.println("IDLE"); + } else { + PixivDownload dl = cur.get(k); + System.out.print("["); + int v = (int) (Math.floor(((double) dl.getProgress() / dl.getTotal()) * 16.0)); + for (int j = 0; j < v; j++) { + System.out.print("="); + } + for (int j = 0; j < 16 - v; j++) { + System.out.print(" "); + } + System.out.print("] " + + dl.getArtwork().getId() + " \t" + + dl.getProgress() + "/" + dl.getTotal() + + (dl.getArtwork().getIllustType() == 2 ? " GIF" : "") + ); + System.out.println(); + } + } + + if (completed.get() == art.size()) break; + + try { + Thread.sleep(500); + } catch (InterruptedException e) { +// throw new RuntimeException(e); + } + } + tpe.shutdown(); + } + + public void revoke(List argList, String cookie, + String proxyHost, int proxyPort, Logger logger) { + for (int i = 0; i < argList.size(); i++) { + switch (argList.get(i).toLowerCase()) { + case "-f", "-file": { + if (argList.size() > i + 1) { + i++; + this.fileName = argList.get(i); + } else { + logger.err("Please specify file name"); + return; + } + + break; + } + + case "-o", "-output": { + if (argList.size() > i + 1) { + i++; + this.target = argList.get(i); + } else { + logger.err("Please specify a output path"); + return; + } + + break; + } + } + } + + if (fileName == null) { + logger.err("Please specify file name"); + return; + } + + try { + final String s = Files.readString(Path.of(fileName)); + final List parse = JSONArray.parse(s).toList(JSONObject.class); + art = new LinkedList<>(); + parse.forEach(e -> { + final var t = e.to(PixivArtwork.class); + t.setOrigJson(e); + art.add(t); + }); + } catch (IOException e) { + logger.err("Cannot read file " + fileName); + return; + } + + startDownload(cookie, proxyHost, proxyPort, art, threads, target); + + logger.info("DONE Downloading"); + } + + private record DownloadTask(int threads, PixivArtwork e, ArrayList cur, + LinkedList err, ThreadPoolExecutor tpe, + AtomicInteger completed, String cookie, String proxyHost, + int proxyPort, String target) implements Runnable { + @Override + public void run() { + int I = -1; + PixivDownload dl = null; + synchronized (cur) { + for (int i = 0; i < threads; i++) { + if (cur.get(i) == null) { + dl = new PixivDownload(e); + cur.set(i, dl); + I = i; + break; + } + } + } + + if (dl == null) { + tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort, target)); + return; + } + + try { + new DownloadUtil(1).downloadPixiv( + dl, + Path.of(target).toFile(), + cookie, + new NamingRule("{$id}{_p$p}", 0, "{$id}"), + false, + proxyHost, + proxyPort, + ArtworkCondition.always() + ); + + synchronized (cur) { + cur.set(I, null); + } + + completed.incrementAndGet(); + } catch (IOException ex) { + dl.setException(ex); + err.add(dl); + } + } + } +} diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java index 68fb5b0..464399f 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java @@ -56,6 +56,11 @@ public void revoke(ArrayList argList, Logger logger) { new Discovery().revoke(argList.subList(i + 1, argList.size()), cookie, proxyHost, proxyPort, logger); return; } + + case "download", "dl": { + new Download().revoke(argList.subList(i + 1, argList.size()), cookie, proxyHost, proxyPort, logger); + return; + } } } } diff --git a/src/main/java/xyz/zcraft/acgpicdownload/util/pixiv/PixivDownload.java b/src/main/java/xyz/zcraft/acgpicdownload/util/pixiv/PixivDownload.java index 2b18d00..bed457d 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/util/pixiv/PixivDownload.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/util/pixiv/PixivDownload.java @@ -10,7 +10,9 @@ public class PixivDownload { private DownloadStatus status = DownloadStatus.CREATED; private Exception exception; private int progress = 0; + private int progressBytes = 0; private int total = 0; + private int totalBytes = 0; public PixivDownload(PixivArtwork artwork) { this.artwork = artwork; From 04ffb338c80f823e4aa48410986efb802d0950a3 Mon Sep 17 00:00:00 2001 From: Zayrex <478576442@qq.com> Date: Wed, 24 Sep 2025 13:37:47 +0800 Subject: [PATCH 3/4] =?UTF-8?q?-=E5=88=A0=E9=99=A4schedule=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=20+=E5=A2=9E=E5=8A=A0=E6=B5=81=E5=BC=8F=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E5=8A=9F=E8=83=BD=20+=E6=B7=BB=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E9=A2=9C=E8=89=B2=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../java/xyz/zcraft/acgpicdownload/Main.java | 11 +- .../zcraft/acgpicdownload/commands/Fetch.java | 2 +- .../acgpicdownload/commands/Schedule.java | 5 +- .../acgpicdownload/commands/pixiv/Author.java | 4 + .../commands/pixiv/Discovery.java | 129 ++---------------- .../commands/pixiv/Download.java | 50 ++++--- .../acgpicdownload/commands/pixiv/Pixiv.java | 114 ++++++++++------ .../commands/pixiv/Ranking.java | 4 + .../zcraft/acgpicdownload/util/Logger.java | 17 ++- 10 files changed, 145 insertions(+), 192 deletions(-) create mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Author.java create mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Ranking.java diff --git a/.gitignore b/.gitignore index b8c349c..ba8c7d4 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ build/ /accounts.json /log/ /.idea/ +/src/test/ diff --git a/src/main/java/xyz/zcraft/acgpicdownload/Main.java b/src/main/java/xyz/zcraft/acgpicdownload/Main.java index c2052e6..0327503 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/Main.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/Main.java @@ -2,7 +2,6 @@ import lombok.Getter; import xyz.zcraft.acgpicdownload.commands.Fetch; -import xyz.zcraft.acgpicdownload.commands.Schedule; import xyz.zcraft.acgpicdownload.commands.pixiv.Pixiv; import xyz.zcraft.acgpicdownload.gui.GUI; import xyz.zcraft.acgpicdownload.util.Logger; @@ -74,16 +73,10 @@ public static void main(String[] args) { argList.remove(0); Fetch f = new Fetch(); f.enableConsoleProgressBar = true; - f.main(argList, new Logger("Fetch", System.out)); - } else if (argList.get(0).equalsIgnoreCase("schedule")) { - err.println("Command schedule will be deprecated soon. See project on GitHub for more information."); - argList.remove(0); - Schedule s = new Schedule(); - s.main(argList); + f.invoke(argList, new Logger("Fetch", System.out)); } else if (argList.get(0).equalsIgnoreCase("pixiv")) { - argList.remove(0); Pixiv p = new Pixiv(); - p.revoke(argList, new Logger("Pixiv", System.out)); + p.invoke(argList, new Logger("Pixiv", System.out)); } } } diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/Fetch.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/Fetch.java index 13db7db..f306e09 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/Fetch.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/Fetch.java @@ -191,7 +191,7 @@ private Source parseSource() { return s; } - public void main(ArrayList args, Logger logger) { + public void invoke(ArrayList args, Logger logger) { this.logger = logger; if (!parseArguments(args)) { diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/Schedule.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/Schedule.java index 2a3414e..6c0cb61 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/Schedule.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/Schedule.java @@ -10,11 +10,12 @@ import static xyz.zcraft.acgpicdownload.util.source.SourceManager.isEmpty; +@Deprecated public class Schedule { private final ArrayList events = new ArrayList<>(); Logger l; - public void main(ArrayList args) { + public void invoke(ArrayList args) { if (!parseEvent(args)) { return; } @@ -91,7 +92,7 @@ public void main(ArrayList args) { public void run() { Fetch f = new Fetch(); Logger t = new Logger(String.valueOf(this.hashCode()), logger, System.out); - f.main(e.getCommands(), t); + f.invoke(e.getCommands(), t); t.info("[Event End]"); } }); diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Author.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Author.java new file mode 100644 index 0000000..21da701 --- /dev/null +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Author.java @@ -0,0 +1,4 @@ +package xyz.zcraft.acgpicdownload.commands.pixiv; + +public class Author { +} diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java index e888a8f..33f2f61 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java @@ -9,13 +9,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.LinkedList; import java.util.List; import java.util.function.Function; import java.util.stream.Stream; public class Discovery { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Discovery.class); - private final int threads = 5; private int mode = 0; private int count = 1; @@ -23,21 +23,22 @@ public class Discovery { private boolean file = false; private String fileName; - public void revoke(List argList, String cookie, String proxyHost, int proxyPort, Logger logger) { + public List invoke(List argList, String cookie, String proxyHost, int proxyPort, Logger logger) { for (int i = 0; i < argList.size(); i++) { + if (!argList.get(i).startsWith("-")) break; switch (argList.get(i).toLowerCase()) { case "-m", "-mode": { if (argList.size() > i + 1) { i++; if (!List.of(PixivFetchUtil.DISCOVERY_MODES).contains(argList.get(i))) { logger.err("Unknown mode " + argList.get(i)); - return; + return null; } mode = argList.indexOf(argList.get(i)); } else { logger.err("Please specify a mode"); - return; + return null; } break; } @@ -48,13 +49,13 @@ public void revoke(List argList, String cookie, String proxyHost, int pr final int c = Integer.parseInt(argList.get(i)); if (c < 0 || c > 50) { logger.err("Count must be between 1 and 50."); - return; + return null; } count = c; } else { logger.err("Please specify a number."); - return; + return null; } break; } @@ -66,7 +67,7 @@ public void revoke(List argList, String cookie, String proxyHost, int pr file = true; } else { logger.err("Please specify a mode"); - return; + return null; } break; @@ -95,6 +96,7 @@ public void revoke(List argList, String cookie, String proxyHost, int pr System.out.print("\033[" + (i) + "G="); System.out.print("\033[" + (i - d) + "G "); try { + //noinspection BusyWait Thread.sleep(50); } catch (InterruptedException ignored) { } @@ -107,12 +109,12 @@ public void revoke(List argList, String cookie, String proxyHost, int pr if (f.getException() != null) { logger.err("Error getting discovery: " + f.getException().getMessage()); - return; + return null; } if (art == null || art.isEmpty()) { logger.err("No artworks found!"); - return; + return new LinkedList<>(); } System.out.println("\033[32mGot " + count + " artworks\033[0m"); @@ -129,122 +131,17 @@ public void revoke(List argList, String cookie, String proxyHost, int pr ); logger.info("File written to " + fileName); } catch (Exception e) { - e.printStackTrace(); + log.error("Error writing file", e); logger.err("Error writing file: " + fileName); } - } else { - System.out.println("\033[1mDownloading...\033[0m"); - - Download.startDownload(cookie, proxyHost, proxyPort, art, threads, "downloads"); } System.out.print("\n\nDONE fetching discovery."); System.out.print("\033[?25h"); + return art; } -// private void startDownload(String cookie, String proxyHost, int proxyPort, List art) { -// AtomicInteger completed = new AtomicInteger(0); -// final ArrayList cur = new ArrayList<>(count); -// for (int i1 = 0; i1 < threads; i1++) cur.add(null); -// final ThreadPoolExecutor tpe = (ThreadPoolExecutor) Executors.newFixedThreadPool(threads); -// -// final LinkedList err = new LinkedList<>(); -// -// art.forEach(e -> tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort))); -// -// boolean first = true; -// while (true) { -// if (!first) -// System.out.print("\033[" + (1 + threads) + "F"); -// first = false; -// System.out.println( -// "\033[32mCompleted:" + completed + "/" + art.size() -// + (err.isEmpty() ? "" : " \033[31mError:" + err.size()) + "\033[0m" -// ); -// for (int k = 0; k < threads; k++) { -// System.out.print("\033[K"); -// if (cur.get(k) == null) { -// System.out.println("IDLE"); -// } else { -// PixivDownload dl = cur.get(k); -// System.out.print("["); -// int v = (int) (Math.floor(((double) dl.getProgress() / dl.getTotal()) * 16.0)); -// for (int j = 0; j < v; j++) { -// System.out.print("="); -// } -// for (int j = 0; j < 16 - v; j++) { -// System.out.print(" "); -// } -// System.out.print("] " + -// dl.getArtwork().getId() + " \t" + -// dl.getProgress() + "/" + dl.getTotal() + -// (dl.getArtwork().getIllustType() == 2 ? " GIF" : "") -// ); -// System.out.println(); -// } -// } -// -// if (completed.get() == art.size()) break; -// -// try { -// Thread.sleep(500); -// } catch (InterruptedException e) { - - /// / throw new RuntimeException(e); -// } -// } -// tpe.shutdown(); -// } -// -// private record DownloadTask(int threads, PixivArtwork e, ArrayList cur, -// LinkedList err, ThreadPoolExecutor tpe, -// AtomicInteger completed, String cookie, String proxyHost, -// int proxyPort) implements Runnable { -// @Override -// public void run() { -// int I = -1; -// PixivDownload dl = null; -// synchronized (cur) { -// for (int i = 0; i < threads; i++) { -// if (cur.get(i) == null) { -// dl = new PixivDownload(e); -// cur.set(i, dl); -// I = i; -// break; -// } -// } -// } -// -// if (dl == null) { -// tpe.submit(new DownloadTask(threads, e, cur, err, tpe, completed, cookie, proxyHost, proxyPort)); -// return; -// } -// -// try { -// new DownloadUtil(1).downloadPixiv( -// dl, -// Path.of("downloads").toFile(), -// cookie, -// new NamingRule("{$id}{_p$p}", 0, "{$id}"), -// false, -// proxyHost, -// proxyPort, -// ArtworkCondition.always() -// ); -// -// synchronized (cur) { -// cur.set(I, null); -// } -// -// completed.incrementAndGet(); -// } catch (IOException ex) { -// dl.setException(ex); -// err.add(dl); -// } -// } -// } - private static class Fetch { private final String cookie; private final String proxyHost; diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java index da6e84c..a3b5d92 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Download.java @@ -23,7 +23,7 @@ public class Download { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Download.class); public int threads = 5; - private String fileName; + private String fileName = null; private String target = "downloads"; private List art; @@ -73,17 +73,19 @@ public static void startDownload(String cookie, String proxyHost, int proxyPort, if (completed.get() == art.size()) break; try { + //noinspection BusyWait Thread.sleep(500); } catch (InterruptedException e) { -// throw new RuntimeException(e); + throw new RuntimeException(e); } } tpe.shutdown(); } - public void revoke(List argList, String cookie, - String proxyHost, int proxyPort, Logger logger) { + public void invoke(List argList, String cookie, + String proxyHost, int proxyPort, Logger logger, List previous) { for (int i = 0; i < argList.size(); i++) { + if (!argList.get(i).startsWith("-")) break; switch (argList.get(i).toLowerCase()) { case "-f", "-file": { if (argList.size() > i + 1) { @@ -111,22 +113,36 @@ public void revoke(List argList, String cookie, } } - if (fileName == null) { - logger.err("Please specify file name"); + if (fileName == null && previous == null) { + logger.err("No artwork source to download!"); return; } - try { - final String s = Files.readString(Path.of(fileName)); - final List parse = JSONArray.parse(s).toList(JSONObject.class); - art = new LinkedList<>(); - parse.forEach(e -> { - final var t = e.to(PixivArtwork.class); - t.setOrigJson(e); - art.add(t); - }); - } catch (IOException e) { - logger.err("Cannot read file " + fileName); + if (fileName != null && previous != null) { + logger.warn("Both file and previous result are provided, using file"); + } + + if (fileName != null) { + try { + final String s = Files.readString(Path.of(fileName)); + final List parse = JSONArray.parse(s).toList(JSONObject.class); + art = new LinkedList<>(); + parse.forEach(e -> { + final var t = e.to(PixivArtwork.class); + t.setOrigJson(e); + art.add(t); + }); + } catch (IOException e) { + logger.err("Cannot read file " + fileName + ": " + e.getMessage()); + log.error("Cannot read file " + fileName, e); + return; + } + } else { + art = previous; + } + + if (art.isEmpty()) { + logger.warn("No artwork to download!"); return; } diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java index 464399f..e38a296 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Pixiv.java @@ -1,65 +1,99 @@ package xyz.zcraft.acgpicdownload.commands.pixiv; import xyz.zcraft.acgpicdownload.util.Logger; +import xyz.zcraft.acgpicdownload.util.pixiv.PixivArtwork; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; public class Pixiv { + private final List subCommands = List.of( + "discovery", "download", "ranking", "user", "disc", "dl", "rk" + ); + private final List fragments = new LinkedList<>(List.of(1)); private String cookie = null; private String proxyHost = null; private int proxyPort = -1; + private List previous; - public void revoke(ArrayList argList, Logger logger) { - for (int i = 0; i < argList.size(); i++) { - switch (argList.get(i).toLowerCase()) { - case "-c", "-cookie": { - if (argList.size() > i + 1) { - i++; - try { - cookie = Files.readString(Path.of(argList.get(i))); - } catch (IOException e) { - logger.err("Cannot read cookie file " + argList.get(i)); - return; + public void invoke(ArrayList argList, Logger logger) { + for (int i = 1; i < argList.size(); i++) { + if (subCommands.contains(argList.get(i).toLowerCase())) { + fragments.add(i); + } + } + + fragments.add(argList.size()); + + for (int i = 0; i < fragments.size() - 1; i++) { + if (i == 0) { + for (int j = 1; j < fragments.get(i + 1); j++) { + switch (argList.get(j).toLowerCase()) { + case "-c", "-cookie": { + if (argList.size() > j + 1) { + j++; + try { + var p = argList.get(j); + if (p.startsWith("\"") && p.endsWith("\"")) p = p.substring(1, p.length() - 1); + cookie = Files.readString(Path.of(p)); + } catch (IOException e) { + logger.err("Cannot read cookie file " + argList.get(j)); + return; + } + } else { + logger.err("Please specify a cookie file"); + return; + } + break; } - } else { - logger.err("Please specify a cookie file"); - return; - } - break; - } - case "-p", "-proxy": { - if (argList.size() > i + 1) { - i++; - try { - final String[] split = argList.get(i).split(":"); - proxyHost = split[0]; - proxyPort = Integer.parseInt(split[1]); + case "-p", "-proxy": { + if (argList.size() > j + 1) { + j++; + try { + final String[] split = argList.get(j).split(":"); + proxyHost = split[0]; + proxyPort = Integer.parseInt(split[1]); - System.getProperties().put("proxySet", "true"); - System.getProperties().put("proxyHost", proxyHost); - System.getProperties().put("proxyPort", String.valueOf(proxyPort)); - } catch (Exception e) { - logger.err("Cannot parse proxy " + argList.get(i)); + System.getProperties().put("proxySet", "true"); + System.getProperties().put("proxyHost", proxyHost); + System.getProperties().put("proxyPort", String.valueOf(proxyPort)); + } catch (Exception e) { + logger.err("Cannot parse proxy " + argList.get(j)); + } + } else { + logger.err("Please specify a proxy"); + return; + } + break; } - } else { - logger.err("Please specify a proxy"); - return; } - break; } + } else { + switch (argList.get(fragments.get(i)).toLowerCase()) { + case "discovery", "disc": { + previous = new Discovery().invoke(argList.subList(fragments.get(i), fragments.get(i + 1)), cookie, proxyHost, proxyPort, logger); + break; + } - case "discovery", "disc": { - new Discovery().revoke(argList.subList(i + 1, argList.size()), cookie, proxyHost, proxyPort, logger); - return; - } + case "download", "dl": { + new Download().invoke(argList.subList(fragments.get(i), fragments.get(i + 1)), cookie, proxyHost, proxyPort, logger, previous); + break; + } - case "download", "dl": { - new Download().revoke(argList.subList(i + 1, argList.size()), cookie, proxyHost, proxyPort, logger); - return; + case "ranking", "rk": { + + break; + } + + case "user", "u": { + + break; + } } } } diff --git a/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Ranking.java b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Ranking.java new file mode 100644 index 0000000..eba0607 --- /dev/null +++ b/src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Ranking.java @@ -0,0 +1,4 @@ +package xyz.zcraft.acgpicdownload.commands.pixiv; + +public class Ranking { +} diff --git a/src/main/java/xyz/zcraft/acgpicdownload/util/Logger.java b/src/main/java/xyz/zcraft/acgpicdownload/util/Logger.java index ad59061..4001cb5 100644 --- a/src/main/java/xyz/zcraft/acgpicdownload/util/Logger.java +++ b/src/main/java/xyz/zcraft/acgpicdownload/util/Logger.java @@ -1,11 +1,14 @@ package xyz.zcraft.acgpicdownload.util; +import lombok.Getter; + import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; public class Logger { private final String name; + @Getter private final Logger parentLogger; private final PrintStream[] out; @@ -29,17 +32,13 @@ public String getName() { return name; } - public Logger getParentLogger() { - return parentLogger; - } - public String getOutputName() { - return "[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "][" + getName() + "] "; + return "[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "][" + getName() + "]"; } public void info(String message) { for (PrintStream t : out) { - t.println(getOutputName() + message); + t.println(getOutputName() + "[I] " + message); t.flush(); } } @@ -54,7 +53,11 @@ public void printr(String str) { } public void err(String message) { - System.err.println(getOutputName() + message); + System.err.println(getOutputName() + "\033[0;31m[E]\033[0m " + message); + } + + public void warn(String message) { + System.err.println(getOutputName() + "\033[0;33m[E]\033[0m " + message); } public void printf(String format, String... arg) { From 662467e2c524af205fbab22ffab1dc6dfb2a12db Mon Sep 17 00:00:00 2001 From: Zayrex <478576442@qq.com> Date: Thu, 25 Sep 2025 23:17:08 +0800 Subject: [PATCH 4/4] =?UTF-8?q?*=E6=94=B9=E5=8F=98=E4=BA=86=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=84=E7=90=86=E6=96=B9=E5=BC=8F=20+=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0save=E5=AD=90=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .idea/dbnavigator.xml | 414 ++++++++++++++++++ .idea/misc.xml | 1 + .../acgpicdownload/commands/pixiv/Author.java | 4 - .../commands/pixiv/Discovery.java | 188 -------- .../commands/pixiv/Download.java | 25 +- .../commands/pixiv/Fetcher.java | 178 ++++++++ .../acgpicdownload/commands/pixiv/Pixiv.java | 13 +- .../commands/pixiv/Ranking.java | 4 - .../acgpicdownload/commands/pixiv/Saver.java | 68 +++ 10 files changed, 695 insertions(+), 201 deletions(-) create mode 100644 .idea/dbnavigator.xml delete mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Author.java delete mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Discovery.java create mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Fetcher.java delete mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Ranking.java create mode 100644 src/main/java/xyz/zcraft/acgpicdownload/commands/pixiv/Saver.java diff --git a/.gitignore b/.gitignore index ba8c7d4..021dd4f 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ build/ /log/ /.idea/ /src/test/ +/debug.cookie diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 0000000..70f212e --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 877a01e..2a24573 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -16,6 +16,7 @@ +