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 inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
||||
inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
||||
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
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>,
|
||||
name: String,
|
||||
): String {
|
||||
@ -39,16 +39,16 @@ object AnimeStreamFilters {
|
||||
.joinToString("&") { "$name[]=$it" }
|
||||
}
|
||||
|
||||
class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
|
||||
class SeasonFilter(name: String) : CheckBoxFilterList(name, SEASON_LIST)
|
||||
class StudioFilter(name: String) : CheckBoxFilterList(name, STUDIO_LIST)
|
||||
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)
|
||||
|
||||
class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
|
||||
class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
|
||||
class SubFilter(name: String) : QueryPartFilter(name, SUB_LIST)
|
||||
class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
|
||||
internal class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
|
||||
internal class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
|
||||
internal class SubFilter(name: String) : QueryPartFilter(name, SUB_LIST)
|
||||
internal class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
|
||||
|
||||
data class FilterSearchParams(
|
||||
internal data class FilterSearchParams(
|
||||
val genres: String = "",
|
||||
val seasons: 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)
|
||||
.select("li")
|
||||
|
@ -11,6 +11,7 @@ class AnimeStreamGenerator : ThemeSourceGenerator {
|
||||
override val baseVersionCode = 2
|
||||
|
||||
override val sources = listOf(
|
||||
SingleLang("AnimeIndo", "https://animeindo.quest", "id", isNsfw = false),
|
||||
SingleLang("AnimeKhor", "https://animekhor.xyz", "en", isNsfw = false),
|
||||
SingleLang("Animenosub", "https://animenosub.com", "en", isNsfw = true),
|
||||
SingleLang("AnimeTitans", "https://animetitans.com", "ar", isNsfw = false, overrideVersionCode = 11),
|
||||
|
Reference in New Issue
Block a user