Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,17 @@
ext {
extName = 'LegionAnime'
extClass = '.LegionAnime'
extVersionCode = 31
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:uqload-extractor'))
implementation(project(':lib:mp4upload-extractor'))
implementation(project(':lib:yourupload-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:dood-extractor'))
implementation(project(':lib:streamwish-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1,479 @@
package eu.kanade.tachiyomi.animeextension.es.legionanime
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.legionanime.extractors.JkanimeExtractor
import eu.kanade.tachiyomi.animeextension.es.legionanime.extractors.MediaFireExtractor
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
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.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
import java.text.SimpleDateFormat
import java.util.Locale
class LegionAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "LegionAnime"
override val baseUrl = "https://legionanime.club/api"
override val lang = "es"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val headers1 = headersBuilder().add("json", JSON_STRING).add("User-Agent", "android l3gi0n4N1mE %E6%9C%AC%E7%89%A9").build()
override fun animeDetailsRequest(anime: SAnime): Request = episodeListRequest(anime)
override fun animeDetailsParse(document: Document): SAnime {
val jsonResponse = json.decodeFromString<JsonObject>(document.body().text())["response"]!!.jsonObject
val anime = jsonResponse["anime"]!!.jsonObject
val studioId = anime["studios"]!!.jsonPrimitive.content.split(",")
val studio = try { studioId.map { id -> STUDIOS_MAP.filter { it.value == id.toInt() }.keys.first() } } catch (e: Exception) { emptyList() }
val malid = anime["mal_id"]!!.jsonPrimitive.content
var thumb: String? = null
try {
val jikanResponse = client.newCall(GET("https://api.jikan.moe/v4/anime/$malid")).execute().asJsoup().body().text()
val jikanJson = json.decodeFromString<JsonObject>(jikanResponse)
val pictures = jikanJson["data"]!!.jsonObject["images"]!!.jsonObject["jpg"]!!.jsonObject
thumb = pictures["large_image_url"]!!.jsonPrimitive.content
} catch (_: Exception) {
// ignore
}
return SAnime.create().apply {
title = anime["name"]!!.jsonPrimitive.content
if (thumb != null) {
thumbnail_url = thumb
}
description = anime["synopsis"]!!.jsonPrimitive.content
genre = anime["genres"]!!.jsonPrimitive.content
author = studio.joinToString { it.toString() }
status = when (anime["status"]!!.jsonPrimitive.content) {
"En emisión" -> SAnime.ONGOING
"Finalizado" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
}
override fun episodeListRequest(anime: SAnime): Request = GET(anime.url, headers1)
override fun episodeListParse(response: Response): List<SEpisode> {
val jsonResponse = json.decodeFromString<JsonObject>(response.asJsoup().body().text())
val episodes = jsonResponse["response"]!!.jsonObject["episodes"]!!.jsonArray
return episodes.map {
SEpisode.create().apply {
name = "Episodio " + it.jsonObject["name"]!!.jsonPrimitive.content
url = "$baseUrl/v2/episode_links/${it.jsonObject["id"]!!.jsonPrimitive.content}"
date_upload = parseDate(it.jsonObject["release_date"]!!.jsonPrimitive.content)
}
}
}
override fun latestUpdatesRequest(page: Int): Request {
val body = FormBody.Builder().add("apyki", API_KEY).build()
return POST(
"$baseUrl/v2/directories?studio=0&not_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", API_KEY).build()
return POST(
"$baseUrl/v2/directories?studio=0&not_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<JsonObject>(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 requestBody = FormBody.Builder().add("apyki", API_KEY).build()
val genreFilter = filters.getTagFilter()?.state ?: emptyList()
val excludeGenreFilter = filters.getExcludeTagFilter()?.state ?: emptyList()
val studioFilter = filters.getStudioFilter()?.state ?: emptyList()
val stateFilter = filters.getStateFilter() ?: StateFilter()
val orderByFilter = filters.getOrderByFilter() ?: OrderByFilter()
val genre = genreFilter.filter { it.state }
.map { GENRES[it.name] }
.joinToString("%2C") { it.toString() }
.takeIf { it.isNotEmpty() } ?: ""
val excludeGenre = excludeGenreFilter.filter { it.state }
.map { GENRES[it.name] }
.joinToString("%2C") { it.toString() }
.takeIf { it.isNotEmpty() } ?: ""
val studio = studioFilter.filter { it.state }
.map { STUDIOS_MAP[it.name] }
.joinToString("%2C") { it.toString() }
.takeIf { it.isNotEmpty() } ?: "0"
val status = stateFilter.toUriPart()
val orderBy = orderByFilter.toUriPart()
val url = buildAnimeSearchUrl(query, page, genre, orderBy, excludeGenre, studio, status)
return POST(
url,
headers = headers1,
body = requestBody,
)
}
private fun AnimeFilterList.getTagFilter() = find { it is TagFilter } as? TagFilter
private fun AnimeFilterList.getExcludeTagFilter() = find { it is ExcludeTagFilter } as? ExcludeTagFilter
private fun AnimeFilterList.getStudioFilter() = find { it is StudioFilter } as? StudioFilter
private fun AnimeFilterList.getStateFilter() = find { it is StateFilter } as? StateFilter
private fun AnimeFilterList.getOrderByFilter() = find { it is OrderByFilter } as? OrderByFilter
private fun buildAnimeSearchUrl(
query: String,
page: Int,
genre: String?,
orderBy: String?,
excludeGenre: String?,
studio: String,
status: String?,
): String {
val itemsPerPage = 24
return "$baseUrl/v2/directories?" +
"studio=$studio&" +
"not_genre=$excludeGenre&" +
"year=&" +
"orderBy=$orderBy&" +
"language=&" +
"type=&" +
"duration=&" +
"search=$query&" +
"letter=0&" +
"limit=$itemsPerPage&" +
"genre=$genre&" +
"season=&" +
"page=${(page - 1) * itemsPerPage}&" +
"status=$status"
}
override fun searchAnimeParse(response: Response): AnimesPage {
val responseJson = json.decodeFromString<JsonObject>(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
}
},
false,
)
} catch (e: Exception) {
return AnimesPage(emptyList(), false)
}
}
override fun videoListRequest(episode: SEpisode): Request {
val body = FormBody.Builder().add("apyki", API_KEY).build()
return POST(
episode.url,
headers1,
body,
)
}
override fun videoListParse(response: Response): List<Video> {
val jsonResponse = json.decodeFromString<JsonObject>(response.asJsoup().body().text())
val responseArray = jsonResponse["response"]!!.jsonObject
val players = responseArray["players"]!!.jsonArray
val videoList = mutableListOf<Video>()
players.forEach {
val server = it.jsonObject["option"]!!.jsonPrimitive.content
val preUrl = it.jsonObject["name"]!!.jsonPrimitive.content
val url = if (preUrl.startsWith("F-")) {
preUrl.substringAfter("-")
} else {
preUrl.substringAfter("-").reversed()
}
videoList.addAll(parseExtractors(url, server))
}
return videoList.filter { it.url.contains("http") }
}
private fun parseExtractors(url: String, server: String): List<Video> {
return when {
url.contains("streamwish") -> StreamWishExtractor(client, headers).videosFromUrl(url, prefix = "StreamWish")
url.contains("mediafire") -> {
val video = MediaFireExtractor(client).getVideoFromUrl(url, server)
if (video != null) {
listOf(video)
} else {
emptyList()
}
}
url.contains("streamtape") -> {
val video = StreamTapeExtractor(client).videoFromUrl(url, server)
if (video != null) {
listOf(video)
} else {
emptyList()
}
}
url.contains("jkanime") -> {
val video = JkanimeExtractor(client).getDesuFromUrl(url)
if (video != null) {
listOf(video)
} else {
emptyList()
}
}
url.contains("/stream/amz.php?") -> {
try {
val video = JkanimeExtractor(client).amazonExtractor(url)
if (video.isNotBlank()) {
listOf(Video(video, server, video))
} else {
emptyList()
}
} catch (_: Exception) {
emptyList()
}
}
url.contains("yourupload") -> {
YourUploadExtractor(client).videoFromUrl(url, headers)
}
url.contains("mp4upload") -> {
Mp4uploadExtractor(client).videosFromUrl(url, headers)
}
url.contains("dood") -> {
try {
val video = DoodExtractor(client).videoFromUrl(url)
if (video != null) {
listOf(video)
} else {
emptyList()
}
} catch (_: Exception) {
emptyList()
}
}
url.contains("ok.ru") -> {
OkruExtractor(client).videosFromUrl(url)
}
url.contains("flvvideo") && (url.endsWith(".m3u8") || url.endsWith(".mp4")) -> {
if (url.contains("http")) {
listOf(Video(url, "VideoFLV", url))
} else {
emptyList()
}
}
url.contains("cdnlat4animecen") && (url.endsWith(".class") || url.endsWith(".m3u8") || url.endsWith(".mp4")) -> {
if (url.contains("http")) {
listOf(Video(url, "AnimeCen", url))
} else {
emptyList()
}
}
url.contains("uqload") -> {
UqloadExtractor(client).videosFromUrl(url)
}
else -> emptyList()
}
}
/* --FilterStuff-- */
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
TagFilter("Generos", checkboxesFrom(GENRES)),
OrderByFilter(),
StateFilter(),
StudioFilter("Estudio", checkboxesFrom(STUDIOS_MAP)),
ExcludeTagFilter("Excluir Genero", checkboxesFrom(GENRES)),
)
private class StateFilter : UriPartFilter(
"Estado",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Emision", "1"),
Pair("Finalizado", "2"),
Pair("Proximamente", "3"),
),
)
private class OrderByFilter : UriPartFilter(
"Ordenar Por",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Fecha (Menor a Mayor)", "0"),
Pair("Recientemente vistos por otros", "1"),
Pair("Fecha (Mayor a Menor)", "2"),
Pair("A-Z", "3"),
Pair("Más Visitado", "4"),
Pair("Z-A", "5"),
Pair("Mejor Calificación", "6"),
Pair("Peor Calificación", "7"),
Pair("Últimos Agregados en app", "8"),
Pair("Primeros Agregados en app", "9"),
),
)
class TagCheckBox(tag: String) : AnimeFilter.CheckBox(tag, false)
private fun checkboxesFrom(tagArray: Map<String, Int>): List<TagCheckBox> = tagArray.map { TagCheckBox(it.key) }
class TagFilter(name: String, checkBoxes: List<TagCheckBox>) : AnimeFilter.Group<TagCheckBox>(name, checkBoxes)
class StudioFilter(name: String, checkBoxes: List<TagCheckBox>) : AnimeFilter.Group<TagCheckBox>(name, checkBoxes)
class ExcludeTagFilter(name: String, checkBoxes: List<TagCheckBox>) : AnimeFilter.Group<TagCheckBox>(name, checkBoxes)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", // Okru
"Xtreme S", "Nozomi", "Desu", "F1S-TAPE", "F1NIX", // video servers without resolution
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Desu")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(videoQualityPref)
}
private fun List<Video>.sortIfContains(item: String): List<Video> {
val newList = mutableListOf<Video>()
for (video in this) {
if (item in video.quality) {
newList.add(0, video)
} else {
newList.add(video)
}
}
return newList
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "desu")!!
return sortIfContains(quality)
}
private fun parseDate(dateStr: String): Long {
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
.getOrNull() ?: 0L
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH)
}
}
/* --Unused stuff-- */
override fun popularAnimeSelector(): String = throw UnsupportedOperationException()
override fun popularAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun popularAnimeNextPageSelector(): String = throw UnsupportedOperationException()
override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException()
override fun videoListSelector(): String = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException()
override fun searchAnimeSelector(): String = throw UnsupportedOperationException()
override fun searchAnimeFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun searchAnimeNextPageSelector(): String = throw UnsupportedOperationException()
override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
override fun episodeListSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
}

View file

@ -0,0 +1,465 @@
package eu.kanade.tachiyomi.animeextension.es.legionanime
val STUDIOS_MAP = mapOf(
"TMS Entertainment" to 4,
"TNK" to 39,
"Toei Animation" to 1,
"Toho Interactive Animation" to 261,
"Tokyo Kids" to 96,
"Tokyo Movie Shinsha" to 3,
"Tomason" to 329,
"Tomovies" to 164,
"Topcraft" to 293,
"Trans Arts" to 105,
"Tri-Slash" to 270,
"Triangle Staff" to 219,
"TriF Studio" to 239,
"Trigger" to 87,
"Trinet Entertainment" to 283,
"TROYCA" to 120,
"Tsuchida Productions" to 192,
"TV Tokyo" to 19,
"Twilight Studio" to 183,
"TYO Animations" to 166,
"Typhoon Graphics" to 232,
"ufotable" to 47,
"Urban Product" to 186,
"UWAN Pictures" to 187,
"Vega Entertainment" to 274,
"View Works" to 248,
"Visual Flight" to 339,
"Viz Media" to 8,
"W-Toon Studio" to 37,
"WAO World" to 176,
"Wawayu Animation" to 389,
"Welz Animation Studios" to 273,
"White Fox" to 107,
"Wit Studio" to 23,
"Wolfsbane" to 136,
"Xebec" to 25,
"XFLAG" to 313,
"Yamato Works" to 299,
"Yaoyorozu" to 256,
"Yokohama Animation Lab" to 84,
"Yostar Pictures" to 343,
"Yumeta Company" to 173,
"Zero-G" to 132,
"Zero-G Room" to 325,
"Zexcs" to 83,
"Lay-duce" to 56,
"Lerche" to 40,
"Lesprit" to 158,
"LEVELS" to 337,
"LICO" to 378,
"Lide" to 212,
"LIDENFILMS" to 43,
"LIDENFILMS Kyoto Studio" to 361,
"Life Work" to 322,
"Lilix" to 225,
"LMD" to 336,
"M.S.C" to 72,
"Madhouse" to 13,
"Magia Doraglier" to 99,
"Magic Bus" to 14,
"Maho Film" to 182,
"Manglobe" to 85,
"MAPPA" to 67,
"Marine Entertainment" to 236,
"Marvy Jack" to 237,
"Marza Animation Planet" to 80,
"MASTER LIGHTS" to 216,
"Millepensee" to 150,
"Minami Machi Bugyousho" to 318,
"monofilmo" to 54,
"MooGoo" to 234,
"Mushi Production" to 26,
"Nakamura Production" to 311,
"Namu Animation" to 199,
"NAZ" to 82,
"Next Media Animation" to 326,
"Nexus" to 127,
"NHK" to 334,
"Nickelodeon Animation Studio" to 62,
"Nihon Ad Systems" to 207,
"Nippon Animation" to 36,
"Nomad" to 155,
"Nut" to 108,
"Office DCI" to 317,
"Office Nobu" to 324,
"Oh! Production" to 310,
"Okuruto Noboru" to 221,
"OLM" to 65,
"OLM Team Yoshioka" to 368,
"Orange" to 59,
"Ordet" to 250,
"OZ" to 367,
"P.A. Works" to 6,
"P.I.C.S." to 230,
"Palm Studio" to 290,
"Passione" to 113,
"Pastel" to 215,
"Picture Magic" to 279,
"Pie in the sky" to 321,
"Pierrot" to 393,
"Pierrot Plus" to 79,
"Pine Jam" to 81,
"Planet" to 386,
"Planet Cartoon" to 394,
"Platinum Vision" to 91,
"Plum" to 259,
"Polygon Pictures" to 88,
"PRA" to 181,
"Primastea" to 245,
"Production +h." to 373,
"production doA" to 188,
"Production GoodBook" to 227,
"Production I.G" to 64,
"Production IMS" to 116,
"Production Reed" to 18,
"Project No.9" to 30,
"Purple Cow Studio Japan" to 135,
"Quad" to 364,
"Qualia Animation" to 217,
"Qubic Pictures" to 347,
"Quebico" to 350,
"Radix" to 235,
"Red Dog Culture House" to 379,
"Remic" to 304,
"Revoroot" to 209,
"Rising Force" to 291,
"Robot Communications" to 277,
"Saetta" to 205,
"Sanrio" to 333,
"SANZIGEN" to 122,
"Satelight" to 51,
"Science SARU" to 157,
"Scooter Films" to 381,
"Seven" to 133,
"Seven Arcs" to 115,
"Seven Arcs Pictures" to 102,
"Seven Stone Entertainment" to 359,
"Shaft" to 15,
"Shanghai Foch Film and TV Culture Investment" to 352,
"Shanghai Foch Film Culture Investment" to 332,
"Shenying Animation" to 358,
"Shin-Ei Animation" to 57,
"Shinkuukan" to 316,
"Shirogumi" to 92,
"Shogakukan Music & Digital Entertainment" to 231,
"Shuka" to 167,
"Signal.MD" to 61,
"Silver" to 124,
"Silver Link." to 31,
"Sola Digital Arts" to 148,
"Space Neko Company" to 342,
"Sparkly Key Animation Studio" to 123,
"Sparky Animation" to 320,
"Staple Entertainment" to 388,
"Stingray" to 268,
"Studio 3Hz" to 137,
"Studio 4°C" to 241,
"Studio A-CAT" to 146,
"Studio Animal" to 276,
"Studio Bind" to 338,
"Studio Blanc" to 109,
"Studio Chizu" to 251,
"Studio Colorido" to 119,
"Studio Comet" to 144,
"Studio Crocodile" to 68,
"Studio Daisy" to 346,
"Studio Deen" to 93,
"Studio elle" to 142,
"Studio Fantasia" to 233,
"Studio Flad" to 77,
"Studio Flag" to 278,
"Studio Ghibli" to 44,
"Studio Gokumi" to 111,
"Studio Hibari" to 49,
"Studio Junio" to 218,
"Studio Kafka" to 353,
"Studio Kai" to 260,
"Studio Kelmadick" to 309,
"Studio Kyuuma" to 284,
"Studio LAN" to 243,
"Studio Lings" to 197,
"Studio Live" to 178,
"Studio M2" to 327,
"Studio Matrix" to 152,
"Studio Moriken" to 35,
"studio MOTHER" to 369,
"Studio Palette" to 356,
"Studio Pierrot" to 11,
"Studio Ponoc" to 264,
"Studio PuYUKAI" to 117,
"Studio Rikka" to 134,
"Studio Signpost" to 238,
"Studio VOLN" to 94,
"Studio W.Baba" to 229,
"Studio! Cucuri" to 288,
"Sublimation" to 253,
"Sunrise" to 45,
"Sunrise Beyond" to 46,
"Super Normal Studio" to 267,
"Synergy Japan" to 28,
"SynergySP" to 7,
"Tatsunoko Production" to 17,
"Team TillDawn" to 269,
"Team YokkyuFuman" to 240,
"teamKG" to 101,
"Tear Studio" to 118,
"Telecom Animation Film" to 194,
"Tengu Kobo" to 319,
"Tezuka Productions" to 110,
"The Answer Studio" to 331,
"Think Corporation" to 297,
"Thundray" to 154,
"Todos" to 0,
"3xCube" to 41,
"8bit" to 90,
"A-1 Pictures" to 9,
"A-Real" to 201,
"A.C.G.T." to 55,
"Acca effe" to 210,
"Actas" to 163,
"Agent 21" to 383,
"AIC" to 24,
"AIC A.S.T.A." to 97,
"AIC Build" to 121,
"AIC Classic" to 159,
"AIC Frontier" to 305,
"AIC Plus+" to 98,
"AIC Spirits" to 200,
"Ajia-Do" to 76,
"Akatsuki" to 372,
"Albacrow" to 147,
"Amuse" to 315,
"Anima" to 306,
"Anima&Co." to 203,
"Animaruya" to 314,
"Animation Do" to 73,
"Annapuru" to 298,
"Anpro" to 294,
"APPP" to 50,
"AQUA ARIS" to 189,
"ARECT" to 360,
"Arms" to 33,
"Artland" to 12,
"Artmic" to 285,
"Arvo Animation" to 125,
"Asahi Production" to 202,
"Ascension" to 289,
"ASK Animation Studio" to 385,
"asread." to 86,
"AtelierPontdarc" to 357,
"AXsiZ" to 131,
"B.CMAY PICTURES" to 184,
"B&T" to 275,
"Bakken Record" to 161,
"Bandai Namco Pictures" to 126,
"Barnum Studio" to 172,
"Bee Media" to 295,
"Bee Train" to 162,
"Beijing Rocen Digital" to 246,
"BeSTACK" to 106,
"Bibury Animation CG" to 374,
"Bibury Animation Studios" to 196,
"BigFireBird Animation" to 104,
"Blade" to 153,
"Bones" to 22,
"Bouncy" to 249,
"Brain's Base" to 53,
"Bridge" to 29,
"Brio Animation" to 280,
"Buemon" to 226,
"C-Station" to 156,
"C2C" to 145,
"CANDY BOX" to 344,
"CGCG Studio" to 348,
"Chaos Project" to 100,
"Charaction" to 296,
"Children's Playground Entertainment" to 78,
"CLAP" to 371,
"CloverWorks" to 52,
"Colored Pencil Animation" to 351,
"CoMix Wave Films" to 69,
"Connect" to 32,
"Craftar Studios" to 223,
"Creators in Pack" to 138,
"CyberConnect2" to 312,
"Cyclone Graphics" to 391,
"CygamesPictures" to 149,
"DandeLion Animation Studio" to 220,
"Daume" to 143,
"David Production" to 2,
"Dentsu" to 20,
"Digital Frontier" to 160,
"Digital Network Animation" to 363,
"Diomedea" to 10,
"DLE" to 140,
"DMM pictures" to 262,
"DMM.futureworks" to 38,
"Doga Kobo" to 58,
"domerica" to 345,
"Dongwoo A&E" to 206,
"DR Movie" to 376,
"DRAWIZ" to 349,
"Drive" to 66,
"drop" to 307,
"Dwango" to 252,
"dwarf" to 272,
"Dynamo Pictures" to 247,
"E&G Films" to 254,
"Egg Firm" to 128,
"EKACHI EPILKA" to 169,
"Emon" to 175,
"EMT Squared" to 95,
"Encourage Films" to 168,
"ENGI" to 129,
"Ezόla" to 130,
"Fanworks" to 71,
"feel." to 21,
"Felix Film" to 174,
"Fever Creations" to 395,
"Fifth Avenue" to 265,
"Flat Studio" to 382,
"Front Line" to 301,
"Fuji TV" to 287,
"G-angle" to 228,
"G&G Entertainment" to 286,
"Gaina" to 112,
"Gainax" to 16,
"Gainax Kyoto" to 224,
"Gallop" to 60,
"Gambit" to 354,
"Gathering" to 213,
"GEEK TOYS" to 366,
"GEEKTOYS" to 114,
"GEMBA" to 151,
"Genco" to 308,
"Geno Studio" to 171,
"GIFTanimation" to 355,
"Giga Production" to 211,
"Ginga Ya" to 282,
"GoHands" to 89,
"Gonzo" to 34,
"Gosay Studio" to 244,
"Graphinica" to 165,
"GRIZZLY" to 384,
"Group TAC" to 185,
"Grouper Productions" to 302,
"Hal Film Maker" to 195,
"Haoliners Animation League" to 139,
"Hayabusa Film" to 377,
"Hoods Drifters Studio" to 190,
"Hoods Entertainment" to 63,
"HORNETS" to 375,
"Hotline" to 328,
"HOTZIPANG" to 70,
"HS Pictures Studio" to 281,
"Husio Studio" to 242,
"I.Gzwei" to 300,
"iDRAGONS Creative Studio" to 208,
"ILCA" to 27,
"Imagica Lab." to 204,
"Imagin" to 177,
"Imagineer" to 191,
"Indivision" to 362,
"Ishimori Entertainment" to 263,
"Issen" to 222,
"ixtl" to 42,
"Iyasakadou Film" to 330,
"J.C.Staff" to 5,
"Japan Vistec" to 303,
"Jinnan Studio" to 370,
"Jinnis Animation Studios" to 271,
"Jumondo" to 380,
"Jumonji" to 257,
"Kachidoki Studio" to 335,
"Kamikaze Douga" to 170,
"Karaku" to 258,
"Kazami Gakuen Koushiki Douga-bu" to 266,
"Kenji Studio" to 390,
"Khara" to 179,
"Kigumi" to 365,
"Kinema Citrus" to 103,
"Kitty Films" to 214,
"KJJ Animation" to 392,
"Kung Fu Frog Animation" to 387,
"Kyoto Animation" to 48,
"Kyotoma" to 323,
"l-a-unch・BOX" to 141,
"L²Studio" to 180,
"LandQ studios" to 193,
"Lapin Track" to 198,
"Larx Entertainment" to 74,
)
val GENRES = mapOf(
"Buscar exactamente con géneros seleccionados" to 100,
"Acción" to 1,
"Artes Marciales" to 11,
"Aventuras" to 21,
"Carreras" to 31,
"Ciencia Ficción" to 2,
"Comedia" to 12,
"Demencia" to 22,
"Demonios" to 32,
"Deportes" to 3,
"Donghua" to 45,
"Drama" to 13,
"Ecchi" to 23,
"Escolares" to 33,
"Espacial" to 4,
"Fantasía" to 14,
"Harem" to 24,
"Historico" to 34,
"Infantil" to 5,
"Isekai" to 43,
"Josei" to 15,
"Juegos" to 25,
"Latino y Castellano" to 41,
"Magia" to 35,
"Mecha" to 6,
"Militar" to 16,
"Misterio" to 26,
"Música" to 36,
"Parodia" to 7,
"Policía" to 17,
"Psicológico" to 27,
"Recuentos de la vida" to 37,
"Romance" to 8,
"Samurai" to 18,
"Seinen" to 28,
"Shoujo" to 38,
"Shounen" to 9,
"Sobrenatural" to 19,
"Superpoderes" to 29,
"Suspenso" to 39,
"Terror / Gore" to 10,
"Vampiros" to 20,
"Yaoi" to 30,
"Yuri" to 40,
)
val ORDER_BY = arrayOf(
Pair("Fecha (Menor a Mayor)", "0"),
Pair("Recientemente vistos por otros", "1"),
Pair("Fecha (Mayor a Menor)", "2"),
Pair("A-Z", "3"),
Pair("Más Visitado", "4"),
Pair("Z-A", "5"),
Pair("Mejor Calificación", "6"),
Pair("Peor Calificación", "7"),
Pair("Últimos Agregados en app", "8"),
Pair("Primeros Agregados en app", "9"),
)
// this is supposed to be information about the device, it is needed to make any api calls, this could probably be randomized
// Apparently there is a section called "package_version" that needs to be updated periodically bruh.
val JSON_STRING = "{\"mob3\":\"wj2fea7esGZ44ef\",\"mob\":\"ca-app-pub-8704883736496335~2640452466\",\"mob2\":\"ca-app-pub-8704883736496335/7509635763\",\"laltx\":\"ca-app-pub-7457591504273346/96408526573970637unleinunleba\",\"language\":\"es\",\"isDeb\":false,\"mobx\":\"ca-app-pub-7457591504273346~4714978974\",\"loadLvl\":1,\"code_name\":\"emu64xa\",\"vcode\":\"2.0.2.6\",\"platform\":\"13\",\"lalt\":\"ca-app-pub-8704883736496335/71217888083970637unleinunleba\",\"kind_device\":\"0\",\"manufacturer\":\"Google\",\"som\":\"android\",\"device_name\":\"Sdk_gphone64_x86_64\",\"player_id\":\"50dfea02-9f24-4116-902b-a726146da421\",\"mobf\":\"ca-app-pub-8704883736496335/2195575817\",\"ipv6\":\"FE80::6898:34FF:FE32:E13E\",\"root\":\"0\",\"eth\":\"02:00:00:00:00:00\",\"tel\":\"XXX-XXX-XXX\",\"UUID\":\"00000000-0001-a657-0001-aad30001b11d\",\"yek\":\"bqUgI4l339bqQbnz\",\"moned\":false,\"lvl_sign\":1,\"mobfx\":\"ca-app-pub-7457591504273346/1377852271\",\"device_id\":\"\",\"orp\":false,\"modelo\":\"sdk_gphone64_x86_64\",\"market_name\":\"Sdk_gphone64_x86_64\",\"token\":\"es\",\"isSign\":true,\"malt\":\"ca-app-pub-8704883736496335/7121788808\",\"maltx\":\"ca-app-pub-7457591504273346/9640852657\",\"api_lvl\":\"33\",\"package_version\":\"50\",\"kind_release\":0,\"ipLocal\":\"10.0.2.15\",\"mob2x\":\"ca-app-pub-7457591504273346/9640852657\",\"wlan\":\"02:00:00:00:00:00\",\"inmo\":\"a3c41c881e7f4bc982db32a889eb9e57\",\"inst\":\"\",\"package_name\":\"aplicaciones.paleta.legionanimefull\",\"androidID\":\"b70a95e4fda18f3c\"}"
// this is supposed to be the api key but apparently it is not necessary, anyway I add it to avoid any errors
val API_KEY = "pM7VYr2bBG2plWQp"
// these are the urls used for the thumbnails, apparently it doesn't matter which one to use but it would be good to check the status of the request before assigning it to the anime
val AIP = arrayOf("https://la-space-4.sfo2.digitaloceanspaces.com/", "https://la-space-5.sfo2.digitaloceanspaces.com/", "https://la-space-4.sfo2.digitaloceanspaces.com/", "https://la-space-5.sfo2.digitaloceanspaces.com/", "https://la-space-4.sfo2.cdn.digitaloceanspaces.com/")

View file

@ -0,0 +1,57 @@
package eu.kanade.tachiyomi.animeextension.es.legionanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
class JkanimeExtractor(
private val client: OkHttpClient,
) {
fun getNozomiFromUrl(url: String, prefix: String = ""): Video? {
val dataKeyHeaders = Headers.Builder().add("Referer", url).build()
val doc = client.newCall(GET(url, dataKeyHeaders)).execute().asJsoup()
val dataKey = doc.select("form input[value]").attr("value")
val gsplayBody = "data=$dataKey".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull())
val location = client.newCall(POST("https://jkanime.net/gsplay/redirect_post.php", dataKeyHeaders, gsplayBody)).execute().request.url.toString()
val postKey = location.substringAfter("player.html#")
val nozomiBody = "v=$postKey".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull())
val nozomiResponse = client.newCall(POST("https://jkanime.net/gsplay/api.php", body = nozomiBody)).execute()
val nozomiUrl = JSONObject(nozomiResponse.body.string()).getString("file")
if (nozomiResponse.isSuccessful && nozomiUrl.isNotBlank()) {
return Video(nozomiUrl, "${prefix}Nozomi", nozomiUrl)
}
return null
}
fun getDesuFromUrl(url: String, prefix: String = ""): Video? {
val document = client.newCall(GET(url)).execute()
val script = document.asJsoup().selectFirst("script:containsData(var parts = {)")!!.data()
val streamUrl = script.substringAfter("url: '").substringBefore("'")
if (document.isSuccessful && streamUrl.isNotBlank()) {
return Video(streamUrl, "${prefix}Desu", streamUrl)
}
return null
}
fun amazonExtractor(url: String): String {
val document = client.newCall(GET(url.replace(".com", ".tv"))).execute().asJsoup()
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data()
.substringAfter("[{\"file\":\"")
.substringBefore("\",").replace("\\", "")
return try {
if (client.newCall(GET(videoURl)).execute().code == 200) videoURl else ""
} catch (_: Exception) {
""
}
}
}

View file

@ -0,0 +1,19 @@
package eu.kanade.tachiyomi.animeextension.es.legionanime.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class MediaFireExtractor(
private val client: OkHttpClient,
) {
fun getVideoFromUrl(url: String, prefix: String = ""): Video? {
val document = client.newCall(GET(url)).execute()
val downloadUrl = document.asJsoup().selectFirst("a#downloadButton")?.attr("href")
if (!downloadUrl.isNullOrBlank()) {
return Video(downloadUrl, "$prefix-MediaFire", downloadUrl)
}
return null
}
}