Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,20 @@ public interface JmClient {
List<JmAlbumMeta> getRandomRecommend();

/**
* 获取首页推广/Banner内容
* 获取首页推荐栏分类
*
* @return 推广数据(原始JSON Map)
* @return 首页推荐栏分类列表
*/
Map getPromote();
List<JmPromoteCategory> getPromote();

/**
* 获取首页推荐栏分类详情(分页)
*
* @param category 推荐栏分类
* @param page 页码(从1开始)
* @return 分页结果
*/
JmSearchPage getPromoteList(JmPromoteCategory category, int page);

/**
* 获取连载/系列化列表(分页)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.github.jukomu.jmcomic.api.model;

import java.util.List;
import java.util.Map;

/**
* @author JUKOMU
* @Description: 首页推荐栏分类
* @Project: jmcomic-api-java
* @Date: 2026/07/13
*/
public record JmPromoteCategory(
/*
分类ID
*/
String id,
/*
分类标题
*/
String title,
/*
分类别名
*/
String slug,
/*
分类类型(promote, not_in_category_id, category_id, library, novels)
*/
String type,
/*
筛选值
*/
String filterVal,
/*
内容列表(不同type的content结构不同,故使用Map)
*/
List<Map> content
) {

public String getId() {
return id;
}

public String getTitle() {
return title;
}

public String getSlug() {
return slug;
}

public String getType() {
return type;
}

public String getFilterVal() {
return filterVal;
}

public List<Map> getContent() {
return content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import com.google.gson.*;
import io.github.jukomu.jmcomic.api.client.JmCreatorClient;
import io.github.jukomu.jmcomic.api.client.JmNovelClient;
import io.github.jukomu.jmcomic.api.enums.FavoriteFolderType;
import io.github.jukomu.jmcomic.api.enums.TimeOption;
import io.github.jukomu.jmcomic.api.enums.VoteType;
import io.github.jukomu.jmcomic.api.enums.*;
import io.github.jukomu.jmcomic.api.exception.*;
import io.github.jukomu.jmcomic.api.model.*;
import io.github.jukomu.jmcomic.core.client.AbstractJmClient;
Expand Down Expand Up @@ -564,7 +562,7 @@ public JmSearchPage getLatest(int page) {
.build();

JmApiResponse jmApiResponse = executeGetRequest(url, JmConstants.APP_TOKEN_SECRET);
return ApiParser.parseLatestOrPromoteList(jmApiResponse.getDecodedData(), page);
return ApiParser.parseLatestList(jmApiResponse.getDecodedData(), page);
}

@Override
Expand Down Expand Up @@ -719,15 +717,57 @@ public List<JmAlbumMeta> getRandomRecommend() {
}

@Override
public Map getPromote() {
public List<JmPromoteCategory> getPromote() {
HttpUrl url = newHttpUrlBuilder()
.addPathSegment(JmConstants.API_COMIC_PROMOTE)
.build();

JmApiResponse jmApiResponse = executeGetRequest(url, JmConstants.APP_TOKEN_SECRET);
// API 返回的是数组,接口要 Map,包一层 {"list": [...]} 兼容一下
List<Map> list = JsonUtils.fromJson(jmApiResponse.getDecodedData(), List.class);
return Map.of("list", list);
return ApiParser.parsePromote(jmApiResponse.getDecodedData());
}

@Override
public JmSearchPage getPromoteList(JmPromoteCategory category, int page) {
switch (category.getType()) {
case "promote": {
HttpUrl url = newHttpUrlBuilder()
.addPathSegment(JmConstants.API_COMIC_PROMOTE_LIST)
.addQueryParameter("id", category.getId())
.addQueryParameter("page", String.valueOf(page))
.build();
JmApiResponse jmApiResponse = executeGetRequest(url, JmConstants.APP_TOKEN_SECRET);
return ApiParser.parsePromoteList(jmApiResponse.getDecodedData(), page);
}
case "category_id": {
Category cat = null;
for (Category c : Category.values()) {
if (c.getValue().equals(category.getSlug())) {
cat = c;
break;
}
}
if (cat == null) {
throw new UnsupportedOperationException("Unknown category slug: " + category.getSlug());
}
SearchQuery searchQuery = new SearchQuery.Builder()
.category(cat)
.page(page)
.orderBy(OrderBy.LATEST)
.build();
return getCategories(searchQuery);
}
case "not_in_category_id": {
SearchQuery searchQuery = new SearchQuery.Builder()
.text(category.getTitle())
.mainTag(SearchMainTag.SITE_SEARCH)
.page(page)
.orderBy(OrderBy.LATEST)
.build();
return search(searchQuery);
}
default:
throw new UnsupportedOperationException("Promote type '" + category.getType() + "' is not supported");
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,12 @@ public List<JmAlbumMeta> getRandomRecommend() {
}

@Override
public Map getPromote() {
public List<JmPromoteCategory> getPromote() {
throw new UnsupportedOperationException("Get promote via HTML client is not currently supported. Use JmApiClient instead.");
}

@Override
public JmSearchPage getPromoteList(JmPromoteCategory category, int page) {
throw new UnsupportedOperationException("Get promote via HTML client is not currently supported. Use JmApiClient instead.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.github.jukomu.jmcomic.api.exception.ResponseException;
import io.github.jukomu.jmcomic.api.model.*;
import io.github.jukomu.jmcomic.core.constant.JmConstants;
import io.github.jukomu.jmcomic.core.util.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Entities;
Expand Down Expand Up @@ -522,14 +523,14 @@ public static JmSearchPage parseSearchPage(String jsonStr, int currentPage) {
}

/**
* 解析最新上架/推广列表 (Latest/Promote List) 的API JSON响应。
* 解析最新上架的API JSON响应。
* 这些 API 返回的是裸 JSON 数组 [{...}, {...}],而非包装对象。
*
* @param jsonStr API返回的JSON字符串(裸数组)。
* @param currentPage 当前页码。
* @return 一个 JmSearchPage 对象。
*/
public static JmSearchPage parseLatestOrPromoteList(String jsonStr, int currentPage) {
public static JmSearchPage parseLatestList(String jsonStr, int currentPage) {
try {
JsonArray array = JsonParser.parseString(jsonStr).getAsJsonArray();
List<JmAlbumMeta> list = parseAlbumMetaList(array);
Expand All @@ -543,7 +544,7 @@ public static JmSearchPage parseLatestOrPromoteList(String jsonStr, int currentP
}
return new JmSearchPage(currentPage, totalItems, totalPages, list);
} catch (Exception e) {
throw new ParseResponseException("Failed to parse latest/promote list API JSON", e);
throw new ParseResponseException("Failed to parse latest list API JSON", e);
}
}

Expand Down Expand Up @@ -592,6 +593,75 @@ public static List<JmAlbumMeta> parseRandomRecommendList(String jsonStr) {
}
}

/**
* 解析首页推荐栏分类 (Promote) 的API JSON响应。
* 该接口返回裸 JSON 数组 [{...}, {...}],每个元素包含 id, title, slug, type, filter_val, content。
*
* @param jsonStr API返回的JSON字符串。
* @return JmPromoteCategory 列表。
*/
public static List<JmPromoteCategory> parsePromote(String jsonStr) {
try {
JsonArray array = JsonParser.parseString(jsonStr).getAsJsonArray();
List<JmPromoteCategory> result = new ArrayList<>();
for (JsonElement item : array) {
JsonObject node = item.getAsJsonObject();
List<Map> content = JsonUtils.fromJson(
node.getAsJsonArray("content").toString(), List.class);
result.add(new JmPromoteCategory(
getString(node, "id"),
getString(node, "title"),
getString(node, "slug"),
getString(node, "type"),
getString(node, "filter_val"),
content
));
}
return result;
} catch (Exception e) {
throw new ParseResponseException("Failed to parse promote API JSON", e);
}
}

/**
* 解析首页推荐栏列表 (Promote List) 的API JSON响应。
* 该接口返回结构为 {@code {"total": "140", "list": [...]}},total 为字符串,内容在 list 字段。
*
* @param jsonStr API返回的JSON字符串。
* @param currentPage 当前页码。
* @return 一个 JmSearchPage 对象。
*/
public static JmSearchPage parsePromoteList(String jsonStr, int currentPage) {
try {
JsonObject jsonObject = JsonParser.parseString(jsonStr).getAsJsonObject();

int totalItems = 0;
if (jsonObject.has("total") && !jsonObject.get("total").isJsonNull()) {
JsonElement totalElement = jsonObject.get("total");
if (totalElement.isJsonPrimitive() && totalElement.getAsJsonPrimitive().isString()) {
totalItems = Integer.parseInt(totalElement.getAsString());
} else {
totalItems = totalElement.getAsInt();
}
}

int totalPages = (totalItems == 0)
? 0
: (int) Math.ceil((double) totalItems / JmConstants.PAGE_SIZE_SEARCH);

JsonArray listArray = jsonObject.has("list") && jsonObject.get("list").isJsonArray()
? jsonObject.getAsJsonArray("list")
: new JsonArray();
List<JmAlbumMeta> content = parseAlbumMetaList(listArray);

return new JmSearchPage(currentPage, totalItems, totalPages, content);
} catch (ParseResponseException e) {
throw e;
} catch (Exception e) {
throw new ParseResponseException("Failed to parse promote list API JSON", e);
}
}

/**
* 解析收藏标签列表的API JSON响应。
* 该接口返回结构为 {"list": [{"tag": "...", "updated_at": "..."}, ...]}。
Expand Down
Loading