From 23acdb649209d251dba1bb4064deb786cd3802c7 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 2 Jan 2023 01:30:52 +0800 Subject: [PATCH] Support non-H@H archive download --- .../com/hippo/ehviewer/client/EhClient.java | 2 +- .../com/hippo/ehviewer/client/EhEngine.java | 44 ++++++++--- .../ehviewer/client/parser/ArchiveParser.java | 78 ++++++++++++++++--- .../ehviewer/ui/scene/GalleryDetailScene.java | 35 +++++++-- app/src/main/res/values-zh-rCN/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + 6 files changed, 134 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index f77e6564b..e50be9369 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -167,7 +167,7 @@ public class EhClient { case METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]); case METHOD_DOWNLOAD_ARCHIVE -> - EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]); + EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3], (Boolean) params[4]); case METHOD_VOTE_TAG -> EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]); case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient); diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index e8f961e2d..5760de2be 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -33,7 +33,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.client.data.PreviewSet; import com.hippo.ehviewer.client.exception.CancelledException; import com.hippo.ehviewer.client.exception.EhException; -import com.hippo.ehviewer.client.exception.NoHAtHClientException; import com.hippo.ehviewer.client.exception.ParseException; import com.hippo.ehviewer.client.parser.ArchiveParser; import com.hippo.ehviewer.client.parser.EventPaneParser; @@ -65,8 +64,6 @@ import org.jsoup.select.Elements; import java.io.File; import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import okhttp3.Call; import okhttp3.FormBody; @@ -88,8 +85,6 @@ public class EhEngine { private static final String KOKOMADE_URL = "https://exhentai.org/img/kokomade.jpg"; private static final MediaType MEDIA_TYPE_JPEG = MediaType.parse("image/jpeg"); - private static final Pattern PATTERN_NEED_HATH_CLIENT = Pattern.compile("(You must have a H@H client assigned to your account to use this feature\\.)"); - public static EhFilter sEhFilter; public static void initialize() { @@ -714,8 +709,8 @@ public class EhEngine { return result; } - public static Void downloadArchive(@Nullable EhClient.Task task, OkHttpClient okHttpClient, - long gid, String token, String or, String res) throws Throwable { + public static String downloadArchive(@Nullable EhClient.Task task, OkHttpClient okHttpClient, + long gid, String token, String or, String res, boolean isHAtH) throws Throwable { if (or == null || or.length() == 0) { throw new EhException("Invalid form param or: " + or); } @@ -723,7 +718,16 @@ public class EhEngine { throw new EhException("Invalid res: " + res); } FormBody.Builder builder = new FormBody.Builder(); - builder.add("hathdl_xres", res); + if (isHAtH) { + builder.add("hathdl_xres", res); + } else { + builder.add("dltype", res); + if (res.equals("org")) { + builder.add("dlcheck", "Download Original Archive"); + } else { + builder.add("dlcheck", "Download Resample Archive"); + } + } String url = EhUrl.getDownloadArchive(gid, token, or); String referer = EhUrl.getGalleryDetailUrl(gid, token); String origin = EhUrl.getOrigin(); @@ -752,11 +756,27 @@ public class EhEngine { throw e; } - Matcher m = PATTERN_NEED_HATH_CLIENT.matcher(body); - if (m.find()) { - throw new NoHAtHClientException("No H@H client"); + var result = ArchiveParser.parseArchiveUrl(body); + if (!isHAtH) { + if (result == null) { + Thread.sleep(1000); + try (var response = call.clone().execute()) { + code = response.code(); + headers = response.headers(); + body = response.body().string(); + throwException(call, code, headers, body, null); + } catch (Throwable e) { + ExceptionUtils.throwIfFatal(e); + throwException(call, code, headers, body, e); + throw e; + } + result = ArchiveParser.parseArchiveUrl(body); + if (result == null) { + throw new EhException("Archive unavailable"); + } + } + return result; } - return null; } diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/ArchiveParser.java b/app/src/main/java/com/hippo/ehviewer/client/parser/ArchiveParser.java index 38e0c600e..9af7a1618 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/parser/ArchiveParser.java +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/ArchiveParser.java @@ -18,51 +18,101 @@ package com.hippo.ehviewer.client.parser; import androidx.annotation.NonNull; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.exception.NoHAtHClientException; + +import org.jsoup.Jsoup; + import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.IntFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ArchiveParser { - private static final Pattern PATTERN_FORM = Pattern.compile("
"); - private static final Pattern PATTERN_ARCHIVE = Pattern.compile("

([^<]+)

\\s*

([\\w. ]+)

\\s*

([\\w. ]+)

"); + private static final Pattern PATTERN_ARCHIVE_URL = Pattern.compile("(.*).*Click Here To Start Downloading"); + private static final Pattern PATTERN_HATH_FORM = Pattern.compile(""); + private static final Pattern PATTERN_HATH_ARCHIVE = Pattern.compile("

([^<]+)

\\s*

([\\w. ]+)

\\s*

([\\w. ]+)

"); + private static final Pattern PATTERN_NEED_HATH_CLIENT = Pattern.compile("You must have a H@H client assigned to your account to use this feature\\."); public static Result parse(String body) { - Matcher m = PATTERN_FORM.matcher(body); + Matcher m = PATTERN_HATH_FORM.matcher(body); if (!m.find()) { return new Result(null, null); } String paramOr = m.group(1); + var archiveList = new ArrayList(); - m = PATTERN_ARCHIVE.matcher(body); + var d = Jsoup.parse(body); + var es = d.select("#db>div>div"); + for (var e : es) { + try { + var res = e.selectFirst("form>input").attr("value"); + var name = e.selectFirst("form>div>input").attr("value"); + var size = e.selectFirst("p>strong").text(); + var cost = e.selectFirst("div>strong").text(); + var item = new Archive(res, name, size, cost, false); + archiveList.add(item); + } catch (NullPointerException ex) { + ex.printStackTrace(); + } + } + + m = PATTERN_HATH_ARCHIVE.matcher(body); while (m.find()) { String res = ParserUtils.trim(m.group(1)); String name = ParserUtils.trim(m.group(2)); String size = ParserUtils.trim(m.group(3)); String cost = ParserUtils.trim(m.group(4)); - var item = new Archive(res, name, size, cost); + var item = new Archive(res, name, size, cost, true); archiveList.add(item); } + return new Result(paramOr, archiveList); } + public static String parseArchiveUrl(String body) throws NoHAtHClientException { + Matcher m = PATTERN_NEED_HATH_CLIENT.matcher(body); + if (m.find()) { + throw new NoHAtHClientException("No H@H client"); + } + + var d = Jsoup.parse(body); + var a = d.selectFirst("#continue>a[href]"); + if (a != null) { + return a.attr("href") + "?start=1"; + } + + // TODO: Check more errors + return null; + } + public static final class Archive { private final String res; private final String name; private final String size; private final String cost; + private final boolean isHAtH; - public Archive(String res, String name, String size, String cost) { + public Archive(String res, String name, String size, String cost, boolean isHAtH) { this.res = res; this.name = name; this.size = size; this.cost = cost; + this.isHAtH = isHAtH; } - public String format() { - return String.format("%s [%s, %s]", name, size, cost); + public String format(IntFunction getString) { + if (isHAtH) { + var costStr = cost.equals("Free") ? getString.apply(R.string.archive_free) : cost; + return String.format("[H@H] %s [%s] [%s]", name, size, costStr); + } else { + var nameStr = getString.apply(res.equals("org") ? R.string.archive_original : R.string.archive_resample); + var costStr = cost.equals("Free!") ? getString.apply(R.string.archive_free) : cost; + return String.format("%s [%s] [%s]", nameStr, size, costStr); + } } public String res() { @@ -81,6 +131,10 @@ public class ArchiveParser { return cost; } + public boolean isHAtH() { + return isHAtH; + } + @Override public boolean equals(Object obj) { if (obj == this) return true; @@ -89,12 +143,13 @@ public class ArchiveParser { return Objects.equals(this.res, that.res) && Objects.equals(this.name, that.name) && Objects.equals(this.size, that.size) && - Objects.equals(this.cost, that.cost); + Objects.equals(this.cost, that.cost) && + this.isHAtH == that.isHAtH; } @Override public int hashCode() { - return Objects.hash(res, name, size, cost); + return Objects.hash(res, name, size, cost, isHAtH); } @NonNull @@ -104,7 +159,8 @@ public class ArchiveParser { "res=" + res + ", " + "name=" + name + ", " + "size=" + size + ", " + - "cost=" + cost + ']'; + "cost=" + cost + ", " + + "isHAtH=" + isHAtH + ']'; } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 1ccdbfb60..4d6adc67b 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -1177,7 +1177,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O } ArchiveListDialogHelper helper = new ArchiveListDialogHelper(); Dialog dialog = new BaseDialogBuilder(context) - .setTitle(R.string.dialog_archive_title) + .setTitle(R.string.settings_download) .setView(R.layout.dialog_archive_list) .setOnDismissListener(helper) .show(); @@ -1713,14 +1713,34 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O } } - private static class DownloadArchiveListener extends EhCallback { + private static class DownloadArchiveListener extends EhCallback { - public DownloadArchiveListener(Context context, int stageId, String sceneTag) { + private final GalleryInfo mGalleryInfo; + + public DownloadArchiveListener(Context context, int stageId, String sceneTag, GalleryInfo galleryInfo) { super(context, stageId, sceneTag); + mGalleryInfo = galleryInfo; } @Override - public void onSuccess(Void result) { + public void onSuccess(String result) { + if (result != null) { + // TODO: Don't use buggy system download service + DownloadManager.Request r = new DownloadManager.Request(Uri.parse(result)); + var name = mGalleryInfo.gid + "-" + EhUtils.getSuitableTitle(mGalleryInfo) + ".zip"; + r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, + FileUtils.sanitizeFilename(name)); + r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + DownloadManager dm = (DownloadManager) getApplication().getSystemService(Context.DOWNLOAD_SERVICE); + if (dm != null) { + try { + dm.enqueue(r); + } catch (Throwable e) { + e.printStackTrace(); + ExceptionUtils.throwIfFatal(e); + } + } + } showTip(R.string.download_archive_started, LENGTH_SHORT); } @@ -1790,7 +1810,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O mListView.setVisibility(View.GONE); mErrorText.setText(R.string.no_archives); } else { - var nameArray = data.stream().map(ArchiveParser.Archive::format).toArray(String[]::new); + var nameArray = data.stream().map(archive -> archive.format(getResources()::getString)).toArray(String[]::new); mProgressView.setVisibility(View.GONE); mErrorText.setVisibility(View.GONE); mListView.setVisibility(View.VISIBLE); @@ -1804,10 +1824,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O MainActivity activity = getMainActivity(); if (null != context && null != activity && null != mArchiveList && position < mArchiveList.size()) { String res = mArchiveList.get(position).res(); + boolean isHAtH = mArchiveList.get(position).isHAtH(); EhRequest request = new EhRequest(); request.setMethod(EhClient.METHOD_DOWNLOAD_ARCHIVE); - request.setArgs(mGalleryDetail.gid, mGalleryDetail.token, mArchiveFormParamOr, res); - request.setCallback(new DownloadArchiveListener(context, activity.getStageId(), getTag())); + request.setArgs(mGalleryDetail.gid, mGalleryDetail.token, mArchiveFormParamOr, res, isHAtH); + request.setCallback(new DownloadArchiveListener(context, activity.getStageId(), getTag(), mGalleryDetail)); EhApplication.getEhClient().execute(request); } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fe8c83a36..4c5b55bc7 100755 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -531,6 +531,9 @@ 开始下载压缩包 无法下载压缩包 压缩包 + 免费 + 原始档案 + 重采样档案 下载压缩包需要 H@H 客户端 隐私 不允许屏幕抓取 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3113ed94..cda6be33b 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -598,6 +598,9 @@ Archive download started Failed to download archive Archive + Free + Original archive + Resample archive Need H@H client for archive download Privacy Prevent screenshots