package xiaozhi.modules.parent.catalog; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; /** * 课文 MP3 数量以 xiaozhi-server 磁盘目录为准。索引接口失败时返回 null,调用方再用清单兜底。 */ @Slf4j public final class LessonClipIndex { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Pattern LESSON = Pattern.compile("^(l[1-6])\\.(\\d{3})$", Pattern.CASE_INSENSITIVE); private static final HttpClient CLIENT = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(2)) .build(); private LessonClipIndex() { } public static List fetch(String baseUrl, String scene, String lessonId, String language) { String base = baseUrl == null ? "" : baseUrl.replaceAll("/+$", ""); Matcher lesson = LESSON.matcher(lessonId == null ? "" : lessonId.trim()); if (base.isEmpty() || !lesson.matches()) { return null; } String level = lesson.group(1).toLowerCase(); String story = lesson.group(2); try { if ("shadowing".equals(scene)) { String lang = "cn".equalsIgnoreCase(language) ? "cn" : "en"; return oneFolder(base, "shadowing", level, story, lang); } if ("extensive".equals(scene)) { List cn = oneFolder(base, "repeater", level, story, "cn"); List en = oneFolder(base, "repeater", level, story, "en"); if (cn == null || en == null) { return null; } List both = new ArrayList<>(cn.size() + en.size()); both.addAll(cn); both.addAll(en); return both; } } catch (Exception ex) { log.warn("lesson clip index failed: {}", ex.toString()); return null; } return null; } private static List oneFolder(String base, String kind, String level, String story, String lang) throws Exception { String url = base + "/index/" + kind + "/" + level + "/" + story + "/" + lang; HttpRequest request = HttpRequest.newBuilder(URI.create(url)) .timeout(Duration.ofSeconds(3)) .GET() .build(); HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { return null; } JsonNode root = MAPPER.readTree(response.body()); if (root == null || !root.path("present").asBoolean(false)) { return null; } List files = new ArrayList<>(); JsonNode raw = root.get("files"); if (raw != null && raw.isArray()) { for (JsonNode item : raw) { if (item.isTextual()) { files.add(item.asText()); } } } return files; } }