diff --git a/src/main/java/com/backend/extractor/kodik/service/anyame_backend/api/KodikPlayerAPI.java b/src/main/java/com/backend/extractor/kodik/service/anyame_backend/api/KodikPlayerAPI.java index 496d80c..41c08d7 100644 --- a/src/main/java/com/backend/extractor/kodik/service/anyame_backend/api/KodikPlayerAPI.java +++ b/src/main/java/com/backend/extractor/kodik/service/anyame_backend/api/KodikPlayerAPI.java @@ -13,11 +13,15 @@ public interface KodikPlayerAPI { } default String kinopoiskURL(String id) { - return BASE_PLAYER_URL + "kinopoiskID%3D"; + return BASE_PLAYER_URL + "kinopoiskID%3D" + id; } default String imdbURL(String id) { - return BASE_PLAYER_URL + "kinopoiskID%3D"; + return BASE_PLAYER_URL + "imdbID%3D" + id; + } + + default String kodikIDURL(String id) { + return BASE_PLAYER_URL + "ID%3D" + id; } @GET("get-player") @@ -26,9 +30,9 @@ public interface KodikPlayerAPI { @Query("hasPlayer") Boolean hasPlayer, @Query("url") String url, @Query("token") String token, + @Query("ID") String kodikId, @Query("shikimoriID") String shikimoriID, @Query("kinopoiskID") String kinopoiskID, - @Query("imdbID") String imdbID - ); + @Query("imdbID") String imdbID); } diff --git a/src/main/java/com/backend/extractor/kodik/service/anyame_backend/service/KodikURLDecoderService.java b/src/main/java/com/backend/extractor/kodik/service/anyame_backend/service/KodikURLDecoderService.java new file mode 100644 index 0000000..e9339fe --- /dev/null +++ b/src/main/java/com/backend/extractor/kodik/service/anyame_backend/service/KodikURLDecoderService.java @@ -0,0 +1,88 @@ +package com.backend.extractor.kodik.service.anyame_backend.service; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.springframework.stereotype.Service; + +/** + * KodikURLDecoder + */ +@Service +// TODO: Test shikimori id: 1535 with KodikURLDecoder, only mp4:hls:manifest is +// included +// TODO: Paste some random anime urls to test decoder +public class KodikURLDecoderService { + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String MANIFEST_IDENTIFIER = "mp4:hls:manifest.m3u8"; + private static final int BASE64_BLOCK_SIZE = 4; + + /** + * Convert a character using ROT cipher with specified rotation + */ + private String convertChar(char ch, int rotation) { + boolean isLower = Character.isLowerCase(ch); + char upperChar = Character.toUpperCase(ch); + + if (ALPHABET.indexOf(upperChar) != -1) { + int index = (ALPHABET.indexOf(upperChar) + rotation) % ALPHABET.length(); + char result = ALPHABET.charAt(index); + return isLower ? String.valueOf(Character.toLowerCase(result)) : String.valueOf(result); + } + return String.valueOf(ch); + } + + /** + * Try to decrypt the input string by testing all possible ROT cipher rotations + */ + private String tryToDecrypt(String input) { + for (int rotation = 0; rotation < 26; rotation++) { + StringBuilder rotatedString = new StringBuilder(); + for (char ch : input.toCharArray()) { + rotatedString.append(convertChar(ch, rotation)); + } + + // Base64 strings must be multiples of 4 characters, add padding if needed + int missingPaddingLength = (BASE64_BLOCK_SIZE - (rotatedString.length() % BASE64_BLOCK_SIZE)) + % BASE64_BLOCK_SIZE; + rotatedString.append("=".repeat(missingPaddingLength)); + + try { + byte[] decodedBytes = Base64.getDecoder().decode(rotatedString.toString()); + String decodedResult = new String(decodedBytes, StandardCharsets.UTF_8); + + if (decodedResult.contains(MANIFEST_IDENTIFIER)) { + return decodedResult; + } + } catch (IllegalArgumentException e) { + continue; + } + } + + throw new KodikDecryptionException("Unable to decrypt the provided URL - no valid rotation found"); + } + + /** + * Public method to get link data from encoded video URL + * + * @param videoUrl The encoded video URL + * @return The decoded video URL + * @throws KodikDecryptionException if decryption fails + */ + public String getLinkData(String videoUrl) { + if (videoUrl == null || videoUrl.trim().isEmpty()) { + throw new IllegalArgumentException("Video URL cannot be null or empty"); + } + + return tryToDecrypt(videoUrl); + } + + /** + * Custom exception for Kodik decryption failures + */ + public static class KodikDecryptionException extends RuntimeException { + public KodikDecryptionException(String message) { + super(message); + } + } +}