feat: add Hiperdex content provider

This commit is contained in:
Rodrigo Verdiani 2026-04-15 10:52:03 -03:00
parent 1a41ae2a8d
commit 1b7766e681
4 changed files with 160 additions and 0 deletions

View File

@ -50,6 +50,13 @@ services:
networks:
- mangamochi-network
flare-solverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
ports:
- "8191:8191"
volumes:
- /var/lib/flaresolver:/config
networks:
mangamochi-network:
driver: bridge

View File

@ -7,4 +7,5 @@ public class ContentProviders {
public static final String TAIMU = "Taimu";
public static final String MANGA_DEX = "MangaDex";
public static final String MANUAL_IMPORT = "Manual Import";
public static final String HIPERDEX = "Hiperdex";
}

View File

@ -0,0 +1,150 @@
package com.magamochi.ingestion.providers.impl;
import com.magamochi.catalog.model.entity.MangaContentProvider;
import com.magamochi.common.ContentProviders;
import com.magamochi.common.exception.NotFoundException;
import com.magamochi.ingestion.model.dto.ContentImageInfoDTO;
import com.magamochi.ingestion.model.dto.ContentInfoDTO;
import com.magamochi.ingestion.model.dto.MangaInfoDTO;
import com.magamochi.ingestion.providers.ContentProvider;
import com.magamochi.ingestion.providers.PagedContentProvider;
import com.magamochi.ingestion.service.FlareService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import static java.util.Objects.nonNull;
@Log4j2
@Service(ContentProviders.HIPERDEX)
@RequiredArgsConstructor
public class HiperdexProvider implements ContentProvider, PagedContentProvider {
private final String url = "https://hiperdex.com/";
private final FlareService flareService;
@Override
public List<ContentInfoDTO> getAvailableChapters(MangaContentProvider provider) {
log.info(
"Getting available chapters from {}, manga {}",
ContentProviders.HIPERDEX,
provider.getManga().getTitle());
try {
var document =
flareService.getContentAsJsoupDocument(
provider.getUrl(), ContentProviders.HIPERDEX);
var chapterBoxes =
document.select("li.wp-manga-chapter");
return chapterBoxes.stream()
.map(
chapterBox -> {
var linkElement = chapterBox.selectFirst("a");
var url = linkElement.attr("href");
var title = linkElement.text();
return new ContentInfoDTO(title.trim(), url.trim(), "en-US");
})
.toList();
} catch (NoSuchElementException e) {
log.error("Error parsing mangas from " + ContentProviders.HIPERDEX, e);
return List.of();
}
}
@Override
public List<ContentImageInfoDTO> getContentImages(String chapterUrl) {
log.info("Getting images from {}, url {}", ContentProviders.HIPERDEX, chapterUrl);
try {
var document =
flareService.getContentAsJsoupDocument(chapterUrl, ContentProviders.HIPERDEX);
var chapterImagesElements = document.select("div.reading-content img.wp-manga-chapter-img");
var imageUrls =
chapterImagesElements.stream()
.map(
chapterImagesElement -> {
return chapterImagesElement.attr("src");
})
.toList();
return IntStream.range(0, imageUrls.size())
.boxed()
.map(position -> new ContentImageInfoDTO(position, imageUrls.get(position)))
.toList();
} catch (NoSuchElementException e) {
log.error("Error parsing manga images from " + ContentProviders.HIPERDEX, e);
return List.of();
}
}
@Override
public List<MangaInfoDTO> getMangasFromPage(int page) {
log.info("Getting mangas from {}, page {}", ContentProviders.HIPERDEX, page);
try {
var document =
flareService.getContentAsJsoupDocument(
url + "page/" + page, ContentProviders.HIPERDEX);
var mangaElements = document.select("div.page-item-detail.manga");
return mangaElements.stream()
.map(
element -> {
var linkElement = element.selectFirst(".item-thumb > a");
var url = linkElement.attr("href");
var title = linkElement.attr("title");
// Fallback: If 'title' attribute is empty, try the <img> tag's 'alt' or 'title'
if (title.isBlank()) {
var imgElement = linkElement.selectFirst("img");
if (nonNull(imgElement)) {
title = imgElement.attr("alt");
}
}
return new MangaInfoDTO(title.trim(), url.trim());
})
.toList();
} catch (NoSuchElementException e) {
log.error("Error parsing mangas from " + ContentProviders.HIPERDEX, e);
return List.of();
}
}
@Override
public int getTotalPages() {
log.info("Getting total pages for {}", ContentProviders.HIPERDEX);
try {
var document = flareService.getContentAsJsoupDocument(url + "mangalist", ContentProviders.HIPERDEX);
var fullText = document.selectFirst("span.pages").text();
var pattern = Pattern.compile("(\\d+)$");
var matcher = pattern.matcher(fullText.trim());
if (matcher.find()) {
var lastNumber = matcher.group(1);
return Integer.parseInt(lastNumber);
}
throw new NotFoundException("Total pages number not found in " + ContentProviders.HIPERDEX);
} catch (NoSuchElementException e) {
log.error("Error parsing total pages from " + ContentProviders.HIPERDEX, e);
return 0;
}
}
}

View File

@ -0,0 +1,2 @@
INSERT INTO content_providers(name, url, active, supports_content_fetch, manual_import)
VALUES ('Hiperdex', 'https://hiperdex.com', TRUE, TRUE, FALSE);