feat: update endpoints and remove authorization from MangaDexClient and Provider

This commit is contained in:
Rodrigo Verdiani 2025-10-27 13:04:33 -03:00
parent 28e71403c1
commit 8182db8cb7
5 changed files with 61 additions and 132 deletions

4
.env
View File

@ -6,8 +6,8 @@ MINIO_ENDPOINT=http://omv.badger-pirarucu.ts.net:9000
MINIO_USER=admin MINIO_USER=admin
MINIO_PASS=!E9v4i0v3 MINIO_PASS=!E9v4i0v3
WEBSCRAPPER_ENDPOINT=http://localhost:8090/url WEBSCRAPPER_ENDPOINT=http://mangamochi.badger-pirarucu.ts.net:8090/url
MANGAMATCHER_ENDPOINT=http://127.0.0.1:8000/match-title MANGAMATCHER_ENDPOINT=http://mangamochi.badger-pirarucu.ts.net:8000/match-title
MANGADEX_USER=rocverde MANGADEX_USER=rocverde
MANGADEX_PASS=!A3u8e4s0 MANGADEX_PASS=!A3u8e4s0

View File

@ -1,33 +0,0 @@
package com.magamochi.mangamochi.client;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "mangaDexAuthentication", url = "https://auth.mangadex.org")
public interface MangaDexAuthenticationClient {
// @Headers("Content-Type: application/x-www-form-urlencoded")
@PostMapping(
value = "/realms/mangadex/protocol/openid-connect/token",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
AuthTokenResponse authenticate(@RequestBody MultiValueMap<String, String> form);
public record AuthTokenResponse(
@JsonProperty("access_token") String accessToken,
@JsonProperty("refresh_token") String refreshToken) {}
public static MultiValueMap<String, String> build(
String username, String password, String clientId, String clientSecret) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("grant_type", "password");
form.add("username", username);
form.add("password", password);
form.add("client_id", clientId);
form.add("client_secret", clientSecret);
return form;
}
}

View File

@ -9,23 +9,17 @@ import org.springframework.web.bind.annotation.*;
@FeignClient(name = "mangaDex", url = "https://api.mangadex.org") @FeignClient(name = "mangaDex", url = "https://api.mangadex.org")
public interface MangaDexClient { public interface MangaDexClient {
@GetMapping("/manga/{id}") @GetMapping("/manga/{id}")
MangaDexMangaDTO getManga( MangaDexMangaDTO getManga(@PathVariable UUID id);
@PathVariable UUID id, @RequestHeader("Authorization") String authorization);
@GetMapping("/manga/{id}/feed")
MangaDexMangaFeedDTO getMangaFeed(@PathVariable UUID id);
@GetMapping("/manga/{id}/feed") @GetMapping("/manga/{id}/feed")
MangaDexMangaFeedDTO getMangaFeed( MangaDexMangaFeedDTO getMangaFeed(
@PathVariable UUID id, @RequestHeader("Authorization") String authorization); @PathVariable UUID id, @RequestParam int limit, @RequestParam int offset);
@GetMapping("/manga/{id}/feed")
MangaDexMangaFeedDTO getMangaFeed(
@PathVariable UUID id,
@RequestParam int limit,
@RequestParam int offset,
@RequestHeader("Authorization") String authorization);
@GetMapping("/at-home/server/{chapterId}") @GetMapping("/at-home/server/{chapterId}")
MangaChapterDataDTO getMangaChapter( MangaChapterDataDTO getMangaChapter(@PathVariable UUID chapterId);
@PathVariable UUID chapterId, @RequestHeader("Authorization") String authorization);
record MangaDexMangaFeedDTO( record MangaDexMangaFeedDTO(
List<MangaFeedData> data, Integer limit, Integer offset, Integer total) { List<MangaFeedData> data, Integer limit, Integer offset, Integer total) {

View File

@ -3,7 +3,6 @@ package com.magamochi.mangamochi.service.providers.impl;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.mangamochi.client.MangaDexAuthenticationClient;
import com.magamochi.mangamochi.client.MangaDexClient; import com.magamochi.mangamochi.client.MangaDexClient;
import com.magamochi.mangamochi.exception.NotFoundException; import com.magamochi.mangamochi.exception.NotFoundException;
import com.magamochi.mangamochi.exception.UnprocessableException; import com.magamochi.mangamochi.exception.UnprocessableException;
@ -23,34 +22,18 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Log4j2 @Log4j2
@Service(ContentProviders.MANGA_DEX) @Service(ContentProviders.MANGA_DEX)
@RequiredArgsConstructor @RequiredArgsConstructor
public class MangaDexProvider implements ContentProvider { public class MangaDexProvider implements ContentProvider {
@Value("${manga-dex.username}")
private String mangaDexUsername;
@Value("${manga-dex.password}")
private String mangaDexPassword;
@Value("${manga-dex.client-id}")
private String mangaDexClientId;
@Value("${manga-dex.client-secret}")
private String mangaDexClientSecret;
private final MangaDexClient mangaDexClient; private final MangaDexClient mangaDexClient;
private final MangaDexAuthenticationClient mangaDexAuthenticationClient;
private final MangaCreationService mangaCreationService; private final MangaCreationService mangaCreationService;
private final ProviderRepository providerRepository; private final ProviderRepository providerRepository;
private final MangaProviderRepository mangaProviderRepository; private final MangaProviderRepository mangaProviderRepository;
RateLimiter rateLimiter = RateLimiter.create(1); RateLimiter rateLimiter = RateLimiter.create(3);
private String authorizationToken;
@Override @Override
public List<ContentProviderMangaInfoResponseDTO> getAvailableMangas() { public List<ContentProviderMangaInfoResponseDTO> getAvailableMangas() {
@ -62,58 +45,66 @@ public class MangaDexProvider implements ContentProvider {
@Override @Override
public List<ContentProviderMangaChapterResponseDTO> getAvailableChapters(MangaProvider provider) { public List<ContentProviderMangaChapterResponseDTO> getAvailableChapters(MangaProvider provider) {
rateLimiter.acquire(); try {
var response = rateLimiter.acquire();
mangaDexClient.getMangaFeed(UUID.fromString(provider.getUrl()), getAuthorizationToken()); var response = mangaDexClient.getMangaFeed(UUID.fromString(provider.getUrl()));
var mangas = new ArrayList<>(response.data()); var mangas = new ArrayList<>(response.data());
var totalPages = (int) Math.ceil((double) response.total() / 100); // Default page size is 100 var totalPages = (int) Math.ceil((double) response.total() / 500);
IntStream.range(1, totalPages) try {
.forEach(
i -> {
rateLimiter.acquire();
var pagedResponse = IntStream.range(1, totalPages)
mangaDexClient.getMangaFeed( .parallel()
UUID.fromString(provider.getUrl()), 100, i * 100, getAuthorizationToken()); .forEach(
i -> {
rateLimiter.acquire();
mangas.addAll(pagedResponse.data()); var pagedResponse =
}); mangaDexClient.getMangaFeed(UUID.fromString(provider.getUrl()), 500, i * 500);
// TODO this is filtering only pt-br chapters for now, we may want to make this configurable mangas.addAll(pagedResponse.data());
// later });
return mangas.stream() } catch (Exception e) {
.filter( log.warn(e.getMessage());
c -> }
c.type().equals("chapter")
&& c.attributes().isUnavailable().equals(Boolean.FALSE) // TODO this is filtering only pt-br chapters for now, we may want to make this configurable
&& c.attributes().translatedLanguage().equals("pt-br")) // later
.sorted( return mangas.stream()
(o1, o2) -> { .filter(
try { c ->
Float chapter1 = Float.parseFloat(o1.attributes().chapter()); c.type().equals("chapter")
Float chapter2 = Float.parseFloat(o2.attributes().chapter()); && c.attributes().isUnavailable().equals(Boolean.FALSE)
return chapter2.compareTo(chapter1); && c.attributes().translatedLanguage().equals("pt-br"))
} catch (NumberFormatException e) { .sorted(
return o2.attributes().chapter().compareTo(o1.attributes().chapter()); (o1, o2) -> {
} try {
}) Float chapter1 = Float.parseFloat(o1.attributes().chapter());
.map( Float chapter2 = Float.parseFloat(o2.attributes().chapter());
c -> return chapter2.compareTo(chapter1);
new ContentProviderMangaChapterResponseDTO( } catch (NumberFormatException e) {
c.attributes().chapter() + " - " + c.attributes().title(), return o2.attributes().chapter().compareTo(o1.attributes().chapter());
c.id().toString(), }
c.attributes().chapter(), })
c.attributes().translatedLanguage())) .map(
.toList(); c ->
new ContentProviderMangaChapterResponseDTO(
c.attributes().chapter() + " - " + c.attributes().title(),
c.id().toString(),
c.attributes().chapter(),
c.attributes().translatedLanguage()))
.toList();
} catch (Exception e) {
log.warn(e.getMessage());
return null;
}
} }
@Override @Override
public Map<Integer, String> getChapterImagesUrls(String chapterUrl) { public Map<Integer, String> getChapterImagesUrls(String chapterUrl) {
rateLimiter.acquire(); rateLimiter.acquire();
var chapter = var chapter = mangaDexClient.getMangaChapter(UUID.fromString(chapterUrl));
mangaDexClient.getMangaChapter(UUID.fromString(chapterUrl), getAuthorizationToken());
var chapterImageHashes = var chapterImageHashes =
chapter.chapter().data().stream() chapter.chapter().data().stream()
@ -131,10 +122,8 @@ public class MangaDexProvider implements ContentProvider {
} }
public ImportMangaDexResponseDTO importManga(UUID id) { public ImportMangaDexResponseDTO importManga(UUID id) {
var token = getAuthorizationToken();
rateLimiter.acquire(); rateLimiter.acquire();
var resultData = mangaDexClient.getManga(id, token).data(); var resultData = mangaDexClient.getManga(id).data();
if (resultData.attributes().title().isEmpty()) { if (resultData.attributes().title().isEmpty()) {
throw new UnprocessableException("Manga title not found for ID: " + id); throw new UnprocessableException("Manga title not found for ID: " + id);
@ -174,19 +163,4 @@ public class MangaDexProvider implements ContentProvider {
return new ImportMangaDexResponseDTO(manga.getId()); return new ImportMangaDexResponseDTO(manga.getId());
} }
private String getAuthorizationToken() {
if (isNull(authorizationToken)) {
rateLimiter.acquire();
var authResponse =
mangaDexAuthenticationClient.authenticate(
MangaDexAuthenticationClient.build(
mangaDexUsername, mangaDexPassword, mangaDexClientId, mangaDexClientSecret));
authorizationToken = "Bearer " + authResponse.accessToken();
}
return authorizationToken;
}
} }

View File

@ -22,7 +22,7 @@ spring:
openfeign: openfeign:
client: client:
config: config:
default: web-scrapper:
connect-timeout: 120000 connect-timeout: 120000
read-timeout: 120000 read-timeout: 120000
@ -44,11 +44,5 @@ jwt:
secret: mySecretKeymySecretKeymySecretKeymySecretKeymySecretKeymySecretKey secret: mySecretKeymySecretKeymySecretKeymySecretKeymySecretKeymySecretKey
expiration: 86400000 # 24 hours in milliseconds expiration: 86400000 # 24 hours in milliseconds
manga-dex:
username: ${MANGADEX_USER}
password: ${MANGADEX_PASS}
client-id: ${MANGADEX_CLIENT_ID}
client-secret: ${MANGADEX_CLIENT_SECRET}
manga-matcher: manga-matcher:
endpoint: ${MANGAMATCHER_ENDPOINT} endpoint: ${MANGAMATCHER_ENDPOINT}