Video link retrieve support with controller
This commit is contained in:
@ -1,8 +1,11 @@
|
|||||||
package com.backend.extractor.kodik.service.anyame_backend.api.model;
|
package com.backend.extractor.kodik.service.anyame_backend.api.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KodikTranslation
|
* KodikTranslation
|
||||||
*/
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class KodikTranslation {
|
public class KodikTranslation {
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String title;
|
private final String title;
|
||||||
@ -11,10 +14,9 @@ public class KodikTranslation {
|
|||||||
private final String mediaType;
|
private final String mediaType;
|
||||||
private final String translationType;
|
private final String translationType;
|
||||||
private final int episodeCount;
|
private final int episodeCount;
|
||||||
private final boolean selected;
|
|
||||||
|
|
||||||
public KodikTranslation(String id, String title, String mediaId, String mediaHash,
|
public KodikTranslation(String id, String title, String mediaId, String mediaHash,
|
||||||
String mediaType, String translationType, int episodeCount, boolean selected) {
|
String mediaType, String translationType, int episodeCount) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.mediaId = mediaId;
|
this.mediaId = mediaId;
|
||||||
@ -22,10 +24,8 @@ public class KodikTranslation {
|
|||||||
this.mediaType = mediaType;
|
this.mediaType = mediaType;
|
||||||
this.translationType = translationType;
|
this.translationType = translationType;
|
||||||
this.episodeCount = episodeCount;
|
this.episodeCount = episodeCount;
|
||||||
this.selected = selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@ -54,11 +54,7 @@ public class KodikTranslation {
|
|||||||
return episodeCount;
|
return episodeCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSelected() {
|
public String buildUrl(String quality, int episodeNumber) {
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String buildUrl(int episodeNumber) {
|
|
||||||
return String.format("https://kodik.info/%s/%s/%s/720p?min_age=16&first_url=false&season=1&episode=%d",
|
return String.format("https://kodik.info/%s/%s/%s/720p?min_age=16&first_url=false&season=1&episode=%d",
|
||||||
mediaType, mediaId, mediaHash, episodeNumber);
|
mediaType, mediaId, mediaHash, episodeNumber);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.backend.extractor.kodik.service.anyame_backend.api.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikDecryptionException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.KodikURLDecoderService;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KodikVideoLinks
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class KodikVideoLinks {
|
||||||
|
private final Map<String, List<Link>> links;
|
||||||
|
|
||||||
|
public KodikVideoLinks(Map<String, List<Link>> links) {
|
||||||
|
this.links = links;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<Link>> getLinks() {
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decodeLinks(KodikURLDecoderService decoderService) throws KodikDecryptionException {
|
||||||
|
for (List<Link> links : links.values()) {
|
||||||
|
for (Link link : links) {
|
||||||
|
link.decodeSrc(decoderService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class Link {
|
||||||
|
private final String type;
|
||||||
|
private String src;
|
||||||
|
|
||||||
|
public Link(String src, String type) {
|
||||||
|
this.src = src;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSrc() {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decodeSrc(KodikURLDecoderService decoderService) throws KodikDecryptionException {
|
||||||
|
this.src = decoderService.getLinkData(this.src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,16 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.KodikPlayerAPI;
|
import com.backend.extractor.kodik.service.anyame_backend.api.KodikPlayerAPI;
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikMetadata;
|
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikPlayerResponse;
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikPlayerResponse;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikVideoLinks;
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.component.KodikAPITokenProvider;
|
import com.backend.extractor.kodik.service.anyame_backend.component.KodikAPITokenProvider;
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.service.KodikMetadataExtractorService;
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikAPINotFoundException;
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.service.KodikNetworkService;
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikDecryptionException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikExtractionException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikPlayerNotFoundException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.KodikExtractService;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.KodikURLDecoderService;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.model.KodikMetadata;
|
||||||
|
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
@ -23,35 +28,64 @@ import retrofit2.Response;
|
|||||||
@RestController
|
@RestController
|
||||||
public class ExtractController {
|
public class ExtractController {
|
||||||
|
|
||||||
private final KodikMetadataExtractorService metadataExtractorService;
|
|
||||||
private final KodikNetworkService networkService;
|
|
||||||
private final KodikPlayerAPI kodikPlayerAPI;
|
private final KodikPlayerAPI kodikPlayerAPI;
|
||||||
private final KodikAPITokenProvider tokenProvider;
|
private final KodikAPITokenProvider tokenProvider;
|
||||||
|
private final KodikExtractService extractService;
|
||||||
|
private final KodikURLDecoderService decoderService;
|
||||||
|
|
||||||
public ExtractController(KodikMetadataExtractorService metadataExtractorService,
|
public ExtractController(
|
||||||
KodikNetworkService networkService,
|
|
||||||
KodikPlayerAPI kodikPlayerAPI,
|
KodikPlayerAPI kodikPlayerAPI,
|
||||||
KodikAPITokenProvider tokenProvider) {
|
KodikAPITokenProvider tokenProvider, KodikExtractService extractService,
|
||||||
this.metadataExtractorService = metadataExtractorService;
|
KodikURLDecoderService decoderService) {
|
||||||
this.networkService = networkService;
|
|
||||||
this.kodikPlayerAPI = kodikPlayerAPI;
|
this.kodikPlayerAPI = kodikPlayerAPI;
|
||||||
this.tokenProvider = tokenProvider;
|
this.tokenProvider = tokenProvider;
|
||||||
|
this.extractService = extractService;
|
||||||
|
this.decoderService = decoderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/shikimori")
|
@GetMapping("/shikimori")
|
||||||
public KodikMetadata shikimori(@RequestParam("id") String shikimoriId) throws IOException {
|
public KodikMetadata shikimori(@RequestParam("id") String shikimoriId) {
|
||||||
|
try {
|
||||||
Response<KodikPlayerResponse> response = kodikPlayerAPI
|
Response<KodikPlayerResponse> response = kodikPlayerAPI
|
||||||
.getShikimoriPlayer(shikimoriId, tokenProvider.getKodikToken()).execute();
|
.getShikimoriPlayer(shikimoriId, tokenProvider.getKodikToken()).execute();
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "cannot find player, check id validity");
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "cannot find player, check id validity");
|
||||||
}
|
}
|
||||||
KodikPlayerResponse responseResult = response.body();
|
KodikPlayerResponse responseResult = response.body();
|
||||||
return extractMetadata(responseResult);
|
String url = responseResult.getLink();
|
||||||
|
return extractService.getMetadata(url);
|
||||||
|
} catch (KodikExtractionException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "cannot retrieve metadat", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "i/o error");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private KodikMetadata extractMetadata(KodikPlayerResponse response) throws IOException {
|
@GetMapping("/shikimori/video")
|
||||||
String rawLink = response.getLink();
|
public KodikVideoLinks shikimoriVideo(@RequestParam("id") String shikimoriId) {
|
||||||
String rawPage = networkService.fetchPage(rawLink);
|
try {
|
||||||
return metadataExtractorService.parseMetadata(rawPage);
|
Response<KodikPlayerResponse> response = kodikPlayerAPI
|
||||||
|
.getShikimoriPlayer(shikimoriId, tokenProvider.getKodikToken()).execute();
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "cannot find player, check id validity");
|
||||||
}
|
}
|
||||||
|
KodikPlayerResponse responseResult = response.body();
|
||||||
|
String url = responseResult.getLink();
|
||||||
|
KodikVideoLinks links = extractService.retrieveVideoLinks(url);
|
||||||
|
links.decodeLinks(decoderService);
|
||||||
|
return links;
|
||||||
|
} catch (KodikExtractionException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "cannot retrieve metadat", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "i/o error");
|
||||||
|
} catch (KodikPlayerNotFoundException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "player not found");
|
||||||
|
} catch (KodikAPINotFoundException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "api endpoint not found");
|
||||||
|
} catch (KodikDecryptionException e) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "cannot decode links");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.backend.extractor.kodik.service.anyame_backend.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom exception for Kodik decryption failures
|
||||||
|
*/
|
||||||
|
public class KodikDecryptionException extends Exception {
|
||||||
|
public KodikDecryptionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.backend.extractor.kodik.service.anyame_backend.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikTranslation;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikVideoLinks;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikAPINotFoundException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikExtractionException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikPlayerNotFoundException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.model.KodikAPIPayload;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.model.KodikMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KodikExtractService
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class KodikExtractService {
|
||||||
|
private final KodikNetworkService networkService;
|
||||||
|
private final KodikPlayerEndpointService endpointService;
|
||||||
|
private final KodikHtmlParserService htmlParserService;
|
||||||
|
|
||||||
|
public KodikExtractService(KodikPlayerEndpointService endpointService, KodikNetworkService networkService,
|
||||||
|
KodikHtmlParserService htmlParserService) {
|
||||||
|
this.networkService = networkService;
|
||||||
|
this.endpointService = endpointService;
|
||||||
|
this.htmlParserService = htmlParserService;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public KodikMetadata getMetadata(String metadataURL) throws KodikExtractionException {
|
||||||
|
try {
|
||||||
|
String html = networkService.fetchPage(metadataURL);
|
||||||
|
return htmlParserService.parseMetadata(html);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new KodikExtractionException("Failed to extract metadata", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KodikVideoLinks retrieveVideoLinks(KodikTranslation translation, int episode)
|
||||||
|
throws KodikExtractionException, IOException, KodikPlayerNotFoundException, KodikAPINotFoundException {
|
||||||
|
String episodeUrl = translation.buildUrl(episode);
|
||||||
|
return retrieveVideoLinks(episodeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KodikVideoLinks retrieveVideoLinks(String episodeUrl)
|
||||||
|
throws KodikExtractionException, IOException, KodikPlayerNotFoundException, KodikAPINotFoundException {
|
||||||
|
KodikMetadata metadata = getMetadata(episodeUrl);
|
||||||
|
String playerEndpoint = endpointService.retrieveEndpoint(episodeUrl);
|
||||||
|
|
||||||
|
return makeApiRequestWithFallback(episodeUrl, playerEndpoint, metadata.getApiPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
private KodikVideoLinks makeApiRequestWithFallback(String episodeUrl, String playerEndpoint,
|
||||||
|
KodikAPIPayload apiPayload)
|
||||||
|
throws IOException, KodikPlayerNotFoundException, KodikAPINotFoundException {
|
||||||
|
String baseUrl = networkService.getBaseUrl(episodeUrl);
|
||||||
|
String apiUrl = baseUrl + playerEndpoint;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return networkService.postFormData(apiUrl, apiPayload.toFormBody(), episodeUrl);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Update endpoint, then request again
|
||||||
|
if (!endpointService.hasUpdateAttempted()) {
|
||||||
|
String newEndpoint = endpointService.updateEndpoint(episodeUrl);
|
||||||
|
String newApiUrl = baseUrl + newEndpoint;
|
||||||
|
return networkService.postFormData(newApiUrl, apiPayload.toFormBody(), episodeUrl);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,16 +9,17 @@ import org.jsoup.nodes.Element;
|
|||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikAPIPayload;
|
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikEpisode;
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikEpisode;
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikMetadata;
|
|
||||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikTranslation;
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikTranslation;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.model.KodikAPIPayload;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.service.model.KodikMetadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KodikMetadataExtractorService
|
* KodikMetadataExtractorService
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class KodikMetadataExtractorService {
|
public class KodikHtmlParserService {
|
||||||
public KodikMetadata parseMetadata(String html) {
|
public KodikMetadata parseMetadata(String html) {
|
||||||
Document doc = Jsoup.parse(html);
|
Document doc = Jsoup.parse(html);
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.backend.extractor.kodik.service.anyame_backend.service;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* KodikLinkExtrtactService
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class KodikLinkExtrtactService {
|
|
||||||
|
|
||||||
}
|
|
@ -6,6 +6,9 @@ import java.net.URL;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikVideoLinks;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
@ -18,9 +21,11 @@ import okhttp3.Response;
|
|||||||
public class KodikNetworkService {
|
public class KodikNetworkService {
|
||||||
private static final String DEFAULT_PROTOCOL = "https:";
|
private static final String DEFAULT_PROTOCOL = "https:";
|
||||||
private final OkHttpClient httpClient;
|
private final OkHttpClient httpClient;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public KodikNetworkService(OkHttpClient httpClient) {
|
public KodikNetworkService(OkHttpClient httpClient, ObjectMapper objectMapper) {
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String fetchPage(String url) throws IOException {
|
public String fetchPage(String url) throws IOException {
|
||||||
@ -36,7 +41,7 @@ public class KodikNetworkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String postFormData(String url, RequestBody body, String referer) throws IOException {
|
public KodikVideoLinks postFormData(String url, RequestBody body, String referer) throws IOException {
|
||||||
String baseUrl = getBaseUrl(url);
|
String baseUrl = getBaseUrl(url);
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
@ -51,7 +56,8 @@ public class KodikNetworkService {
|
|||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
throw new IOException("API request failed: " + response);
|
throw new IOException("API request failed: " + response);
|
||||||
}
|
}
|
||||||
return response.body().string();
|
String rawResult = response.body().string();
|
||||||
|
return objectMapper.readValue(rawResult, KodikVideoLinks.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +68,9 @@ public class KodikNetworkService {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getBaseUrl(String url) {
|
public String getBaseUrl(String url) {
|
||||||
try {
|
try {
|
||||||
|
url = normalizeUrl(url);
|
||||||
URL urlObj = new URI(url).toURL();
|
URL urlObj = new URI(url).toURL();
|
||||||
return urlObj.getProtocol() + "://" + urlObj.getHost();
|
return urlObj.getProtocol() + "://" + urlObj.getHost();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.backend.extractor.kodik.service.anyame_backend.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikAPINotFoundException;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikPlayerNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KodikPlayerEndpointService
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class KodikPlayerEndpointService {
|
||||||
|
private static final Pattern PLAYER_JS_PATTERN = Pattern
|
||||||
|
.compile("<script\\s*type=\"text/javascript\"\\s*src=\"(/assets/js/app\\..*?)\">");
|
||||||
|
private static final Pattern AJAX_URL_PATTERN = Pattern
|
||||||
|
.compile("\\$\\.ajax\\([^>]+,url:\\s*atob\\([\"']([\\w=]+)[\"']\\)");
|
||||||
|
|
||||||
|
private final KodikNetworkService networkService;
|
||||||
|
private String cachedEndpoint;
|
||||||
|
private boolean updateAttempted = false;
|
||||||
|
|
||||||
|
public KodikPlayerEndpointService(KodikNetworkService networkService) {
|
||||||
|
this.networkService = networkService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String retrieveEndpoint(String metadataUrl)
|
||||||
|
throws IOException, KodikPlayerNotFoundException, KodikAPINotFoundException {
|
||||||
|
if (cachedEndpoint != null) {
|
||||||
|
return cachedEndpoint;
|
||||||
|
}
|
||||||
|
return updateEndpoint(metadataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String updateEndpoint(String metadataUrl)
|
||||||
|
throws IOException, KodikPlayerNotFoundException, KodikAPINotFoundException {
|
||||||
|
String html = networkService.fetchPage(metadataUrl);
|
||||||
|
String playerJsPath = extractPlayerJsPath(html, metadataUrl);
|
||||||
|
cachedEndpoint = extractEndpointFromJs(metadataUrl, playerJsPath);
|
||||||
|
updateAttempted = true;
|
||||||
|
return cachedEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasUpdateAttempted() {
|
||||||
|
return updateAttempted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearCache() {
|
||||||
|
cachedEndpoint = null;
|
||||||
|
updateAttempted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractPlayerJsPath(String html, String metadataUrl) throws KodikPlayerNotFoundException {
|
||||||
|
Matcher matcher = PLAYER_JS_PATTERN.matcher(html);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
throw new KodikPlayerNotFoundException("Player JS asset not found in: " + metadataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractEndpointFromJs(String metadataUrl, String playerJsPath)
|
||||||
|
throws IOException, KodikAPINotFoundException {
|
||||||
|
String baseUrl = networkService.getBaseUrl(metadataUrl);
|
||||||
|
String playerJsUrl = baseUrl + playerJsPath;
|
||||||
|
String jsContent = networkService.fetchPage(playerJsUrl);
|
||||||
|
|
||||||
|
Matcher matcher = AJAX_URL_PATTERN.matcher(jsContent);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String base64Endpoint = matcher.group(1);
|
||||||
|
byte[] decodedBytes = Base64.getDecoder().decode(base64Endpoint);
|
||||||
|
return new String(decodedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new KodikAPINotFoundException("API endpoint not found in: " + playerJsUrl);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@ import java.util.Base64;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikDecryptionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KodikURLDecoder
|
* KodikURLDecoder
|
||||||
*/
|
*/
|
||||||
@ -34,8 +36,10 @@ public class KodikURLDecoderService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to decrypt the input string by testing all possible ROT cipher rotations
|
* Try to decrypt the input string by testing all possible ROT cipher rotations
|
||||||
|
*
|
||||||
|
* @throws KodikDecryptionException
|
||||||
*/
|
*/
|
||||||
private String tryToDecrypt(String input) {
|
private String tryToDecrypt(String input) throws KodikDecryptionException {
|
||||||
for (int rotation = 0; rotation < 26; rotation++) {
|
for (int rotation = 0; rotation < 26; rotation++) {
|
||||||
StringBuilder rotatedString = new StringBuilder();
|
StringBuilder rotatedString = new StringBuilder();
|
||||||
for (char ch : input.toCharArray()) {
|
for (char ch : input.toCharArray()) {
|
||||||
@ -69,7 +73,7 @@ public class KodikURLDecoderService {
|
|||||||
* @return The decoded video URL
|
* @return The decoded video URL
|
||||||
* @throws KodikDecryptionException if decryption fails
|
* @throws KodikDecryptionException if decryption fails
|
||||||
*/
|
*/
|
||||||
public String getLinkData(String videoUrl) {
|
public String getLinkData(String videoUrl) throws KodikDecryptionException {
|
||||||
if (videoUrl == null || videoUrl.trim().isEmpty()) {
|
if (videoUrl == null || videoUrl.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("Video URL cannot be null or empty");
|
throw new IllegalArgumentException("Video URL cannot be null or empty");
|
||||||
}
|
}
|
||||||
@ -77,12 +81,4 @@ public class KodikURLDecoderService {
|
|||||||
return tryToDecrypt(videoUrl);
|
return tryToDecrypt(videoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom exception for Kodik decryption failures
|
|
||||||
*/
|
|
||||||
public static class KodikDecryptionException extends RuntimeException {
|
|
||||||
public KodikDecryptionException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.backend.extractor.kodik.service.anyame_backend.api.model;
|
package com.backend.extractor.kodik.service.anyame_backend.service.model;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
@ -1,7 +1,10 @@
|
|||||||
package com.backend.extractor.kodik.service.anyame_backend.api.model;
|
package com.backend.extractor.kodik.service.anyame_backend.service.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikEpisode;
|
||||||
|
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikTranslation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KodikMetadata
|
* KodikMetadata
|
||||||
*/
|
*/
|
@ -1 +1,2 @@
|
|||||||
spring.application.name=anyame-backend
|
spring.application.name=anyame-backend
|
||||||
|
server.error.include-message=always
|
||||||
|
Reference in New Issue
Block a user