Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

72 changed files with 202 additions and 315 deletions

View file

@ -48,7 +48,7 @@ body:
required: true required: true
- label: I have written a title with source name. - label: I have written a title with source name.
required: true required: true
- label: I have checked that the extension does not already exist by searching the [Repository](https://kohiden.xyz/Kohi-den/extensions-source/src/branch/main/src) and verified it does not appear in the code base. - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/Kohi-den/extensions-source) and verified it does not appear in the code base.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View file

@ -13,7 +13,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
CI_CHUNK_SIZE: 288 CI_CHUNK_SIZE: 65
jobs: jobs:
prepare: prepare:
@ -86,7 +86,7 @@ jobs:
build-cache-${{ github.event.pull_request.base.sha }}- build-cache-${{ github.event.pull_request.base.sha }}-
build-cache- build-cache-
- name: Build extensions - name: Build extensions (chunk ${{ matrix.chunk }})
env: env:
CI_CHUNK_NUM: ${{ matrix.chunk }} CI_CHUNK_NUM: ${{ matrix.chunk }}
run: chmod +x ./gradlew && ./gradlew -p src assembleDebug run: chmod +x ./gradlew && ./gradlew -p src assembleDebug

View file

@ -196,10 +196,6 @@ jobs:
run: | run: |
rsync -a --delete --exclude .git --exclude .gitignore main/repo/ repo --exclude README.md --exclude repo.json rsync -a --delete --exclude .git --exclude .gitignore main/repo/ repo --exclude README.md --exclude repo.json
- name: Increase buffer size
run: |
git config --global http.postBuffer 157286400
- name: Deploy repo - name: Deploy repo
uses: https://github.com/EndBug/add-and-commit@v9 uses: https://github.com/EndBug/add-and-commit@v9
with: with:

View file

@ -18,33 +18,14 @@ class VoeExtractor(private val client: OkHttpClient) {
private val playlistUtils by lazy { PlaylistUtils(clientDdos) } private val playlistUtils by lazy { PlaylistUtils(clientDdos) }
private val linkRegex = "(http|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])".toRegex()
private val base64Regex = Regex("'.*'")
private val scriptBase64Regex = "(let|var)\\s+\\w+\\s*=\\s*'(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)';".toRegex()
@Serializable @Serializable
data class VideoLinkDTO(val source: String) data class VideoLinkDTO(val file: String)
private fun decodeVoeData(data: String): String {
val shifted = data.map { char ->
when (char) {
in 'A'..'Z' -> 'A' + (char - 'A' + 13).mod(26)
in 'a'..'z' -> 'a' + (char - 'a' + 13).mod(26)
else -> char
}
}.joinToString()
val junk = listOf("@$", "^^", "~@", "%?", "*~", "!!", "#&")
var result = shifted
for (part in junk) {
result = result.replace(part, "_")
}
val clean = result.replace("_", "")
val transformed = String(Base64.decode(clean, Base64.DEFAULT)).map {
(it.code - 3).toChar()
}.joinToString().reversed()
val decoded = String(Base64.decode(transformed, Base64.DEFAULT))
return json.decodeFromString<VideoLinkDTO>(decoded).source
}
fun videosFromUrl(url: String, prefix: String = ""): List<Video> { fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
var document = clientDdos.newCall(GET(url)).execute().asJsoup() var document = clientDdos.newCall(GET(url)).execute().asJsoup()
@ -57,12 +38,25 @@ class VoeExtractor(private val client: OkHttpClient) {
document = clientDdos.newCall(GET(originalUrl)).execute().asJsoup() document = clientDdos.newCall(GET(originalUrl)).execute().asJsoup()
} }
val encodedVoeData = document.select("script").find { it.data().contains("MKGMa=\"")}?.data() val alternativeScript = document.select("script").find { scriptBase64Regex.containsMatchIn(it.data()) }?.data()
?.substringAfter("MKGMa=\"") val script = document.selectFirst("script:containsData(const sources), script:containsData(var sources), script:containsData(wc0)")?.data()
?.substringBefore('"') ?: return emptyList() ?: alternativeScript
?: return emptyList()
val playlistUrl = decodeVoeData(encodedVoeData) val playlistUrl = when {
// Layout 1
script.contains("sources") -> {
val link = script.substringAfter("hls': '").substringBefore("'")
if (linkRegex.matches(link)) link else String(Base64.decode(link, Base64.DEFAULT))
}
// Layout 2
script.contains("wc0") || alternativeScript != null -> {
val base64 = base64Regex.find(script)!!.value
val decoded = Base64.decode(base64, Base64.DEFAULT).let(::String)
json.decodeFromString<VideoLinkDTO>(if (alternativeScript != null) decoded.reversed() else decoded).file
}
else -> return emptyList()
}
return playlistUtils.extractFromHls(playlistUrl, return playlistUtils.extractFromHls(playlistUrl,
videoNameGen = { quality -> "${prefix}Voe:$quality" } videoNameGen = { quality -> "${prefix}Voe:$quality" }
) )

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'JavGG' extName = 'JavGG'
extClass = '.Javgg' extClass = '.Javgg'
extVersionCode = 9 extVersionCode = 7
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'SupJav' extName = 'SupJav'
extClass = '.SupJavFactory' extClass = '.SupJavFactory'
extVersionCode = 18 extVersionCode = 16
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anime4up' extName = 'Anime4up'
extClass = '.Anime4Up' extClass = '.Anime4Up'
extVersionCode = 66 extVersionCode = 64
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Arab Seed' extName = 'Arab Seed'
extClass = '.ArabSeed' extClass = '.ArabSeed'
extVersionCode = 21 extVersionCode = 19
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Okanime' extName = 'Okanime'
extClass = '.Okanime' extClass = '.Okanime'
extVersionCode = 16 extVersionCode = 14
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anime-Base' extName = 'Anime-Base'
extClass = '.AnimeBase' extClass = '.AnimeBase'
extVersionCode = 35 extVersionCode = 33
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anime-Loads' extName = 'Anime-Loads'
extClass = '.AnimeLoads' extClass = '.AnimeLoads'
extVersionCode = 20 extVersionCode = 18
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'AnimeToast' extName = 'AnimeToast'
extClass = '.AnimeToast' extClass = '.AnimeToast'
extVersionCode = 25 extVersionCode = 23
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'AniWorld' extName = 'AniWorld'
extClass = '.AniWorld' extClass = '.AniWorld'
extVersionCode = 28 extVersionCode = 26
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Einfach' extName = 'Einfach'
extClass = '.Einfach' extClass = '.Einfach'
extVersionCode = 20 extVersionCode = 18
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'FilmPalast' extName = 'FilmPalast'
extClass = '.FilmPalast' extClass = '.FilmPalast'
extVersionCode = 22 extVersionCode = 20
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.Kinoking' extClass = '.Kinoking'
themePkg = 'dooplay' themePkg = 'dooplay'
baseUrl = 'https://kinoking.cc' baseUrl = 'https://kinoking.cc'
overrideVersionCode = 26 overrideVersionCode = 24
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Kool' extName = 'Kool'
extClass = '.Kool' extClass = '.Kool'
extVersionCode = 17 extVersionCode = 15
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Movie4k' extName = 'Movie4k'
extClass = '.Movie4k' extClass = '.Movie4k'
extVersionCode = 14 extVersionCode = 12
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Serienstream' extName = 'Serienstream'
extClass = '.Serienstream' extClass = '.Serienstream'
extVersionCode = 27 extVersionCode = 25
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Animefenix' extName = 'Animefenix'
extClass = '.Animefenix' extClass = '.Animefenix'
extVersionCode = 61 extVersionCode = 59
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Animejl' extName = 'Animejl'
extClass = '.Animejl' extClass = '.Animejl'
extVersionCode = 9 extVersionCode = 7
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'AnimeMovil' extName = 'AnimeMovil'
extClass = '.AnimeMovil' extClass = '.AnimeMovil'
extVersionCode = 33 extVersionCode = 31
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Cine24h' extName = 'Cine24h'
extClass = '.Cine24h' extClass = '.Cine24h'
extVersionCode = 15 extVersionCode = 13
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'CineCalidad' extName = 'CineCalidad'
extClass = '.CineCalidad' extClass = '.CineCalidad'
extVersionCode = 21 extVersionCode = 19
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Cuevana' extName = 'Cuevana'
extClass = '.CuevanaFactory' extClass = '.CuevanaFactory'
extVersionCode = 50 extVersionCode = 48
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.DeTodoPeliculas' extClass = '.DeTodoPeliculas'
themePkg = 'dooplay' themePkg = 'dooplay'
baseUrl = 'https://detodopeliculas.nu' baseUrl = 'https://detodopeliculas.nu'
overrideVersionCode = 7 overrideVersionCode = 5
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Doramasflix' extName = 'Doramasflix'
extClass = '.Doramasflix' extClass = '.Doramasflix'
extVersionCode = 37 extVersionCode = 35
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Doramasyt' extName = 'Doramasyt'
extClass = '.Doramasyt' extClass = '.Doramasyt'
extVersionCode = 24 extVersionCode = 22
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'EnNovelas' extName = 'EnNovelas'
extClass = '.EnNovelas' extClass = '.EnNovelas'
extVersionCode = 23 extVersionCode = 21
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'EstrenosDoramas' extName = 'EstrenosDoramas'
extClass = '.EstrenosDoramas' extClass = '.EstrenosDoramas'
extVersionCode = 9 extVersionCode = 7
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.FlixLatam' extClass = '.FlixLatam'
themePkg = 'dooplay' themePkg = 'dooplay'
baseUrl = 'https://flixlatam.com' baseUrl = 'https://flixlatam.com'
overrideVersionCode = 8 overrideVersionCode = 6
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Gnula' extName = 'Gnula'
extClass = '.Gnula' extClass = '.Gnula'
extVersionCode = 35 extVersionCode = 33
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hackstore' extName = 'Hackstore'
extClass = '.Hackstore' extClass = '.Hackstore'
extVersionCode = 29 extVersionCode = 27
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'HentaiLA' extName = 'HentaiLA'
extClass = '.Hentaila' extClass = '.Hentaila'
extVersionCode = 38 extVersionCode = 36
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'HentaiTk' extName = 'HentaiTk'
extClass = '.Hentaitk' extClass = '.Hentaitk'
extVersionCode = 17 extVersionCode = 15
isNsfw = true isNsfw = true
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'HomeCine' extName = 'HomeCine'
extClass = '.HomeCine' extClass = '.HomeCine'
extVersionCode = 9 extVersionCode = 7
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Jkanime' extName = 'Jkanime'
extClass = '.Jkanime' extClass = '.Jkanime'
extVersionCode = 39 extVersionCode = 37
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'LACartoons' extName = 'LACartoons'
extClass = '.Lacartoons' extClass = '.Lacartoons'
extVersionCode = 15 extVersionCode = 13
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'MetroSeries' extName = 'MetroSeries'
extClass = '.MetroSeries' extClass = '.MetroSeries'
extVersionCode = 22 extVersionCode = 20
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'MhdFlix' extName = 'MhdFlix'
extClass = '.MhdFlix' extClass = '.MhdFlix'
extVersionCode = 10 extVersionCode = 8
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'MonosChinos' extName = 'MonosChinos'
extClass = '.MonosChinos' extClass = '.MonosChinos'
extVersionCode = 39 extVersionCode = 37
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'MundoDonghua' extName = 'MundoDonghua'
extClass = '.MundoDonghua' extClass = '.MundoDonghua'
extVersionCode = 33 extVersionCode = 31
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Otakuverso' extName = 'Otakuverso'
extClass = '.Otakuverso' extClass = '.Otakuverso'
extVersionCode = 5 extVersionCode = 3
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'PelisForte' extName = 'PelisForte'
extClass = '.PelisForte' extClass = '.PelisForte'
extVersionCode = 34 extVersionCode = 32
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Pelisplushd' extName = 'Pelisplushd'
extClass = '.PelisplushdFactory' extClass = '.PelisplushdFactory'
extVersionCode = 77 extVersionCode = 75
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Serieskao' extName = 'Serieskao'
extClass = '.Serieskao' extClass = '.Serieskao'
extVersionCode = 8 extVersionCode = 6
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.SoloLatino' extClass = '.SoloLatino'
themePkg = 'dooplay' themePkg = 'dooplay'
baseUrl = 'https://sololatino.net' baseUrl = 'https://sololatino.net'
overrideVersionCode = 10 overrideVersionCode = 8
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'TioanimeH' extName = 'TioanimeH'
extClass = '.TioanimeHFactory' extClass = '.TioanimeHFactory'
extVersionCode = 28 extVersionCode = 26
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.Tiodonghua' extClass = '.Tiodonghua'
themePkg = 'animestream' themePkg = 'animestream'
baseUrl = 'https://anime.tiodonghua.com' baseUrl = 'https://anime.tiodonghua.com'
overrideVersionCode = 9 overrideVersionCode = 7
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'VerAnimes' extName = 'VerAnimes'
extClass = '.VerAnimes' extClass = '.VerAnimes'
extVersionCode = 14 extVersionCode = 12
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'VerSeriesOnline' extName = 'VerSeriesOnline'
extClass = '.VerSeriesOnline' extClass = '.VerSeriesOnline'
extVersionCode = 8 extVersionCode = 6
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Zonaleros' extName = 'Zonaleros'
extClass = '.Zonaleros' extClass = '.Zonaleros'
extVersionCode = 5 extVersionCode = 3
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anime-Sama' extName = 'Anime-Sama'
extClass = '.AnimeSama' extClass = '.AnimeSama'
extVersionCode = 13 extVersionCode = 12
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -4,7 +4,6 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.AnimesPage
@ -18,7 +17,6 @@ import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMap import eu.kanade.tachiyomi.util.parallelCatchingFlatMap
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -51,11 +49,12 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
val doc = response.asJsoup() val doc = response.body.string()
val page = response.request.url.fragment?.toInt() ?: 0 val page = response.request.url.fragment?.toInt() ?: 0
val chunks = doc.select("#containerPepites > div a").chunked(5) val regex = Regex("^\\s*carteClassique\\(\\s*.*?\\s*,\\s*\"(.*?)\".*\\)", RegexOption.MULTILINE)
val chunks = regex.findAll(doc).chunked(5).toList()
val seasons = chunks.getOrNull(page - 1)?.flatMap { val seasons = chunks.getOrNull(page - 1)?.flatMap {
val animeUrl = "$baseUrl${it.attr("href")}" val animeUrl = "$baseUrl/catalogue/${it.groupValues[1]}"
fetchAnimeSeasons(animeUrl) fetchAnimeSeasons(animeUrl)
}?.toList().orEmpty() }?.toList().orEmpty()
return AnimesPage(seasons, page < chunks.size) return AnimesPage(seasons, page < chunks.size)
@ -73,7 +72,7 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
.removePathSegment(animeUrl.pathSize - 3) .removePathSegment(animeUrl.pathSize - 3)
.build() .build()
fetchAnimeSeasons(url.toString()) fetchAnimeSeasons(url.toString())
}.distinctBy { it.url } }
return AnimesPage(seasons, false) return AnimesPage(seasons, false)
} }
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl) override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
@ -81,26 +80,29 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
// =============================== Search =============================== // =============================== Search ===============================
override fun getFilterList() = AnimeSamaFilters.FILTER_LIST override fun getFilterList() = AnimeSamaFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
val url = "$baseUrl/catalogue/".toHttpUrl().newBuilder() if (query.startsWith(PREFIX_SEARCH)) {
return AnimesPage(fetchAnimeSeasons("$baseUrl/catalogue/${query.removePrefix(PREFIX_SEARCH)}/"), false)
}
val params = AnimeSamaFilters.getSearchFilters(filters) val params = AnimeSamaFilters.getSearchFilters(filters)
params.types.forEach { url.addQueryParameter("type[]", it) } val elements = database
params.language.forEach { url.addQueryParameter("langue[]", it) } .asSequence()
params.genres.forEach { url.addQueryParameter("genre[]", it) } .filter { it.select("h1, p").fold(false) { v, e -> v || e.text().contains(query, true) } }
url.addQueryParameter("search", query) .filter { params.include.all { p -> it.className().contains(p) } }
url.addQueryParameter("page", "$page") .filter { params.exclude.none { p -> it.className().contains(p) } }
return GET(url.build(), headers) .filter { params.types.fold(params.types.isEmpty()) { v, p -> v || it.className().contains(p) } }
.filter { params.language.fold(params.language.isEmpty()) { v, p -> v || it.className().contains(p) } }
.chunked(5)
.toList()
if (elements.isEmpty()) return AnimesPage(emptyList(), false)
val animes = elements[page - 1].flatMap {
fetchAnimeSeasons(it.getElementsByTag("a").attr("href"))
}
return AnimesPage(animes, page < elements.size)
} }
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage = throw UnsupportedOperationException()
val document = response.asJsoup() override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw UnsupportedOperationException()
val anime = document.select("#list_catalog > div a").parallelFlatMapBlocking {
fetchAnimeSeasons(it.attr("href"))
}
val page = response.request.url.queryParameterValues("page").first()
val hasNextPage = document.select("#list_pagination a:last-child").text() != page
return AnimesPage(anime, hasNextPage)
}
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override suspend fun getAnimeDetails(anime: SAnime): SAnime = anime override suspend fun getAnimeDetails(anime: SAnime): SAnime = anime
@ -119,10 +121,6 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> = throw UnsupportedOperationException() override fun episodeListParse(response: Response): List<SEpisode> = throw UnsupportedOperationException()
// ============================ Video Links ============================= // ============================ Video Links =============================
private val sibnetExtractor by lazy { SibnetExtractor(client) }
private val vkExtractor by lazy { VkExtractor(client, headers) }
private val sendvidExtractor by lazy { SendvidExtractor(client, headers) }
override suspend fun getVideoList(episode: SEpisode): List<Video> { override suspend fun getVideoList(episode: SEpisode): List<Video> {
val playerUrls = json.decodeFromString<List<List<String>>>(episode.url) val playerUrls = json.decodeFromString<List<List<String>>>(episode.url)
val videos = playerUrls.flatMapIndexed { i, it -> val videos = playerUrls.flatMapIndexed { i, it ->
@ -130,9 +128,9 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
it.parallelCatchingFlatMap { playerUrl -> it.parallelCatchingFlatMap { playerUrl ->
with(playerUrl) { with(playerUrl) {
when { when {
contains("sibnet.ru") -> sibnetExtractor.videosFromUrl(playerUrl, prefix) contains("sibnet.ru") -> SibnetExtractor(client).videosFromUrl(playerUrl, prefix)
contains("vk.") -> vkExtractor.videosFromUrl(playerUrl, prefix) contains("vk.") -> VkExtractor(client, headers).videosFromUrl(playerUrl, prefix)
contains("sendvid.com") -> sendvidExtractor.videosFromUrl(playerUrl, prefix) contains("sendvid.com") -> SendvidExtractor(client, headers).videosFromUrl(playerUrl, prefix)
else -> emptyList() else -> emptyList()
} }
} }
@ -142,16 +140,21 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
} }
// ============================ Utils ============================= // ============================ Utils =============================
private fun sanitizeEpisodesJs(doc: String) = doc
.replace(Regex("[\"\t]"), "") // Fix trash format
.replace("'", "\"") // Fix quotes
.replace(Regex("/\\*.*?\\*/", setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL)), "") // Remove block comments
.replace(Regex("(^|,|\\[)\\s*//.*?$", RegexOption.MULTILINE), "$1") // Remove line comments
.replace(Regex(",\\s*]"), "]") // Remove trailing comma
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val voices = preferences.getString(PREF_VOICES_KEY, PREF_VOICES_DEFAULT)!! val voices = preferences.getString(PREF_VOICES_KEY, PREF_VOICES_DEFAULT)!!
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val player = preferences.getString(PREF_PLAYER_KEY, PREF_PLAYER_DEFAULT)!!
return this.sortedWith( return this.sortedWith(
compareBy( compareBy(
{ it.quality.contains(voices, true) }, { it.quality.contains(voices, true) },
{ it.quality.contains(quality) }, { it.quality.contains(quality) },
{ it.quality.contains(player, true) },
), ),
).reversed() ).reversed()
} }
@ -161,17 +164,14 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
return fetchAnimeSeasons(res) return fetchAnimeSeasons(res)
} }
private val commentRegex by lazy { Regex("/\\*.*?\\*/", RegexOption.DOT_MATCHES_ALL) }
private val seasonRegex by lazy { Regex("^\\s*panneauAnime\\(\"(.*)\", \"(.*)\"\\)", RegexOption.MULTILINE) }
private fun fetchAnimeSeasons(response: Response): List<SAnime> { private fun fetchAnimeSeasons(response: Response): List<SAnime> {
val animeDoc = response.asJsoup() val animeDoc = response.asJsoup()
val animeUrl = response.request.url val animeUrl = response.request.url
val animeName = animeDoc.getElementById("titreOeuvre")?.text() ?: "" val animeName = animeDoc.getElementById("titreOeuvre")?.text() ?: ""
val seasonRegex = Regex("^\\s*panneauAnime\\(\"(.*)\", \"(.*)\"\\)", RegexOption.MULTILINE)
val scripts = animeDoc.select("h2 + p + div > script, h2 + div > script").toString() val scripts = animeDoc.select("h2 + p + div > script, h2 + div > script").toString()
val uncommented = commentRegex.replace(scripts, "") val animes = seasonRegex.findAll(scripts).flatMapIndexed { animeIndex, seasonMatch ->
val animes = seasonRegex.findAll(uncommented).flatMapIndexed { animeIndex, seasonMatch ->
val (seasonName, seasonStem) = seasonMatch.destructured val (seasonName, seasonStem) = seasonMatch.destructured
if (seasonStem.contains("film", true)) { if (seasonStem.contains("film", true)) {
val moviesUrl = "$animeUrl/$seasonStem" val moviesUrl = "$animeUrl/$seasonStem"
@ -219,16 +219,23 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
private fun fetchPlayers(url: String): List<List<String>> { private fun fetchPlayers(url: String): List<List<String>> {
val docUrl = "$url/episodes.js" val docUrl = "$url/episodes.js"
val doc = client.newCall(GET(docUrl)).execute().use { val players = mutableListOf<List<String>>()
if (!it.isSuccessful) return emptyList() val doc = client.newCall(GET(docUrl)).execute().run {
it.body.string() if (code != 200) {
close()
return listOf()
} }
val urls = QuickJs.create().use { qjs -> body.string()
qjs.evaluate(doc)
val res = qjs.evaluate("JSON.stringify(Array.from({length: 10}, (e,i) => this[`eps\${i}`]).filter(e => e))")
json.decodeFromString<List<List<String>>>(res as String)
} }
return List(urls[0].size) { i -> urls.mapNotNull { it.getOrNull(i) }.distinct() } val sanitizedDoc = sanitizeEpisodesJs(doc)
for (i in 1..8) {
val numPlayers = getPlayers("eps$i", sanitizedDoc)
if (numPlayers != null) players.add(numPlayers)
}
val asPlayers = getPlayers("epsAS", sanitizedDoc)
if (asPlayers != null) players.add(asPlayers)
if (players.isEmpty()) return emptyList()
return List(players[0].size) { i -> players.mapNotNull { it.getOrNull(i) }.distinct() }
} }
private fun getPlayers(playerName: String, doc: String): List<String>? { private fun getPlayers(playerName: String, doc: String): List<String>? {
@ -269,22 +276,6 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_PLAYER_KEY
title = "Lecteur par défaut"
entries = PLAYERS
entryValues = PLAYERS_VALUES
setDefaultValue(PREF_PLAYER_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)
} }
companion object { companion object {
@ -300,25 +291,10 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
"vf", "vf",
) )
private val PLAYERS = arrayOf(
"Sendvid",
"Sibnet",
"VK",
)
private val PLAYERS_VALUES = arrayOf(
"sendvid",
"sibnet",
"vk",
)
private const val PREF_VOICES_KEY = "voices_preference" private const val PREF_VOICES_KEY = "voices_preference"
private const val PREF_VOICES_DEFAULT = "vostfr" private const val PREF_VOICES_DEFAULT = "vostfr"
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080" private const val PREF_QUALITY_DEFAULT = "1080"
private const val PREF_PLAYER_KEY = "player_preference"
private const val PREF_PLAYER_DEFAULT = "sibnet"
} }
} }

View file

@ -9,6 +9,10 @@ object AnimeSamaFilters {
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state) private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
open class TriStateFilterList(name: String, values: List<TriFilter>) : AnimeFilter.Group<AnimeFilter.TriState>(name, values)
class TriFilter(name: String) : AnimeFilter.TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R { private inline fun <reified R> AnimeFilterList.getFirst(): R {
return this.filterIsInstance<R>().first() return this.filterIsInstance<R>().first()
} }
@ -26,6 +30,20 @@ object AnimeSamaFilters {
} }
} }
private inline fun <reified R> AnimeFilterList.parseTriFilter(
options: Array<Pair<String, String>>,
): List<List<String>> {
return (this.getFirst<R>() as TriStateFilterList).state
.filterNot { it.isIgnored() }
.map { filter -> filter.state to filter.name }
.groupBy { it.first }
.let {
val included = it.get(AnimeFilter.TriState.STATE_INCLUDE)?.map { options.find { o -> o.first == it.second }!!.second } ?: emptyList()
val excluded = it.get(AnimeFilter.TriState.STATE_EXCLUDE)?.map { options.find { o -> o.first == it.second }!!.second } ?: emptyList()
listOf(included, excluded)
}
}
class TypesFilter : CheckBoxFilterList( class TypesFilter : CheckBoxFilterList(
"Type", "Type",
AnimeSamaFiltersData.TYPES.map { CheckBoxVal(it.first, false) }, AnimeSamaFiltersData.TYPES.map { CheckBoxVal(it.first, false) },
@ -36,9 +54,9 @@ object AnimeSamaFilters {
AnimeSamaFiltersData.LANGUAGES.map { CheckBoxVal(it.first, false) }, AnimeSamaFiltersData.LANGUAGES.map { CheckBoxVal(it.first, false) },
) )
class GenresFilter : CheckBoxFilterList( class GenresFilter : TriStateFilterList(
"Genre", "Genre",
AnimeSamaFiltersData.GENRES.map { CheckBoxVal(it.first, false) }, AnimeSamaFiltersData.GENRES.map { TriFilter(it.first) },
) )
val FILTER_LIST get() = AnimeFilterList( val FILTER_LIST get() = AnimeFilterList(
@ -50,15 +68,19 @@ object AnimeSamaFilters {
data class SearchFilters( data class SearchFilters(
val types: List<String> = emptyList(), val types: List<String> = emptyList(),
val language: List<String> = emptyList(), val language: List<String> = emptyList(),
val genres: List<String> = emptyList(), val include: List<String> = emptyList(),
val exclude: List<String> = emptyList(),
) )
fun getSearchFilters(filters: AnimeFilterList): SearchFilters { fun getSearchFilters(filters: AnimeFilterList): SearchFilters {
if (filters.isEmpty()) return SearchFilters() if (filters.isEmpty()) return SearchFilters()
val (include, exclude) = filters.parseTriFilter<GenresFilter>(AnimeSamaFiltersData.GENRES)
return SearchFilters( return SearchFilters(
filters.parseCheckbox<TypesFilter>(AnimeSamaFiltersData.TYPES), filters.parseCheckbox<TypesFilter>(AnimeSamaFiltersData.TYPES),
filters.parseCheckbox<LangFilter>(AnimeSamaFiltersData.LANGUAGES), filters.parseCheckbox<LangFilter>(AnimeSamaFiltersData.LANGUAGES),
filters.parseCheckbox<GenresFilter>(AnimeSamaFiltersData.GENRES), include,
exclude,
) )
} }
@ -72,111 +94,31 @@ object AnimeSamaFilters {
val LANGUAGES = arrayOf( val LANGUAGES = arrayOf(
Pair("VF", "VF"), Pair("VF", "VF"),
Pair("VOSTFR", "VOSTFR"), Pair("VOSTFR", "VOSTFR"),
Pair("VASTFR", "VASTFR"),
) )
val GENRES = arrayOf( val GENRES = arrayOf(
Pair("Action", "Action"), Pair("Action", "Action"),
Pair("Adolescence", "Adolescence"),
Pair("Aliens / Extra-terrestres", "Aliens / Extra-terrestres"),
Pair("Amitié", "Amitié"),
Pair("Amour", "Amour"),
Pair("Apocalypse", "Apocalypse"),
Pair("Art", "Art"),
Pair("Arts martiaux", "Arts martiaux"),
Pair("Assassinat", "Assassinat"),
Pair("Autre monde", "Autre monde"),
Pair("Aventure", "Aventure"), Pair("Aventure", "Aventure"),
Pair("Combats", "Combats"), Pair("Combats", "Combats"),
Pair("Comédie", "Comédie"), Pair("Comédie", "Comédie"),
Pair("Crime", "Crime"),
Pair("Cyberpunk", "Cyberpunk"),
Pair("Danse", "Danse"),
Pair("Démons", "Démons"),
Pair("Détective", "Détective"),
Pair("Donghua", "Donghua"),
Pair("Drame", "Drame"), Pair("Drame", "Drame"),
Pair("Ecchi", "Ecchi"), Pair("Ecchi", "Ecchi"),
Pair("Ecole", "Ecole"), Pair("École", "School-Life"),
Pair("Enquête", "Enquête"), Pair("Fantaisie", "Fantasy"),
Pair("Famille", "Famille"),
Pair("Fantastique", "Fantastique"),
Pair("Fantasy", "Fantasy"),
Pair("Fantômes", "Fantômes"),
Pair("Futur", "Futur"),
Pair("Ghibli", "Ghibli"),
Pair("Guerre", "Guerre"),
Pair("Harcèlement", "Harcèlement"),
Pair("Harem", "Harem"),
Pair("Harem inversé", "Harem inversé"),
Pair("Histoire", "Histoire"),
Pair("Historique", "Historique"),
Pair("Horreur", "Horreur"), Pair("Horreur", "Horreur"),
Pair("Isekai", "Isekai"), Pair("Isekai", "Isekai"),
Pair("Jeunesse", "Jeunesse"),
Pair("Jeux", "Jeux"),
Pair("Jeux vidéo", "Jeux vidéo"),
Pair("Josei", "Josei"), Pair("Josei", "Josei"),
Pair("Journalisme", "Journalisme"),
Pair("Mafia", "Mafia"),
Pair("Magical girl", "Magical girl"),
Pair("Magie", "Magie"),
Pair("Maladie", "Maladie"),
Pair("Mariage", "Mariage"),
Pair("Mature", "Mature"),
Pair("Mechas", "Mechas"),
Pair("Médiéval", "Médiéval"),
Pair("Militaire", "Militaire"),
Pair("Monde virtuel", "Monde virtuel"),
Pair("Monstres", "Monstres"),
Pair("Musique", "Musique"),
Pair("Mystère", "Mystère"), Pair("Mystère", "Mystère"),
Pair("Nekketsu", "Nekketsu"),
Pair("Ninjas", "Ninjas"),
Pair("Nostalgie", "Nostalgie"),
Pair("Paranormal", "Paranormal"),
Pair("Philosophie", "Philosophie"),
Pair("Pirates", "Pirates"),
Pair("Police", "Police"),
Pair("Politique", "Politique"),
Pair("Post-apocalyptique", "Post-apocalyptique"),
Pair("Pouvoirs psychiques", "Pouvoirs psychiques"),
Pair("Préhistoire", "Préhistoire"),
Pair("Prison", "Prison"),
Pair("Psychologique", "Psychologique"), Pair("Psychologique", "Psychologique"),
Pair("Quotidien", "Quotidien"), Pair("Quotidien", "Slice-of-Life"),
Pair("Religion", "Religion"),
Pair("Réincarnation / Transmigration", "Réincarnation / Transmigration"),
Pair("Romance", "Romance"), Pair("Romance", "Romance"),
Pair("Samouraïs", "Samouraïs"),
Pair("School Life", "School Life"),
Pair("Science-Fantasy", "Science-Fantasy"),
Pair("Science-fiction", "Science-fiction"),
Pair("Scientifique", "Scientifique"),
Pair("Seinen", "Seinen"), Pair("Seinen", "Seinen"),
Pair("Shôjo", "Shôjo"),
Pair("Shônen", "Shônen"), Pair("Shônen", "Shônen"),
Pair("Shônen-Ai", "Shônen-Ai"), Pair("Shôjo", "Shôjo"),
Pair("Slice of Life", "Slice of Life"), Pair("Sports", "Sports"),
Pair("Société", "Société"),
Pair("Sport", "Sport"),
Pair("Super pouvoirs", "Super pouvoirs"),
Pair("Super-héros", "Super-héros"),
Pair("Surnaturel", "Surnaturel"), Pair("Surnaturel", "Surnaturel"),
Pair("Survie", "Survie"),
Pair("Survival game", "Survival game"),
Pair("Technologies", "Technologies"),
Pair("Thriller", "Thriller"),
Pair("Tournois", "Tournois"), Pair("Tournois", "Tournois"),
Pair("Travail", "Travail"),
Pair("Vampires", "Vampires"),
Pair("Vengeance", "Vengeance"),
Pair("Voyage", "Voyage"),
Pair("Voyage temporel", "Voyage temporel"),
Pair("Webcomic", "Webcomic"),
Pair("Yakuza", "Yakuza"),
Pair("Yaoi", "Yaoi"), Pair("Yaoi", "Yaoi"),
Pair("Yokai", "Yokai"),
Pair("Yuri", "Yuri"), Pair("Yuri", "Yuri"),
) )
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'AniSama' extName = 'AniSama'
extClass = '.AniSama' extClass = '.AniSama'
extVersionCode = 15 extVersionCode = 13
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'EmpireStreaming' extName = 'EmpireStreaming'
extClass = '.EmpireStreaming' extClass = '.EmpireStreaming'
extVersionCode = 21 extVersionCode = 19
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.FrenchAnime' extClass = '.FrenchAnime'
themePkg = 'datalifeengine' themePkg = 'datalifeengine'
baseUrl = 'https://french-anime.com' baseUrl = 'https://french-anime.com'
overrideVersionCode = 20 overrideVersionCode = 18
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'OtakuFR' extName = 'OtakuFR'
extClass = '.OtakuFR' extClass = '.OtakuFR'
extVersionCode = 29 extVersionCode = 27
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Vostfree' extName = 'Vostfree'
extClass = '.Vostfree' extClass = '.Vostfree'
extVersionCode = 29 extVersionCode = 27
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -3,7 +3,7 @@ ext {
extClass = '.Wiflix' extClass = '.Wiflix'
themePkg = 'datalifeengine' themePkg = 'datalifeengine'
baseUrl = 'https://wiflix.voto' baseUrl = 'https://wiflix.voto'
overrideVersionCode = 18 overrideVersionCode = 16
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'StreamingCommunity' extName = 'StreamingCommunity'
extClass = '.StreamingCommunity' extClass = '.StreamingCommunity'
extVersionCode = 7 extVersionCode = 6
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -30,7 +30,7 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "StreamingCommunity" override val name = "StreamingCommunity"
override val baseUrl = "https://streamingcommunity.ovh" override val baseUrl = "https://streamingcommunity.spa"
override val lang = "it" override val lang = "it"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Toonitalia' extName = 'Toonitalia'
extClass = '.Toonitalia' extClass = '.Toonitalia'
extVersionCode = 28 extVersionCode = 26
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Animeler' extName = 'Animeler'
extClass = '.Animeler' extClass = '.Animeler'
extVersionCode = 20 extVersionCode = 18
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Anizm' extName = 'Anizm'
extClass = '.Anizm' extClass = '.Anizm'
extVersionCode = 29 extVersionCode = 27
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'TR Anime Izle' extName = 'TR Anime Izle'
extClass = '.TRAnimeIzle' extClass = '.TRAnimeIzle'
extVersionCode = 25 extVersionCode = 23
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Türk Anime TV' extName = 'Türk Anime TV'
extClass = '.TurkAnime' extClass = '.TurkAnime'
extVersionCode = 38 extVersionCode = 36
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hanime1' extName = 'Hanime1'
extClass = '.Hanime1' extClass = '.Hanime1'
extVersionCode = 5 extVersionCode = 4
isNsfw = true isNsfw = true
} }

View file

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.animeextension.zh.hanime1
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
@ -17,15 +16,12 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie import okhttp3.Cookie
@ -69,32 +65,14 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
} }
override fun animeDetailsParse(response: Response): SAnime { override fun animeDetailsParse(response: Response): SAnime {
val doc = response.asJsoup() val jsoup = response.asJsoup()
return SAnime.create().apply { return SAnime.create().apply {
genre = doc.select(".single-video-tag").not("[data-toggle]").eachText().joinToString() genre = jsoup.select(".single-video-tag").not("[data-toggle]").eachText().joinToString()
author = doc.select("#video-artist-name").text() author = jsoup.select("#video-artist-name").text()
doc.select("script[type=application/ld+json]").first()?.data()?.let { jsoup.select("script[type=application/ld+json]").first()?.data()?.let {
val info = json.decodeFromString<JsonElement>(it).jsonObject val info = json.decodeFromString<JsonElement>(it).jsonObject
title = info["name"]!!.jsonPrimitive.content title = info["name"]!!.jsonPrimitive.content
description = info["description"]!!.jsonPrimitive.content description = info["description"]!!.jsonPrimitive.content
thumbnail_url = info["thumbnailUrl"]?.jsonArray?.get(0)?.jsonPrimitive?.content
}
val type = doc.select("a#video-artist-name + a").text().trim()
if (type == "裏番" || type == "泡麵番") {
// Use the series cover image for bangumi entries instead of the episode image.
runBlocking {
try {
val animesPage =
getSearchAnime(
1,
title,
AnimeFilterList(GenreFilter(arrayOf("", type)).apply { state = 1 }),
)
thumbnail_url = animesPage.animes.first().thumbnail_url
} catch (e: Exception) {
Log.e(name, "Failed to get bangumi cover image")
}
}
} }
} }
} }
@ -159,7 +137,7 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
val jsoup = response.asJsoup() val jsoup = response.asJsoup()
val nodes = jsoup.select("div.search-doujin-videos.hidden-xs:not(:has(a[target=_blank]))") val nodes = jsoup.select("div.search-doujin-videos.hidden-xs")
val list = if (nodes.isNotEmpty()) { val list = if (nodes.isNotEmpty()) {
nodes.map { nodes.map {
SAnime.create().apply { SAnime.create().apply {
@ -238,12 +216,11 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
return chain.proceed(chain.request()) return chain.proceed(chain.request())
} }
@OptIn(DelicateCoroutinesApi::class)
private fun updateFilters() { private fun updateFilters() {
filterUpdateState = FilterUpdateState.UPDATING filterUpdateState = FilterUpdateState.UPDATING
val exceptionHandler = val exceptionHandler =
CoroutineExceptionHandler { _, _ -> filterUpdateState = FilterUpdateState.FAILED } CoroutineExceptionHandler { _, _ -> filterUpdateState = FilterUpdateState.FAILED }
GlobalScope.launch(Dispatchers.IO + exceptionHandler) { CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
val jsoup = client.newCall(GET("$baseUrl/search")).awaitSuccess().asJsoup() val jsoup = client.newCall(GET("$baseUrl/search")).awaitSuccess().asJsoup()
val genreList = jsoup.select("div.genre-option div.hentai-sort-options").eachText() val genreList = jsoup.select("div.genre-option div.hentai-sort-options").eachText()
val sortList = val sortList =

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Xfani' extName = 'Xfani'
extClass = '.Xfani' extClass = '.Xfani'
extVersionCode = 6 extVersionCode = 5
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -20,9 +20,8 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@ -54,7 +53,7 @@ enum class FilterUpdateState {
class Xfani : AnimeHttpSource(), ConfigurableAnimeSource { class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
override val baseUrl: String override val baseUrl: String
get() = "https://dm.xifanacg.com" get() = "https://dick.xfani.com"
override val lang: String override val lang: String
get() = "zh" get() = "zh"
override val name: String override val name: String
@ -118,16 +117,10 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
} }
override fun animeDetailsParse(response: Response): SAnime { override fun animeDetailsParse(response: Response): SAnime {
val doc = response.asJsoup() val jsoup = response.asJsoup()
return SAnime.create().apply { return SAnime.create().apply {
description = doc.select("#height_limit.text").text() description = jsoup.select("#height_limit.text").text()
title = doc.select(".slide-info-title").text() title = jsoup.select(".slide-info-title").text()
author = doc.select(".slide-info:contains(导演 :)").text().removePrefix("导演 :")
.removeSuffix(",")
artist = doc.select(".slide-info:contains(演员 :)").text().removePrefix("演员 :")
.removeSuffix(",")
genre = doc.select(".slide-info:contains(类型 :)").text().removePrefix("类型 :")
.removeSuffix(",").replace(",", ", ")
} }
} }
@ -241,13 +234,19 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
return vodListToAnimePageList(response) return vodListToAnimePageList(response)
} }
val jsoup = response.asJsoup() val jsoup = response.asJsoup()
val items = jsoup.select("div.search-list") val items = jsoup.select("div.public-list-box.search-box.flex.rel")
val animeList = items.map { item -> val animeList = items.map { item ->
SAnime.create().apply { SAnime.create().apply {
title = item.select("div.detail-info > a").text() title = item.select(".thumb-txt").text()
url = item.select("div.detail-info > a").attr("href") url = item.select("div.left.public-list-bj a.public-list-exp").attr("href")
thumbnail_url = thumbnail_url =
item.select("div.detail-pic img[data-src]").attr("data-src") item.select("div.left.public-list-bj img[data-src]").attr("data-src")
author = item.select("div.thumb-actor").text().removeSuffix("/")
artist = item.select("div.thumb-director").text().removeSuffix("/")
description = item.select(".thumb-blurb").text()
genre = item.select("div.thumb-else").text()
val statusString = item.select("div.left.public-list-bj .public-list-prb").text()
status = STATUS_STR_MAPPING.getOrElse(statusString) { SAnime.ONGOING }
} }
} }
val tip = jsoup.select("div.pages div.page-tip").text() val tip = jsoup.select("div.pages div.page-tip").text()
@ -260,13 +259,12 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
return numbers.size == 2 && numbers[0] != numbers[1] return numbers.size == 2 && numbers[0] != numbers[1]
} }
@OptIn(DelicateCoroutinesApi::class)
private fun updateFilter() { private fun updateFilter() {
filterState = FilterUpdateState.UPDATING filterState = FilterUpdateState.UPDATING
val handler = CoroutineExceptionHandler { _, _ -> val handler = CoroutineExceptionHandler { _, _ ->
filterState = FilterUpdateState.FAILED filterState = FilterUpdateState.FAILED
} }
GlobalScope.launch(Dispatchers.IO + handler) { CoroutineScope(Dispatchers.IO + handler).launch {
val jsoup = client.newCall(GET("$baseUrl/show/1/html")).awaitSuccess().asJsoup() val jsoup = client.newCall(GET("$baseUrl/show/1/html")).awaitSuccess().asJsoup()
// update class and year filter type // update class and year filter type
val classList = jsoup.select("li[data-type=class]").eachAttr("data-val") val classList = jsoup.select("li[data-type=class]").eachAttr("data-val")
@ -395,5 +393,9 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
const val PREF_KEY_FILTER_YEAR = "PREF_KEY_FILTER_YEAR" const val PREF_KEY_FILTER_YEAR = "PREF_KEY_FILTER_YEAR"
const val DEFAULT_VIDEO_SOURCE = "0" const val DEFAULT_VIDEO_SOURCE = "0"
val STATUS_STR_MAPPING = mapOf(
"已完结" to SAnime.COMPLETED,
)
} }
} }