Merged with dark25 (#636)

* merge

merged lib, lib-multisrc, all, ar, de, en, es, fr, hi, id, it, pt, tr src from dark25

* patch
This commit is contained in:
Hak 2025-02-10 15:41:59 +07:00 committed by GitHub
parent 9f385108fc
commit 1384df62f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
350 changed files with 12176 additions and 1064 deletions

View file

@ -1,7 +1,7 @@
ext {
extName = 'Torrentio Anime (Torrent / Debrid)'
extClass = '.Torrentio'
extVersionCode = 11
extVersionCode = 14
containsNsfw = false
}

View file

@ -0,0 +1,155 @@
package eu.kanade.tachiyomi.animeextension.all.torrentioanime
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AniListFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toQueryPart() = vals[state].second
}
open class CheckBoxFilterList(name: String, val 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)
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.parseCheckboxList(
options: Array<Pair<String, String>>,
): List<String> {
return (getFirst<R>() as CheckBoxFilterList).state
.filter { it.state }
.map { checkBox -> options.find { it.first == checkBox.name }!!.second }
.filter(String::isNotBlank)
}
private inline fun <reified R> AnimeFilterList.getSort(): String {
val state = (getFirst<R>() as AnimeFilter.Sort).state ?: return ""
val index = state.index
val suffix = if (state.ascending) "" else "_DESC"
return AniListFiltersData.SORT_LIST[index].second + suffix
}
class GenreFilter : CheckBoxFilterList("Genres", AniListFiltersData.GENRE_LIST)
class YearFilter : QueryPartFilter("Year", AniListFiltersData.YEAR_LIST)
class SeasonFilter : QueryPartFilter("Season", AniListFiltersData.SEASON_LIST)
class FormatFilter : CheckBoxFilterList("Format", AniListFiltersData.FORMAT_LIST)
class StatusFilter : QueryPartFilter("Airing Status", AniListFiltersData.STATUS_LIST)
class SortFilter : AnimeFilter.Sort(
"Sort",
AniListFiltersData.SORT_LIST.map { it.first }.toTypedArray(),
Selection(1, false),
)
val FILTER_LIST get() = AnimeFilterList(
SortFilter(),
FormatFilter(),
GenreFilter(),
YearFilter(),
SeasonFilter(),
StatusFilter(),
)
class FilterSearchParams(
val sort: String = "",
val format: List<String> = emptyList(),
val genres: List<String> = emptyList(),
val year: String = "",
val season: String = "",
val status: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
return FilterSearchParams(
filters.getSort<SortFilter>(),
filters.parseCheckboxList<FormatFilter>(AniListFiltersData.FORMAT_LIST),
filters.parseCheckboxList<GenreFilter>(AniListFiltersData.GENRE_LIST),
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<SeasonFilter>(),
filters.asQueryPart<StatusFilter>(),
)
}
private object AniListFiltersData {
val GENRE_LIST = arrayOf(
Pair("Action", "Action"),
Pair("Adventure", "Adventure"),
Pair("Comedy", "Comedy"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Horror", "Horror"),
Pair("Mahou Shoujo", "Mahou Shoujo"),
Pair("Mecha", "Mecha"),
Pair("Music", "Music"),
Pair("Mystery", "Mystery"),
Pair("Psychological", "Psychological"),
Pair("Romance", "Romance"),
Pair("Sci-Fi", "Sci-Fi"),
Pair("Slice of Life", "Slice of Life"),
Pair("Sports", "Sports"),
Pair("Supernatural", "Supernatural"),
Pair("Thriller", "Thriller"),
)
val YEAR_LIST: Array<Pair<String, String>> = arrayOf(
Pair("Any", ""),
) + (1940..2025).reversed().map { Pair(it.toString(), it.toString()) }.toTypedArray()
val SEASON_LIST = arrayOf(
Pair("Any", ""),
Pair("Winter", "WINTER"),
Pair("Spring", "SPRING"),
Pair("Summer", "SUMMER"),
Pair("Fall", "FALL"),
)
val FORMAT_LIST = arrayOf(
Pair("Any", ""),
Pair("TV Show", "TV"),
Pair("Movie", "MOVIE"),
Pair("TV Short", "TV_SHORT"),
Pair("Special", "SPECIAL"),
Pair("OVA", "OVA"),
Pair("ONA", "ONA"),
Pair("Music", "MUSIC"),
)
val STATUS_LIST = arrayOf(
Pair("Any", ""),
Pair("Airing", "RELEASING"),
Pair("Finished", "FINISHED"),
Pair("Not Yet Aired", "NOT_YET_RELEASED"),
Pair("Cancelled", "CANCELLED"),
)
val SORT_LIST = arrayOf(
Pair("Title", "TITLE_ENGLISH"),
Pair("Popularity", "POPULARITY"),
Pair("Average Score", "SCORE"),
Pair("Trending", "TRENDING"),
Pair("Favorites", "FAVOURITES"),
Pair("Date Added", "ID"),
Pair("Release Date", "START_DATE"),
)
}
}

View file

@ -0,0 +1,159 @@
package eu.kanade.tachiyomi.animeextension.all.torrentioanime
private fun String.toQuery() = this.trimIndent().replace("%", "$")
fun anilistQuery() = """
query (
%page: Int,
%perPage: Int,
%sort: [MediaSort],
%search: String,
%genres: [String],
%year: String,
%seasonYear: Int,
%season: MediaSeason,
%format: [MediaFormat],
%status: [MediaStatus],
) {
Page(page: %page, perPage: %perPage) {
pageInfo {
currentPage
hasNextPage
}
media(
type: ANIME,
sort: %sort,
search: %search,
status_in: %status,
genre_in: %genres,
startDate_like: %year,
seasonYear: %seasonYear,
season: %season,
format_in: %format
) {
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
}
description
status
tags {
name
}
genres
studios {
nodes {
name
}
}
countryOfOrigin
isAdult
}
}
}
""".toQuery()
fun anilistLatestQuery() = """
query (%page: Int, %perPage: Int, %sort: [AiringSort]) {
Page(page: %page, perPage: %perPage) {
pageInfo {
currentPage
hasNextPage
}
airingSchedules(
airingAt_greater: 0,
airingAt_lesser: ${System.currentTimeMillis() / 1000 - 10000},
sort: %sort
) {
media {
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
}
description
status
tags {
name
}
genres
studios {
nodes {
name
}
}
countryOfOrigin
isAdult
}
}
}
}
""".toQuery()
fun getDetailsQuery() = """
query media(%id: Int) {
Media(id: %id) {
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
medium
}
description
season
seasonYear
format
status
genres
episodes
format
countryOfOrigin
isAdult
tags{
name
}
studios {
nodes {
id
name
}
}
}
}
""".toQuery()
fun getEpisodeQuery() = """
query media(%id: Int, %type: MediaType) {
Media(id: %id, type: %type) {
episodes
nextAiringEpisode {
episode
}
}
}
""".toQuery()
fun getMalIdQuery() = """
query media(%id: Int, %type: MediaType) {
Media(id: %id, type: %type) {
idMal
id
}
}
""".toQuery()

View file

@ -25,15 +25,19 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.Jsoup
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Locale
@ -62,93 +66,9 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
.add("query", query)
.add("variables", variables)
.build()
return POST("https://graphql.anilist.co", body = requestBody)
}
// ============================== Anilist Meta List ======================
private fun anilistQuery(): String {
return """
query (${"$"}page: Int, ${"$"}perPage: Int, ${"$"}sort: [MediaSort], ${"$"}search: String) {
Page(page: ${"$"}page, perPage: ${"$"}perPage) {
pageInfo{
currentPage
hasNextPage
}
media(type: ANIME, sort: ${"$"}sort, search: ${"$"}search, status_in:[RELEASING,FINISHED,NOT_YET_RELEASED]) {
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
}
description
status
tags{
name
}
genres
studios {
nodes {
name
}
}
countryOfOrigin
isAdult
}
}
}
""".trimIndent()
}
private fun anilistLatestQuery(): String {
return """
query (${"$"}page: Int, ${"$"}perPage: Int, ${"$"}sort: [AiringSort]) {
Page(page: ${"$"}page, perPage: ${"$"}perPage) {
pageInfo {
currentPage
hasNextPage
}
airingSchedules(
airingAt_greater: 0
airingAt_lesser: ${System.currentTimeMillis() / 1000 - 10000}
sort: ${"$"}sort
) {
media{
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
}
description
status
tags{
name
}
genres
studios {
nodes {
name
}
}
countryOfOrigin
isAdult
}
}
}
}
""".trimIndent()
}
private fun parseSearchJson(jsonLine: String?, isLatestQuery: Boolean = false): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val metaData: Any = if (!isLatestQuery) {
@ -218,7 +138,8 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
{
"page": $page,
"perPage": 30,
"sort": "TRENDING_DESC"
"sort": "TRENDING_DESC",
"status": ["FINISHED", "RELEASING"]
}
""".trimIndent()
@ -227,8 +148,7 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
override fun popularAnimeParse(response: Response): AnimesPage {
val jsonData = response.body.string()
return parseSearchJson(jsonData)
}
return parseSearchJson(jsonData) }
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request {
@ -261,67 +181,73 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
}
private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}
val details = animeDetailsParse(response)
return AnimesPage(listOf(details), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val variables = """
{
"page": $page,
"perPage": 30,
"sort": "POPULARITY_DESC",
"search": "$query"
val params = AniListFilters.getSearchParameters(filters)
val variablesObject = buildJsonObject {
put("page", page)
put("perPage", 30)
put("sort", params.sort)
if (query.isNotBlank()) put("search", query)
if (params.genres.isNotEmpty()) {
putJsonArray("genres") {
params.genres.forEach { add(it) }
}
}
""".trimIndent()
if (params.format.isNotEmpty()) {
putJsonArray("format") {
params.format.forEach { add(it) }
}
}
if (params.season.isBlank() && params.year.isNotBlank()) {
put("year", "${params.year}%")
}
if (params.season.isNotBlank() && params.year.isBlank()) {
throw Exception("Year cannot be blank if season is set")
}
if (params.season.isNotBlank() && params.year.isNotBlank()) {
put("season", params.season)
put("seasonYear", params.year)
}
if (params.status.isNotBlank()) {
putJsonArray("status") {
params.status.forEach { add(it.toString()) }
}
}
}
val variables = json.encodeToString(variablesObject)
println(anilistQuery())
println(variables)
return makeGraphQLRequest(anilistQuery(), variables)
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AniListFilters.FILTER_LIST
// =========================== Anime Details ============================
override fun animeDetailsParse(response: Response): SAnime = throw UnsupportedOperationException()
override suspend fun getAnimeDetails(anime: SAnime): SAnime {
val query = """
query(${"$"}id: Int){
Media(id: ${"$"}id){
id
title {
romaji
english
native
}
coverImage {
extraLarge
large
}
description
status
tags{
name
}
genres
studios {
nodes {
name
}
}
countryOfOrigin
isAdult
}
}
""".trimIndent()
val variables = """{"id": ${anime.url}}"""
val metaData = runCatching {
json.decodeFromString<DetailsById>(client.newCall(makeGraphQLRequest(query, variables)).execute().body.string())
json.decodeFromString<DetailsById>(client.newCall(makeGraphQLRequest(getDetailsQuery(), variables)).execute().body.string())
}.getOrNull()?.data?.media
anime.title = metaData?.title?.let { title ->
@ -334,10 +260,24 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
} ?: ""
anime.thumbnail_url = metaData?.coverImage?.extraLarge
anime.description = metaData?.description
?.replace(Regex("<br><br>"), "\n")
?.replace(Regex("<.*?>"), "")
?: "No Description"
anime.description = buildString {
append(
metaData?.description?.let {
Jsoup.parseBodyFragment(
it.replace("<br>\n", "br2n")
.replace("<br>", "br2n")
.replace("\n", "br2n"),
).text().replace("br2n", "\n")
},
)
append("\n\n")
if (!(metaData?.season == null && metaData?.seasonYear == null)) {
append("Release: ${ metaData.season ?: ""} ${ metaData.seasonYear ?: ""}")
}
metaData?.format?.let { append("\nType: ${metaData.format}") }
metaData?.episodes?.let { append("\nTotal Episode Count: ${metaData.episodes}") }
}.trim()
anime.status = when (metaData?.status) {
"RELEASING" -> SAnime.ONGOING
@ -360,9 +300,7 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime): Request {
val res = URL("https://api.ani.zip/mappings?anilist_id=${anime.url}").readText()
val kitsuId = JSONObject(res).getJSONObject("mappings").getInt("kitsu_id").toString()
return GET("https://anime-kitsu.strem.fun/meta/series/kitsu%3A$kitsuId.json")
return GET("https://anime-kitsu.strem.fun/meta/series/anilist%3A${anime.url}.json")
}
override fun episodeListParse(response: Response): List<SEpisode> {
@ -375,7 +313,6 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
?.let { videos ->
if (preferences.getBoolean(UPCOMING_EP_KEY, UPCOMING_EP_DEFAULT)) { videos } else { videos.filter { video -> (video.released?.let { parseDate(it) } ?: 0L) <= System.currentTimeMillis() } }
}
?.filter { it.thumbnail != null }
?.map { video ->
SEpisode.create().apply {
episode_number = video.episode?.toFloat() ?: 0.0F
@ -481,9 +418,9 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
udp://tracker.tiny-vps.com:6969/announce,
udp://tracker.torrent.eu.org:451/announce,
udp://valakas.rollo.dnsabr.com:2710/announce,
udp://www.torrent.eu.org:451/announce
udp://www.torrent.eu.org:451/announce,
${fetchTrackers().split("\n").joinToString(",")}
""".trimIndent()
return streamList.streams?.map { stream ->
val urlOrHash =
if (debridProvider == "none") {
@ -509,6 +446,17 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
)
}
private fun fetchTrackers(): String {
val request = Request.Builder()
.url("https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw Exception("Unexpected code $response")
return response.body.string().trim()
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
// Debrid provider
ListPreference(screen.context).apply {
@ -714,7 +662,10 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
"🇫🇷 Torrent9",
"🇪🇸 MejorTorrent",
"🇲🇽 Cinecalidad",
"🇮🇹 ilCorsaroNero",
"🇪🇸 Wolfmax4k",
)
private val PREF_PROVIDERS_VALUE = arrayOf(
"yts",
"eztv",
@ -735,6 +686,8 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
"torrent9",
"mejortorrent",
"cinecalidad",
"ilcorsaronero",
"wolfmax4k",
)
private val PREF_DEFAULT_PROVIDERS_VALUE = arrayOf(
@ -759,6 +712,9 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
"BluRay REMUX",
"HDR/HDR10+/Dolby Vision",
"Dolby Vision",
"Dolby Vision + HDR",
"3D",
"Non 3D (DO NOT SELECT IF NOT SURE)",
"4k",
"1080p",
"720p",
@ -768,10 +724,14 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
"Cam",
"Unknown",
)
private val PREF_QUALITY_VALUE = arrayOf(
"brremux",
"hdrall",
"dolbyvision",
"dolbyvisionwithhdr",
"threed",
"nonthreed",
"4k",
"1080p",
"720p",

View file

@ -73,7 +73,11 @@ data class AnilistMedia(
val status: String? = null,
val tags: List<AnilistTag>? = null,
val genres: List<String>? = null,
val episodes: Int? = null,
val format: String? = null,
val studios: AnilistStudios? = null,
val season: String? = null,
val seasonYear: Int? = null,
val countryOfOrigin: String? = null,
val isAdult: Boolean = false,
)