diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmHtmlClient.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmHtmlClient.java index b515d14..81ed36e 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmHtmlClient.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmHtmlClient.java @@ -340,11 +340,22 @@ public void toggleAlbumFavorite(String albumId, String folderId) { HttpUrl url = newHttpUrlBuilder() .addPathSegment("ajax") .addPathSegment("favorite_album") - .addQueryParameter("album_id", albumId) - .addQueryParameter("fid", folderId == null ? "0" : folderId) .build(); - JmHtmlResponse jmHtmlResponse = executeGetRequest(url); + HttpUrl urlDel = newHttpUrlBuilder() + .addPathSegment("ajax") + .addPathSegment("delete_favorite_album") + .build(); + + FormBody.Builder formBuilder = new FormBody.Builder() + .add("album_id", albumId) + .add("fid", folderId == null ? "0" : folderId); + + FormBody.Builder formBuilderDel = new FormBody.Builder() + .add("album_id", albumId); + + JmHtmlResponse jmHtmlResponse = executePostRequest(url, formBuilder.build()); + try { /* * 网页端收藏接口返回 JSON,status=1 成功,=0 表示已收藏过。 @@ -356,6 +367,15 @@ public void toggleAlbumFavorite(String albumId, String folderId) { status = jsonObject.get("status").getAsInt(); } + if (status == 0) { + // 删除收藏 + JmHtmlResponse jmHtmlResponseDel = executePostRequest(urlDel, formBuilderDel.build()); + jsonObject = JsonParser.parseString(jmHtmlResponseDel.getHtml()).getAsJsonObject(); + if (jsonObject.has("status") && !jsonObject.get("status").isJsonNull()) { + status = jsonObject.get("status").getAsInt(); + } + } + if (status != 1) { String message = ""; if (jsonObject.has("msg") && !jsonObject.get("msg").isJsonNull()) { @@ -453,9 +473,48 @@ public JmAlbumDownloadInfo getAlbumDownloadInfo(String albumId) { throw new UnsupportedOperationException("Getting album download info via HTML client is not currently supported. Use JmApiClient instead."); } + /** + * 未完成 + * 移动作品到文件夹只对已经收藏作品的有效 + * 删除文件夹只能删除空文件夹 + * @param type 操作类型 (add/edit/move/del) + * @param folderId 文件夹ID + * @param folderName 文件夹名称(添加/重命名时需要) + * @param albumId 本子ID(移动时需要) + * @return + */ @Override public JmFavoriteFolderResult manageFavoriteFolder(FavoriteFolderType type, String folderId, String folderName, String albumId) { - throw new UnsupportedOperationException("Managing favorite folders via HTML client is not currently supported. Use JmApiClient instead."); + HttpUrl.Builder urlBuilder = newHttpUrlBuilder() + .addPathSegment("user") + .addPathSegment(getLoggedInUserName()) + .addPathSegment("favorite") + .addPathSegment("albums"); + + FormBody.Builder formBuilder = new FormBody.Builder(); + + if (type == FavoriteFolderType.ADD) { + formBuilder.add("addfolder-name", folderName); + } + + if (type == FavoriteFolderType.DELETE) { + formBuilder.add("deletefolder-name", folderId); + } + + if (type == FavoriteFolderType.EDIT) { + urlBuilder.addQueryParameter("folder", folderId); + formBuilder.add("editfolder-fid", folderId); + formBuilder.add("editfolder-name", folderName); + } + + if (type == FavoriteFolderType.MOVE) { + formBuilder.add("movefolder-fid", folderId); + formBuilder.add("movefolder-aid", albumId); + } + + JmHtmlResponse jmHtmlResponse = executePostRequest(urlBuilder.build(), formBuilder.build()); + + return new JmFavoriteFolderResult("ok", type.getDescription()+"成功"); } // == HTML 客户端暂不实现(使用 JmApiClient) == @@ -468,7 +527,11 @@ public Map register(String username, String password, String passwordConfirm, St @Override public void logout() { - throw new UnsupportedOperationException("Logout via HTML client is not currently supported. Use JmApiClient instead."); + HttpUrl url = newHttpUrlBuilder() + .addPathSegment("logout") + .build(); + + JmHtmlResponse jmHtmlResponse = executeGetRequest(url); } @Override diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/parser/HtmlParser.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/parser/HtmlParser.java index 8b5e4ab..0ac3654 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/parser/HtmlParser.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/parser/HtmlParser.java @@ -79,10 +79,9 @@ public static JmAlbum parseAlbum(String html) { parseRelatedAlbums(doc), // 章节列表 parsePhotoMetas(doc, id), - // HTML 解析不支持以下字段 "0", // seriesId - false, // isFavorite - false, // liked + parseFavorite(doc, id), // isFavorite + parseLiked(doc, id), // liked false, // isAids Collections.emptyList(), // images "", // price @@ -105,6 +104,11 @@ private static String decodeBase64Html(String html) { } private static String parseAlbumId(Document doc) { + Element albumIdElement = doc.getElementById("album_id"); + if (albumIdElement != null && StringUtils.isNotBlank(albumIdElement.attr("value"))) { + return albumIdElement.attr("value"); + } + // 优先从PC布局的 h2 标签提取 Element h2Element = doc.selectFirst("div.col-lg-7 h2:contains(禁漫车:), div.col-lg-7 h2:contains(禁漫車:)"); if (h2Element != null && h2Element.parent() != null) { @@ -123,6 +127,30 @@ private static String parseAlbumId(Document doc) { throw new ParseResponseException("Could not parse album id."); } + private static boolean parseFavorite(Document doc, String albumId) { + Element favoriteElement = doc.getElementById("favorite_album_" + albumId); + if (favoriteElement == null) { + return false; + } + + Element favoriteIcon = favoriteElement.selectFirst("i"); + return favoriteIcon != null && !hasInlineStyle(favoriteIcon, "color", "#000000"); + } + + private static boolean parseLiked(Document doc, String albumId) { + return doc.select("[id=love_likes_" + albumId + "] i") + .stream() + .anyMatch(element -> hasInlineStyle(element, "color", "red")); + } + + private static boolean hasInlineStyle(Element element, String property, String value) { + return Arrays.stream(element.attr("style").split(";")) + .map(declaration -> declaration.split(":", 2)) + .anyMatch(parts -> parts.length == 2 + && property.equalsIgnoreCase(parts[0].trim()) + && value.equalsIgnoreCase(parts[1].trim())); + } + private static String extractDate(Document doc, String key) { // 优先尝试PC端结构 Element pcDateSpan = doc.selectFirst(String.format("div.col-lg-7 span:contains(%s)", key));