feat(multisrc/id): New source: AnimeIndo (#1986)
This commit is contained in:
@ -0,0 +1,8 @@
|
|||||||
|
dependencies {
|
||||||
|
implementation(project(":lib-mp4upload-extractor"))
|
||||||
|
implementation(project(":lib-streamsb-extractor"))
|
||||||
|
implementation(project(":lib-gdriveplayer-extractor"))
|
||||||
|
implementation(project(":lib-streamtape-extractor"))
|
||||||
|
implementation(project(":lib-yourupload-extractor"))
|
||||||
|
implementation(project(":lib-okru-extractor"))
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
134
multisrc/overrides/animestream/animeindo/src/AnimeIndo.kt
Normal file
134
multisrc/overrides/animestream/animeindo/src/AnimeIndo.kt
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.id.animeindo
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.GenresFilter
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.OrderFilter
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.SeasonFilter
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.StatusFilter
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.StudioFilter
|
||||||
|
import eu.kanade.tachiyomi.animeextension.id.animeindo.AnimeIndoFilters.TypeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
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.lib.gdriveplayerextractor.GdrivePlayerExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
class AnimeIndo : AnimeStream(
|
||||||
|
"id",
|
||||||
|
"AnimeIndo",
|
||||||
|
"https://animeindo.quest",
|
||||||
|
) {
|
||||||
|
override val animeListUrl = "$baseUrl/pages/animelist"
|
||||||
|
|
||||||
|
// ============================== Popular ===============================
|
||||||
|
override fun popularAnimeRequest(page: Int) = GET("$animeListUrl/page/$page/?order=popular")
|
||||||
|
|
||||||
|
// =============================== Latest ===============================
|
||||||
|
override fun latestUpdatesRequest(page: Int) = GET("$animeListUrl/page/$page/?order=update")
|
||||||
|
|
||||||
|
// =============================== Search ===============================
|
||||||
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
|
val params = AnimeIndoFilters.getSearchParameters(filters)
|
||||||
|
val multiString = buildString {
|
||||||
|
if (params.genres.isNotEmpty()) append(params.genres + "&")
|
||||||
|
if (params.seasons.isNotEmpty()) append(params.seasons + "&")
|
||||||
|
if (params.studios.isNotEmpty()) append(params.studios + "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET("$animeListUrl/page/$page/?title=$query&$multiString&status=${params.status}&type=${params.type}&order=${params.order}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchAnimeSelector() = "div.animepost > div > a"
|
||||||
|
|
||||||
|
override fun searchAnimeFromElement(element: Element) = SAnime.create().apply {
|
||||||
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
|
title = element.selectFirst("div.title")!!.text()
|
||||||
|
thumbnail_url = element.selectFirst("img")!!.getImageUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchAnimeNextPageSelector() = "div.pagination a:has(i#nextpagination)"
|
||||||
|
|
||||||
|
// ============================== Filters ===============================
|
||||||
|
override val filtersSelector = "div.filtersearch tbody > tr:not(:has(td.filter_title:contains(Search))) > td.filter_act"
|
||||||
|
|
||||||
|
override fun getFilterList(): AnimeFilterList {
|
||||||
|
return if (AnimeStreamFilters.filterInitialized()) {
|
||||||
|
AnimeFilterList(
|
||||||
|
OrderFilter(orderFilterText),
|
||||||
|
StatusFilter(statusFilterText),
|
||||||
|
TypeFilter(typeFilterText),
|
||||||
|
AnimeFilter.Separator(),
|
||||||
|
GenresFilter(genresFilterText),
|
||||||
|
SeasonFilter(seasonsFilterText),
|
||||||
|
StudioFilter(studioFilterText),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AnimeFilterList(AnimeFilter.Header(filtersMissingWarning))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== Anime Details ============================
|
||||||
|
override fun parseStatus(statusString: String?): Int {
|
||||||
|
return when (statusString?.trim()?.lowercase()) {
|
||||||
|
"finished airing" -> SAnime.COMPLETED
|
||||||
|
"currently airing" -> SAnime.ONGOING
|
||||||
|
else -> SAnime.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Episodes ==============================
|
||||||
|
override fun episodeListSelector() = "div.listeps li:has(.epsleft)"
|
||||||
|
|
||||||
|
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
|
||||||
|
val ahref = element.selectFirst("a")!!
|
||||||
|
setUrlWithoutDomain(ahref.attr("href"))
|
||||||
|
val num = ahref.text()
|
||||||
|
name = "Episode $num"
|
||||||
|
episode_number = num.trim().toFloatOrNull() ?: 0F
|
||||||
|
date_upload = element.selectFirst("span.date")?.text().toDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ Video Links =============================
|
||||||
|
private val streamSbExtractor by lazy { StreamSBExtractor(client) }
|
||||||
|
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||||
|
private val gdrivePlayerExtractor by lazy { GdrivePlayerExtractor(client) }
|
||||||
|
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||||
|
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
|
||||||
|
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||||
|
|
||||||
|
override fun getVideoList(url: String, name: String): List<Video> {
|
||||||
|
return with(name) {
|
||||||
|
when {
|
||||||
|
contains("streamsb") -> streamSbExtractor.videosFromUrl(url, headers)
|
||||||
|
contains("streamtape") -> streamTapeExtractor.videoFromUrl(url)?.let(::listOf).orEmpty()
|
||||||
|
contains("mp4") -> mp4uploadExtractor.videosFromUrl(url, headers)
|
||||||
|
contains("yourupload") -> yourUploadExtractor.videoFromUrl(url, headers)
|
||||||
|
url.contains("ok.ru") -> okruExtractor.videosFromUrl(url)
|
||||||
|
contains("gdrive") -> {
|
||||||
|
val gdriveUrl = when {
|
||||||
|
baseUrl in url -> "https:" + url.toHttpUrl().queryParameter("data")!!
|
||||||
|
else -> url
|
||||||
|
}
|
||||||
|
gdrivePlayerExtractor.videosFromUrl(gdriveUrl, "Gdrive", headers)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// just to detect video hosts easily
|
||||||
|
Log.i("AnimeIndo", "Unrecognized at getVideoList => Name -> $name || URL => $url")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.id.animeindo
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.CheckBoxFilterList
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.QueryPartFilter
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.asQueryPart
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.filterElements
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.filterInitialized
|
||||||
|
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStreamFilters.parseCheckbox
|
||||||
|
|
||||||
|
object AnimeIndoFilters {
|
||||||
|
internal class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
||||||
|
internal class SeasonFilter(name: String) : CheckBoxFilterList(name, SEASON_LIST)
|
||||||
|
internal class StudioFilter(name: String) : CheckBoxFilterList(name, STUDIO_LIST)
|
||||||
|
|
||||||
|
internal class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
|
||||||
|
internal class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
|
||||||
|
internal class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
|
||||||
|
|
||||||
|
internal data class FilterSearchParams(
|
||||||
|
val genres: String = "",
|
||||||
|
val seasons: String = "",
|
||||||
|
val studios: String = "",
|
||||||
|
val status: String = "",
|
||||||
|
val type: String = "",
|
||||||
|
val order: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||||
|
if (filters.isEmpty()) return FilterSearchParams()
|
||||||
|
if (!filterInitialized()) return FilterSearchParams()
|
||||||
|
|
||||||
|
return FilterSearchParams(
|
||||||
|
filters.parseCheckbox<GenresFilter>(GENRES_LIST, "genre"),
|
||||||
|
filters.parseCheckbox<SeasonFilter>(SEASON_LIST, "season"),
|
||||||
|
filters.parseCheckbox<StudioFilter>(STUDIO_LIST, "studio"),
|
||||||
|
filters.asQueryPart<StatusFilter>(),
|
||||||
|
filters.asQueryPart<TypeFilter>(),
|
||||||
|
filters.asQueryPart<OrderFilter>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPairListByIndex(index: Int) = filterElements.get(index)
|
||||||
|
.select("ul > li, td > label")
|
||||||
|
.map { element ->
|
||||||
|
val key = element.text()
|
||||||
|
val value = element.selectFirst("input")!!.attr("value")
|
||||||
|
Pair(key, value)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
private val ORDER_LIST by lazy { getPairListByIndex(0) }
|
||||||
|
private val STATUS_LIST by lazy { getPairListByIndex(1) }
|
||||||
|
private val TYPE_LIST by lazy { getPairListByIndex(2) }
|
||||||
|
private val GENRES_LIST by lazy { getPairListByIndex(3) }
|
||||||
|
private val SEASON_LIST by lazy { getPairListByIndex(4) }
|
||||||
|
private val STUDIO_LIST by lazy { getPairListByIndex(5) }
|
||||||
|
}
|
@ -20,15 +20,15 @@ object AnimeStreamFilters {
|
|||||||
|
|
||||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||||
|
|
||||||
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
||||||
return (getFirst<R>() as QueryPartFilter).toQueryPart()
|
return (getFirst<R>() as QueryPartFilter).toQueryPart()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||||
return first { it is R } as R
|
return first { it is R } as R
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||||
options: Array<Pair<String, String>>,
|
options: Array<Pair<String, String>>,
|
||||||
name: String,
|
name: String,
|
||||||
): String {
|
): String {
|
||||||
@ -39,16 +39,16 @@ object AnimeStreamFilters {
|
|||||||
.joinToString("&") { "$name[]=$it" }
|
.joinToString("&") { "$name[]=$it" }
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
internal class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
||||||
class SeasonFilter(name: String) : CheckBoxFilterList(name, SEASON_LIST)
|
internal class SeasonFilter(name: String) : CheckBoxFilterList(name, SEASON_LIST)
|
||||||
class StudioFilter(name: String) : CheckBoxFilterList(name, STUDIO_LIST)
|
internal class StudioFilter(name: String) : CheckBoxFilterList(name, STUDIO_LIST)
|
||||||
|
|
||||||
class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
|
internal class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
|
||||||
class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
|
internal class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
|
||||||
class SubFilter(name: String) : QueryPartFilter(name, SUB_LIST)
|
internal class SubFilter(name: String) : QueryPartFilter(name, SUB_LIST)
|
||||||
class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
|
internal class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
|
||||||
|
|
||||||
data class FilterSearchParams(
|
internal data class FilterSearchParams(
|
||||||
val genres: String = "",
|
val genres: String = "",
|
||||||
val seasons: String = "",
|
val seasons: String = "",
|
||||||
val studios: String = "",
|
val studios: String = "",
|
||||||
@ -72,9 +72,9 @@ object AnimeStreamFilters {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal lateinit var filterElements: Elements
|
lateinit var filterElements: Elements
|
||||||
|
|
||||||
internal fun filterInitialized() = ::filterElements.isInitialized
|
fun filterInitialized() = ::filterElements.isInitialized
|
||||||
|
|
||||||
private fun getPairListByIndex(index: Int) = filterElements.get(index)
|
private fun getPairListByIndex(index: Int) = filterElements.get(index)
|
||||||
.select("li")
|
.select("li")
|
||||||
|
@ -11,6 +11,7 @@ class AnimeStreamGenerator : ThemeSourceGenerator {
|
|||||||
override val baseVersionCode = 2
|
override val baseVersionCode = 2
|
||||||
|
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
|
SingleLang("AnimeIndo", "https://animeindo.quest", "id", isNsfw = false),
|
||||||
SingleLang("AnimeKhor", "https://animekhor.xyz", "en", isNsfw = false),
|
SingleLang("AnimeKhor", "https://animekhor.xyz", "en", isNsfw = false),
|
||||||
SingleLang("Animenosub", "https://animenosub.com", "en", isNsfw = true),
|
SingleLang("Animenosub", "https://animenosub.com", "en", isNsfw = true),
|
||||||
SingleLang("AnimeTitans", "https://animetitans.com", "ar", isNsfw = false, overrideVersionCode = 11),
|
SingleLang("AnimeTitans", "https://animetitans.com", "ar", isNsfw = false, overrideVersionCode = 11),
|
||||||
|
Reference in New Issue
Block a user