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,14 @@
ext {
extName = 'AnimeTake'
extClass = '.AnimeTake'
extVersionCode = 5
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:dood-extractor"))
implementation(project(":lib:mp4upload-extractor"))
implementation(project(":lib:filemoon-extractor"))
implementation(project(":lib:gogostream-extractor"))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,290 @@
package eu.kanade.tachiyomi.animeextension.en.animetake
import android.app.Application
import android.content.SharedPreferences
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.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.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.gogostreamextractor.GogoStreamExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parallelFlatMap
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 java.text.SimpleDateFormat
import java.util.Locale
class AnimeTake : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "AnimeTake"
override val baseUrl = "https://animetake.tv"
override val lang = "en"
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/animelist/popular")
override fun popularAnimeSelector() = "div.col-sm-6"
override fun popularAnimeFromElement(element: Element): SAnime {
return SAnime.create().apply {
setUrlWithoutDomain(element.select("div > a").attr("href"))
thumbnail_url = baseUrl + element.select("div.latestep_image > img").attr("data-src")
title = element.select("span.latestep_title > h4").first()!!.ownText()
}
}
override fun popularAnimeNextPageSelector() = "ul.pagination > li.page-item:last-child"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animelist/?page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimeTakeFilters.getSearchParameters(filters)
val cleanQuery = query.replace(" ", "+").lowercase()
return if (query.isNotEmpty()) {
GET("$baseUrl/search/?search=$cleanQuery&page=$page")
} else {
val multiString = buildString {
if (params.letters.isNotEmpty()) append(params.letters + "&")
if (params.genres.isNotEmpty()) append(params.genres + "&")
if (params.score.isNotEmpty()) append(params.score + "&")
if (params.years.isNotEmpty()) append(params.years + "&")
if (params.ratings.isNotEmpty()) append(params.ratings + "&")
}
GET("$baseUrl/animelist/?page=$page&$multiString")
}
}
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
// ============================== Filters ===============================
override fun getFilterList() = AnimeTakeFilters.FILTER_LIST
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.select("h3 > b").text()
anime.genre = document.select("a.animeinfo_label").joinToString {
it.select("span").text()
}
anime.description = document.select("div.visible-md").first()!!.ownText()
anime.status =
parseStatus(document.select("div.well > center:contains(Next Episode)").isNotEmpty())
document.select("div.well > p").first()!!.text().let {
if (it.isBlank().not()) {
anime.description = when {
anime.description.isNullOrBlank() -> it
else -> anime.description + "\n\n" + it
}
}
}
return anime
}
// ============================== Episodes ==============================
override fun episodeListSelector() = "div.tab-content"
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodesLink = document.select(episodeListSelector())
val episodes = mutableListOf<SEpisode>()
val specialsDiv = episodesLink.select("div#specials")
if (specialsDiv.isNotEmpty()) {
episodes.addAll(specialsDiv.select("a[href]").map(::episodeFromElement).reversed())
}
episodes.addAll(
episodesLink.select("div#eps").select("a[href]")
.map(::episodeFromElement).reversed(),
)
return episodes.toList()
}
override fun episodeFromElement(element: Element): SEpisode {
return SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
val upDate = element.select("div.col-xs-12 > span.front_time").text().trim()
date_upload = parseDate(upDate)
val epName = element.select("div.col-xs-12 > div.anime-title > b").text().trim()
val epNum = epName.split(" ").last()
name = epName
episode_number = epNum.toFloatOrNull() ?: 0F
}
}
// ============================ Video Links =============================
private val doodExtractor by lazy { DoodExtractor(client) }
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
private val gogoStreamExtractor by lazy { GogoStreamExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val scripts = document.select("div#divscript > script").filter { it ->
it.data().contains("function")
}
return scripts.parallelCatchingFlatMapBlocking { elem ->
val data = elem.data().trimIndent()
val url = baseUrl + extractIframeSrc(data)
if (data.contains("vidstream()")) {
val iframeSrc = client.newCall(GET(url)).execute().asJsoup()
.select("iframe").attr("src")
extractVideo(iframeSrc)
} else if (data.contains("fm()")) {
val iframeSrc = client.newCall(GET(url)).execute().asJsoup()
.select("iframe").attr("src")
filemoonExtractor.videosFromUrl(url = iframeSrc, headers = headers)
} else {
emptyList()
}
}
}
private suspend fun extractVideo(url: String): List<Video> {
val videos = gogoStreamExtractor.videosFromUrl(url)
val request = GET(url)
val response = client.newCall(request).await()
val document = response.asJsoup()
val servers = document.select("div#list-server-more > ul > li.linkserver")
return servers.parallelFlatMap {
val link = it.attr("data-video")
when (it.text().lowercase()) {
"doodstream" -> doodExtractor.videosFromUrl(link)
"mp4upload" -> mp4uploadExtractor.videosFromUrl(link, headers)
else -> emptyList()
}
} + videos
}
override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoListSelector() = throw UnsupportedOperationException()
// ============================= Utilities ==============================
private fun extractIframeSrc(scriptData: String): String {
val iframeRegex = "<iframe[^>]*>.*?</iframe>".toRegex()
val iframe = iframeRegex.find(scriptData)!!.value
val srcRegex = "(?<=src=\").*?(?=[\\*\"])".toRegex()
return srcRegex.find(iframe)!!.value
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ it.quality.contains(server) },
),
).reversed()
}
private fun parseDate(date: String): Long {
val formatter = SimpleDateFormat("dd LLLL yyyy", Locale.ENGLISH)
val newDate = formatter.parse(date)
val dateStr = newDate?.let { DATE_FORMATTER.format(it) }
return runCatching { DATE_FORMATTER.parse(dateStr!!)?.time }
.getOrNull() ?: 0L
}
private fun parseStatus(statusBool: Boolean): Int {
return if (statusBool) {
SAnime.ONGOING
} else {
SAnime.COMPLETED
}
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
}
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "1080p"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Vidstreaming"
private val PREF_SERVER_ENTRIES = arrayOf(
"Vidstreaming",
"Filemoon",
"Doodstream",
"Mp4upload",
)
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_ENTRIES
setDefaultValue(PREF_QUALITY_DEFAULT)
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()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = PREF_SERVER_ENTRIES
entryValues = PREF_SERVER_ENTRIES
setDefaultValue(PREF_SERVER_DEFAULT)
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()
}
}.also(screen::addPreference)
}
}

View file

@ -0,0 +1,170 @@
package eu.kanade.tachiyomi.animeextension.en.animetake
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimeTakeFilters {
open class CheckBoxFilterList(name: String, pairs: Array<Pair<String, String>>) :
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
private class CheckBoxVal(name: String, state: Boolean = false) :
AnimeFilter.CheckBox(name, state)
inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
name: String,
): String {
return (getFirst<R>() as CheckBoxFilterList).state
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank)
.joinToString("&") { "$name[]=$it" }
}
internal class LetterFilter : CheckBoxFilterList("Letter", AnimeTakeFiltersData.LETTER)
internal class GenresFilter : CheckBoxFilterList("Genre", AnimeTakeFiltersData.GENRE)
internal class ScoreFilter : CheckBoxFilterList("Score", AnimeTakeFiltersData.SCORE)
internal class YearFilter : CheckBoxFilterList("Year", AnimeTakeFiltersData.YEAR)
internal class RatingFilter : CheckBoxFilterList("Rating", AnimeTakeFiltersData.RATING)
val FILTER_LIST
get() = AnimeFilterList(
AnimeFilter.Header("Note: Ignores search"),
LetterFilter(),
GenresFilter(),
ScoreFilter(),
YearFilter(),
RatingFilter(),
)
internal data class FilterSearchParams(
val letters: String = "",
val genres: String = "",
val score: String = "",
val years: String = "",
val ratings: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.parseCheckbox<LetterFilter>(AnimeTakeFiltersData.LETTER, "letters"),
filters.parseCheckbox<GenresFilter>(AnimeTakeFiltersData.GENRE, "genres"),
filters.parseCheckbox<ScoreFilter>(AnimeTakeFiltersData.SCORE, "score"),
filters.parseCheckbox<YearFilter>(AnimeTakeFiltersData.YEAR, "years"),
filters.parseCheckbox<RatingFilter>(AnimeTakeFiltersData.RATING, "ratings"),
)
}
private object AnimeTakeFiltersData {
val LETTER = ('A'..'Z').map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val GENRE = arrayOf(
Pair("Action", "Action"),
Pair("Adventure", "Adventure"),
Pair("Chinese", "Chinese"),
Pair("Comedy", "Comedy"),
Pair("Detective", "Detective"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gourmet", "Gourmet"),
Pair("Harem", "Harem"),
Pair("High Stakes Game", "High+Stakes+Game"),
Pair("Historical", "Historical"),
Pair("Horror", "Horror"),
Pair("Isekai", "Isekai"),
Pair("Iyashikei", "Iyashikei"),
Pair("Josei", "Josei"),
Pair("Kids", "Kids"),
Pair("Magic", "Magic"),
Pair("Martial Arts", "Martial+Arts"),
Pair("Mecha", "Mecha"),
Pair("Military", "Military"),
Pair("Music", "Music"),
Pair("Mystery", "Mystery"),
Pair("Mythology", "Mythology"),
Pair("Parody", "Parody"),
Pair("Psychological", "Psychological"),
Pair("Racing", "Racing"),
Pair("Reincarnation", "Reincarnation"),
Pair("Romance", "Romance"),
Pair("Samurai", "Samurai"),
Pair("School", "School"),
Pair("Sci-Fi", "Sci-Fi"),
Pair("Seinen", "Seinen"),
Pair("Shoujo", "Shoujo"),
Pair("Shoujo Ai", "Shoujo+Ai"),
Pair("Shounen", "Shounen"),
Pair("Shounen Ai", "Shounen+Ai"),
Pair("Slice of Life", "Slice+of+Life"),
Pair("Space", "Space"),
Pair("Sports", "Sports"),
Pair("Strategy Game", "Strategy+Game"),
Pair("Super Power", "Super+Power"),
Pair("Supernatural", "Supernatural"),
Pair("Survival", "Survival"),
Pair("Suspense", "Suspense"),
Pair("Team Sports", "Team+Sports"),
Pair("Time Travel", "Time+Travel"),
Pair("Vampire", "Vampire"),
Pair("Video Game", "Video+Game"),
)
val SCORE = arrayOf(
Pair("Masterpiece (9+)", "masterpiece"),
Pair("Fantastic (8+)", "fantastic"),
Pair("Very Good+(7+)", "verygood"),
Pair("Fine (6+)", "fine"),
Pair("Average (5+)", "average"),
Pair("Bad (4+)", "bad"),
Pair("Very Bad (3+)", "verybad"),
Pair("Unwatchable (2+)", "unwatchable"),
)
val YEAR = arrayOf(
Pair("2024", "2024"),
Pair("2023", "2023"),
Pair("2022", "2022"),
Pair("2021", "2021"),
Pair("2020", "2020"),
Pair("2019", "2019"),
Pair("2018", "2018"),
Pair("2017", "2017"),
Pair("2016", "2016"),
Pair("2015", "2015"),
Pair("2014", "2014"),
Pair("2013", "2013"),
Pair("2012", "2012"),
Pair("2011", "2011"),
Pair("2010", "2010"),
Pair("2009", "2009"),
Pair("2008", "2008"),
Pair("2007", "2007"),
Pair("2006", "2006"),
Pair("2005", "2005"),
Pair("2004", "2004"),
Pair("2003", "2003"),
Pair("2002", "2002"),
Pair("2001", "2001"),
Pair("2000", "2000"),
Pair("1990-1999", "1990"),
Pair("1980-1989", "1980"),
Pair("1970-1979", "1970"),
)
val RATING = arrayOf(
Pair("G - All Ages", "allages"),
Pair("PG 13 - Teens 13 and Older", "pg13"),
Pair("R - 17+, Violence & Profanity", "r17"),
Pair("R+ - Profanity & Mild Nudity", "rplus"),
)
}
}