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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/**
|
||||
* KodikTranslation
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class KodikTranslation {
|
||||
private final String id;
|
||||
private final String title;
|
||||
@ -11,10 +14,9 @@ public class KodikTranslation {
|
||||
private final String mediaType;
|
||||
private final String translationType;
|
||||
private final int episodeCount;
|
||||
private final boolean selected;
|
||||
|
||||
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.title = title;
|
||||
this.mediaId = mediaId;
|
||||
@ -22,10 +24,8 @@ public class KodikTranslation {
|
||||
this.mediaType = mediaType;
|
||||
this.translationType = translationType;
|
||||
this.episodeCount = episodeCount;
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
@ -54,11 +54,7 @@ public class KodikTranslation {
|
||||
return episodeCount;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public String buildUrl(int episodeNumber) {
|
||||
public String buildUrl(String quality, int episodeNumber) {
|
||||
return String.format("https://kodik.info/%s/%s/%s/720p?min_age=16&first_url=false&season=1&episode=%d",
|
||||
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 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.KodikVideoLinks;
|
||||
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.service.KodikNetworkService;
|
||||
import com.backend.extractor.kodik.service.anyame_backend.exception.KodikAPINotFoundException;
|
||||
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;
|
||||
|
||||
@ -23,35 +28,64 @@ import retrofit2.Response;
|
||||
@RestController
|
||||
public class ExtractController {
|
||||
|
||||
private final KodikMetadataExtractorService metadataExtractorService;
|
||||
private final KodikNetworkService networkService;
|
||||
private final KodikPlayerAPI kodikPlayerAPI;
|
||||
private final KodikAPITokenProvider tokenProvider;
|
||||
private final KodikExtractService extractService;
|
||||
private final KodikURLDecoderService decoderService;
|
||||
|
||||
public ExtractController(KodikMetadataExtractorService metadataExtractorService,
|
||||
KodikNetworkService networkService,
|
||||
public ExtractController(
|
||||
KodikPlayerAPI kodikPlayerAPI,
|
||||
KodikAPITokenProvider tokenProvider) {
|
||||
this.metadataExtractorService = metadataExtractorService;
|
||||
this.networkService = networkService;
|
||||
KodikAPITokenProvider tokenProvider, KodikExtractService extractService,
|
||||
KodikURLDecoderService decoderService) {
|
||||
this.kodikPlayerAPI = kodikPlayerAPI;
|
||||
this.tokenProvider = tokenProvider;
|
||||
this.extractService = extractService;
|
||||
this.decoderService = decoderService;
|
||||
}
|
||||
|
||||
@GetMapping("/shikimori")
|
||||
public KodikMetadata shikimori(@RequestParam("id") String shikimoriId) throws IOException {
|
||||
public KodikMetadata shikimori(@RequestParam("id") String shikimoriId) {
|
||||
try {
|
||||
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();
|
||||
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 {
|
||||
String rawLink = response.getLink();
|
||||
String rawPage = networkService.fetchPage(rawLink);
|
||||
return metadataExtractorService.parseMetadata(rawPage);
|
||||
@GetMapping("/shikimori/video")
|
||||
public KodikVideoLinks shikimoriVideo(@RequestParam("id") String shikimoriId) {
|
||||
try {
|
||||
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.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.KodikMetadata;
|
||||
|
||||
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
|
||||
*/
|
||||
@Service
|
||||
public class KodikMetadataExtractorService {
|
||||
public class KodikHtmlParserService {
|
||||
public KodikMetadata parseMetadata(String 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 com.backend.extractor.kodik.service.anyame_backend.api.model.KodikVideoLinks;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
@ -18,9 +21,11 @@ import okhttp3.Response;
|
||||
public class KodikNetworkService {
|
||||
private static final String DEFAULT_PROTOCOL = "https:";
|
||||
private final OkHttpClient httpClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public KodikNetworkService(OkHttpClient httpClient) {
|
||||
public KodikNetworkService(OkHttpClient httpClient, ObjectMapper objectMapper) {
|
||||
this.httpClient = httpClient;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
@ -51,7 +56,8 @@ public class KodikNetworkService {
|
||||
if (!response.isSuccessful()) {
|
||||
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;
|
||||
}
|
||||
|
||||
private String getBaseUrl(String url) {
|
||||
public String getBaseUrl(String url) {
|
||||
try {
|
||||
url = normalizeUrl(url);
|
||||
URL urlObj = new URI(url).toURL();
|
||||
return urlObj.getProtocol() + "://" + urlObj.getHost();
|
||||
} 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 com.backend.extractor.kodik.service.anyame_backend.exception.KodikDecryptionException;
|
||||
|
||||
/**
|
||||
* KodikURLDecoder
|
||||
*/
|
||||
@ -34,8 +36,10 @@ public class KodikURLDecoderService {
|
||||
|
||||
/**
|
||||
* 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++) {
|
||||
StringBuilder rotatedString = new StringBuilder();
|
||||
for (char ch : input.toCharArray()) {
|
||||
@ -69,7 +73,7 @@ public class KodikURLDecoderService {
|
||||
* @return The decoded video URL
|
||||
* @throws KodikDecryptionException if decryption fails
|
||||
*/
|
||||
public String getLinkData(String videoUrl) {
|
||||
public String getLinkData(String videoUrl) throws KodikDecryptionException {
|
||||
if (videoUrl == null || videoUrl.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Video URL cannot be null or empty");
|
||||
}
|
||||
@ -77,12 +81,4 @@ public class KodikURLDecoderService {
|
||||
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.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 com.backend.extractor.kodik.service.anyame_backend.api.model.KodikEpisode;
|
||||
import com.backend.extractor.kodik.service.anyame_backend.api.model.KodikTranslation;
|
||||
|
||||
/**
|
||||
* KodikMetadata
|
||||
*/
|
@ -1 +1,2 @@
|
||||
spring.application.name=anyame-backend
|
||||
server.error.include-message=always
|
||||
|
Reference in New Issue
Block a user