Compare commits

...

2 Commits

Author SHA1 Message Date
rov
ccec6eafca Merge pull request 'feat: add Pink Rosa Scan provider' (#13) from feature/pink-scan-provider into main
All checks were successful
ci/woodpecker/push/pipeline Pipeline was successful
Reviewed-on: #13
2025-11-04 17:12:42 -03:00
78d38bc3c9 feat: add Pink Rosa Scan provider
All checks were successful
ci/woodpecker/push/pipeline Pipeline was successful
ci/woodpecker/pr/pipeline Pipeline was successful
2025-11-04 17:05:01 -03:00
3 changed files with 138 additions and 1 deletions

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -99,7 +100,10 @@ public class MangaChapterService {
public MangaChapterImagesDTO getMangaChapterImages(Long chapterId) { public MangaChapterImagesDTO getMangaChapterImages(Long chapterId) {
var chapter = getMangaChapterThrowIfNotFound(chapterId); var chapter = getMangaChapterThrowIfNotFound(chapterId);
var chapters = chapter.getMangaProvider().getMangaChapters(); var chapters =
chapter.getMangaProvider().getMangaChapters().stream()
.sorted(Comparator.comparing(MangaChapter::getId))
.toList();
Long prevId = null; Long prevId = null;
Long nextId = null; Long nextId = null;

View File

@ -4,4 +4,5 @@ public class ContentProviders {
public static final String MANGA_LIVRE = "Manga Livre"; public static final String MANGA_LIVRE = "Manga Livre";
public static final String MANGA_LIVRE_BLOG = "Manga Livre Blog"; public static final String MANGA_LIVRE_BLOG = "Manga Livre Blog";
public static final String MANGA_DEX = "MangaDex"; public static final String MANGA_DEX = "MangaDex";
public static final String PINK_ROSA_SCAN = "Pink Rosa Scan";
} }

View File

@ -0,0 +1,132 @@
package com.magamochi.mangamochi.service.providers.impl;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import com.magamochi.mangamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.mangamochi.model.dto.ContentProviderMangaInfoResponseDTO;
import com.magamochi.mangamochi.model.entity.MangaProvider;
import com.magamochi.mangamochi.model.enumeration.MangaStatus;
import com.magamochi.mangamochi.service.WebScrapperClientProxyService;
import com.magamochi.mangamochi.service.providers.ContentProvider;
import com.magamochi.mangamochi.service.providers.ContentProviders;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Log4j2
@Service(ContentProviders.PINK_ROSA_SCAN)
@RequiredArgsConstructor
public class PinkRosaScanProvider implements ContentProvider {
private final WebScrapperClientProxyService webScrapperClientProxyService;
@Override
public List<ContentProviderMangaInfoResponseDTO> getAvailableMangas() {
try {
var document =
webScrapperClientProxyService.scrapeToJsoupDocument(
"https://scanpinkrosa.blogspot.com/search/label/Series?max-results=1000");
var mangaElements =
document.getElementsByClass("grid relative sm:gap-3.5 gap-[2.5vw] w-full h-fit");
return mangaElements.stream()
.map(
element -> {
var linkElement =
element
.getElementsByClass(
"flex sm:gap-2.5 gap-[2vw] justify-start items-start sm:-mt-0.5 -mt-[0.5vw] w-full")
.getFirst()
.getElementsByTag("div")
.getFirst()
.getElementsByTag("a")
.getFirst();
var url = linkElement.attr("href");
var textElement = linkElement.getElementsByTag("h3");
var title = textElement.text().trim();
return new ContentProviderMangaInfoResponseDTO(
title, url, null, MangaStatus.UNKNOWN);
})
.toList();
} catch (NoSuchElementException | IOException e) {
log.error("Error parsing mangas from Pink Rosa Scan", e);
return List.of();
}
}
@Override
public List<ContentProviderMangaChapterResponseDTO> getAvailableChapters(MangaProvider provider) {
try {
var document = webScrapperClientProxyService.scrapeToJsoupDocument(provider.getUrl());
var chapterList = document.getElementById("chapter-list");
if (isNull(chapterList)) {
throw new NoSuchElementException();
}
return chapterList.getElementsByTag("a").stream()
.map(
chapterItemElement -> {
var chapterTitleElement =
chapterItemElement
.getElementsByClass("flex items-center sm:gap-4 gap-[4vw]")
.getFirst()
.getElementsByClass("grid h-fit ")
.getFirst()
.getElementsByClass("flex gap-1 justify-start items-center overflow-hidden")
.getFirst()
.getElementsByClass("text-sm truncate")
.getFirst();
return new ContentProviderMangaChapterResponseDTO(
chapterTitleElement.text().trim(), chapterItemElement.attr("href"), null, null);
})
.toList();
} catch (NoSuchElementException | IOException e) {
log.error("Error parsing mangas from Pink Rosa Scan", e);
return List.of();
}
}
@Override
public Map<Integer, String> getChapterImagesUrls(String chapterUrl) {
try {
var document = webScrapperClientProxyService.scrapeToJsoupDocument(chapterUrl);
var chapterImagesContainer = document.getElementById("pages");
if (isNull(chapterImagesContainer)) {
throw new NoSuchElementException();
}
var chapterImagesElements = chapterImagesContainer.getElementsByTag("img");
var imageUrls =
chapterImagesElements.stream()
.map(
chapterImagesElement -> {
if (!isBlank(chapterImagesElement.attr("data-src"))) {
return chapterImagesElement.attr("data-src");
}
throw new NoSuchElementException();
})
.toList();
return IntStream.range(0, imageUrls.size())
.boxed()
.collect(
Collectors.toMap(
i -> i, imageUrls::get, (existing, replacement) -> existing, LinkedHashMap::new));
} catch (NoSuchElementException | IOException e) {
log.error("Error parsing mangas from Pink Rosa Scan", e);
return Map.of();
}
}
}