diff --git a/src/en/kickassanime/AndroidManifest.xml b/src/en/kickassanime/AndroidManifest.xml new file mode 100644 index 000000000..acb4de356 --- /dev/null +++ b/src/en/kickassanime/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/en/kickassanime/build.gradle b/src/en/kickassanime/build.gradle new file mode 100644 index 000000000..8d3088578 --- /dev/null +++ b/src/en/kickassanime/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'KickAssAnime' + pkgNameSuffix = 'en.kickassanime' + extClass = '.KickAssAnime' + extVersionCode = 1 + libVersion = '13' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/kickassanime/res/mipmap-hdpi/ic_launcher.png b/src/en/kickassanime/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..530ac20b7 Binary files /dev/null and b/src/en/kickassanime/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/kickassanime/res/mipmap-mdpi/ic_launcher.png b/src/en/kickassanime/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..13c03bea4 Binary files /dev/null and b/src/en/kickassanime/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/kickassanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/kickassanime/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..a21923eba Binary files /dev/null and b/src/en/kickassanime/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/kickassanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/kickassanime/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..6e2555103 Binary files /dev/null and b/src/en/kickassanime/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/kickassanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/kickassanime/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..5458d4434 Binary files /dev/null and b/src/en/kickassanime/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/kickassanime/res/web_hi_res_512.png b/src/en/kickassanime/res/web_hi_res_512.png new file mode 100644 index 000000000..0c3866109 Binary files /dev/null and b/src/en/kickassanime/res/web_hi_res_512.png differ diff --git a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/JSONUtil.java b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/JSONUtil.java new file mode 100644 index 000000000..11d506f47 --- /dev/null +++ b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/JSONUtil.java @@ -0,0 +1,88 @@ +package eu.kanade.tachiyomi.animeextension.en.kickassanime; + +public class JSONUtil { + public static String escape(String input) { + StringBuilder output = new StringBuilder(); + + for(int i=0; i= 0x10000) { + assert false : "Java stores as u16, so it should never give us a character that's bigger than 2 bytes. It literally can't."; + } else if(chx > 127) { + output.append(String.format("\\u%04x", chx)); + } else { + output.append(ch); + } + } + + return output.toString(); + } + + public static String unescape(String input) { + StringBuilder builder = new StringBuilder(); + + int i = 0; + while (i < input.length()) { + char delimiter = input.charAt(i); i++; // consume letter or backslash + + if(delimiter == '\\' && i < input.length()) { + + // consume first after backslash + char ch = input.charAt(i); i++; + + if(ch == '\\' || ch == '/' || ch == '"' || ch == '\'') { + builder.append(ch); + } + else if(ch == 'n') builder.append('\n'); + else if(ch == 'r') builder.append('\r'); + else if(ch == 't') builder.append('\t'); + else if(ch == 'b') builder.append('\b'); + else if(ch == 'f') builder.append('\f'); + else if(ch == 'u') { + + StringBuilder hex = new StringBuilder(); + + // expect 4 digits + if (i+4 > input.length()) { + throw new RuntimeException("Not enough unicode digits! "); + } + for (char x : input.substring(i, i + 4).toCharArray()) { + if(!Character.isLetterOrDigit(x)) { + throw new RuntimeException("Bad character in unicode escape."); + } + hex.append(Character.toLowerCase(x)); + } + i+=4; // consume those four digits. + + int code = Integer.parseInt(hex.toString(), 16); + builder.append((char) code); + } else { + throw new RuntimeException("Illegal escape sequence: \\"+ch); + } + } else { // it's not a backslash, or it's the last character. + builder.append(delimiter); + } + } + + return builder.toString(); + } +} diff --git a/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt new file mode 100644 index 000000000..dd2566523 --- /dev/null +++ b/src/en/kickassanime/src/eu/kanade/tachiyomi/animeextension/en/kickassanime/KickAssAnime.kt @@ -0,0 +1,326 @@ +package eu.kanade.tachiyomi.animeextension.en.kickassanime + +import android.app.Application +import android.content.SharedPreferences +import android.net.Uri +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Track +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.float +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.util.regex.Pattern + +@ExperimentalSerializationApi +class KickAssAnime : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "KickAssAnime" + + override val baseUrl by lazy { preferences.getString("preferred_domain", "https://www2.kickassanime.ro")!! } + + override val lang = "en" + + override val supportsLatest = false + + override val client: OkHttpClient = network.cloudflareClient + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/api/get_anime_list/all/$page") + + override fun popularAnimeParse(response: Response): AnimesPage { + val responseObject = json.decodeFromString(response.body!!.string()) + val data = responseObject["data"]!!.jsonArray + val animes = data.map { item -> + SAnime.create().apply { + setUrlWithoutDomain(item.jsonObject["slug"]!!.jsonPrimitive.content.substringBefore("/episode")) + thumbnail_url = "$baseUrl/uploads/" + item.jsonObject["poster"]!!.jsonPrimitive.content + title = item.jsonObject["name"]!!.jsonPrimitive.content + } + } + return AnimesPage(animes, true) + } + + override fun episodeListParse(response: Response): List { + val data = getAppdata(response.asJsoup()) + val anime = data["anime"]!!.jsonObject + val episodeList = anime["episodes"]!!.jsonArray + return episodeList.map { item -> + SEpisode.create().apply { + url = item.jsonObject["slug"]!!.jsonPrimitive.content + episode_number = item.jsonObject["num"]!!.jsonPrimitive.float + name = item.jsonObject["epnum"]!!.jsonPrimitive.content + } + } + } + + override fun latestUpdatesParse(response: Response) = throw Exception("not used") + + override fun latestUpdatesRequest(page: Int) = throw Exception("not used") + + override fun videoListParse(response: Response): List