diff --git a/src/es/legionanime/AndroidManifest.xml b/src/es/legionanime/AndroidManifest.xml new file mode 100644 index 000000000..acb4de356 --- /dev/null +++ b/src/es/legionanime/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/es/legionanime/build.gradle b/src/es/legionanime/build.gradle new file mode 100644 index 000000000..53a95c1c5 --- /dev/null +++ b/src/es/legionanime/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'LegionAnime' + pkgNameSuffix = 'es.legionanime' + extClass = '.LegionAnime' + extVersionCode = 1 + libVersion = '13' +} + +dependencies { + implementation(project(':lib-fembed-extractor')) + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-okru-extractor')) + implementation(project(':lib-streamsb-extractor')) + implementation(project(':lib-dood-extractor')) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/legionanime/res/mipmap-hdpi/ic_launcher.png b/src/es/legionanime/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b9458eaf5 Binary files /dev/null and b/src/es/legionanime/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/legionanime/res/mipmap-mdpi/ic_launcher.png b/src/es/legionanime/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..237013518 Binary files /dev/null and b/src/es/legionanime/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/legionanime/res/mipmap-xhdpi/ic_launcher.png b/src/es/legionanime/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..770f8025e Binary files /dev/null and b/src/es/legionanime/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/legionanime/res/mipmap-xxhdpi/ic_launcher.png b/src/es/legionanime/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5bac2468 Binary files /dev/null and b/src/es/legionanime/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/legionanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/legionanime/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..1259ab2d2 Binary files /dev/null and b/src/es/legionanime/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/legionanime/src/eu/kanade/tachiyomi/animeextension/es/legionanime/LegionAnime.kt b/src/es/legionanime/src/eu/kanade/tachiyomi/animeextension/es/legionanime/LegionAnime.kt new file mode 100644 index 000000000..5400d8d40 --- /dev/null +++ b/src/es/legionanime/src/eu/kanade/tachiyomi/animeextension/es/legionanime/LegionAnime.kt @@ -0,0 +1,293 @@ +package eu.kanade.tachiyomi.animeextension.es.legionanime + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.es.legionanime.extractors.JkanimeExtractor +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +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.Video +import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.lib.fembedextractor.FembedExtractor +import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.FormBody +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy + +class LegionAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "LegionAnime" + + override val baseUrl = "https://legionanime.club/api" + + override val lang = "es" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private val headers1 = headersBuilder().add("json", jsonString).add("User-Agent", "android l3gi0n4N1mE %E6%9C%AC%E7%89%A9").build() + + override fun animeDetailsParse(document: Document): SAnime { + val jsonResponse = json.decodeFromString(document.body().text())["response"]!!.jsonObject + val anime = jsonResponse["anime"]!!.jsonObject + val studioId = anime["studios"]!! + val studio = studiosMap.filter { it.value == studioId.jsonPrimitive.content.toInt() }.keys.firstOrNull() + return SAnime.create().apply { + title = anime["name"]!!.jsonPrimitive.content + description = anime["synopsis"]!!.jsonPrimitive.content + genre = anime["genres"]!!.jsonPrimitive.content + author = studio + status = when (anime["status"]!!.jsonPrimitive.content) { + "En emisión" -> SAnime.ONGOING + "Finalizado" -> SAnime.COMPLETED + else -> SAnime.UNKNOWN + } + } + } + + override fun animeDetailsRequest(anime: SAnime): Request = episodeListRequest(anime) + + override fun episodeListParse(response: Response): List { + val jsonResponse = json.decodeFromString(response.asJsoup().body().text()) + val episodes = jsonResponse["response"]!!.jsonObject["episodes"]!!.jsonArray + + return episodes.map { + SEpisode.create().apply { + name = it.jsonObject["name"]!!.jsonPrimitive.content + url = "$baseUrl/v2/episode_links/${it.jsonObject["id"]!!.jsonPrimitive.content}" + } + } + } + + override fun episodeListRequest(anime: SAnime): Request = GET(anime.url, headers1) + + override fun latestUpdatesRequest(page: Int): Request { + val body = FormBody.Builder().add("apyki", apyki).build() + return POST( + "$baseUrl/v2/directories?studio=0¬_genre=&year=&orderBy=2&language=&type=&duration=&search=&letter=0&limit=24&genre=&season=&page=${(page - 1) * 24}&status=", + headers = headers1, body = body + ) + } + + override fun latestUpdatesParse(response: Response): AnimesPage = popularAnimeParse(response) + + override fun popularAnimeRequest(page: Int): Request { + val body = FormBody.Builder().add("apyki", apyki).build() + return POST( + "$baseUrl/v2/directories?studio=0¬_genre=&year=&orderBy=4&language=&type=&duration=&search=&letter=0&limit=24&genre=&season=&page=${(page - 1) * 24}&status=", + headers = headers1, body = body + ) + } + + override fun popularAnimeParse(response: Response): AnimesPage { + val responseJson = json.decodeFromString(response.asJsoup().body().text()) + try { + val animeArray = responseJson["response"]!!.jsonArray + return AnimesPage( + animeArray.map { + val animeDetail = it.jsonObject + val animeId = animeDetail["id"]!!.jsonPrimitive.content + SAnime.create().apply { + title = animeDetail["nombre"]!!.jsonPrimitive.content + url = "$baseUrl/v1/episodes/$animeId" + thumbnail_url = aip.random() + animeDetail["img_url"]!!.jsonPrimitive.content + } + }, + true + ) + } catch (e: Exception) { + return AnimesPage(emptyList(), false) + } + } + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val body = FormBody.Builder().add("apyki", apyki).build() + val genreFilter = (filters.find { it is TagFilter } as TagFilter).state + val excludeGenreFilter = (filters.find { it is ExcludeTagFilter } as ExcludeTagFilter).state + val studioFilter = (filters.find { it is StudioFilter } as StudioFilter).state + val stateFilter = (filters.find { it is StateFilter } as StateFilter) + + val genre = if (genreFilter.isNotEmpty()) { + genreFilter.filter { it.state }.map { genres[it.name] }.joinToString("%2C") { it.toString() } + } else { + "" + } + val excludeGenre = if (genreFilter.isNotEmpty()) { + excludeGenreFilter.filter { it.state }.map { genres[it.name] }.joinToString("%2C") { it.toString() } + } else { + "" + } + val studio = if (studioFilter.isNotEmpty()) { + studioFilter.filter { it.state }.map { studiosMap[it.name] }.joinToString("%2C") { it.toString() } + } else { + "0" + } + val status = if (stateFilter.state != 0) { + stateFilter.toUriPart() + } else { + "" + } + + val url = "$baseUrl/v2/directories?studio=$studio¬_genre=$excludeGenre&year=&orderBy=4&language=&type=&duration=&search=$query&letter=0&limit=24&genre=$genre&season=&page=${(page - 1) * 24}&status=$status" + + return POST( + url, + headers = headers1, body = body + ) + } + + override fun searchAnimeParse(response: Response): AnimesPage = popularAnimeParse(response) + + override fun videoListParse(response: Response): List