Compare commits

...
Sign in to create a new pull request.

11 commits
main ... main

Author SHA1 Message Date
db50768403 Update urls 2025-06-21 00:11:27 -05:00
9c55a842ec revert 8e5f14d70e
revert Added a sort by codec feature to animetorrentio extension (#961)

Checklist:

- [x] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [ ] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [x] Have not changed source names
- [x] Have explicitly kept the `id` if a source's name or language were changed
- [x] Have tested the modifications by compiling and running the extension through Android Studio
- [ ] Have removed `web_hi_res_512.png` when adding a new extension
- [ ] Have made sure all the icons are in png format

Co-authored-by: worldInColors <almonzer1234567@gmail.com>
Reviewed-on: Kohi-den/extensions-source#961
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2025-06-20 01:18:38 -05:00
1018982a2e Some chinese sources update (#1022)
Close #957

Checklist:

- [x] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [x] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [x] Have not changed source names
- [ ] Have explicitly kept the `id` if a source's name or language were changed
- [x] Have tested the modifications by compiling and running the extension through Android Studio
- [ ] Have removed `web_hi_res_512.png` when adding a new extension
- [ ] Have made sure all the icons are in png format

Co-authored-by: AlphaBoom <30779939+AlphaBoom@users.noreply.github.com>
Co-authored-by: ZhendongWu <30779939+AlphaBoom@users.noreply.github.com>
Reviewed-on: Kohi-den/extensions-source#1022
Co-authored-by: AlphaBoom <alphaboom@noreply.localhost>
Co-committed-by: AlphaBoom <alphaboom@noreply.localhost>
2025-06-20 01:05:04 -05:00
f97d742c40 feat(fr/animesama): update AnimeSama with new filters, improved search functionality and add a default player preference in param (#1018)
Checklist:

- [X] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [X] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [X] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [X] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [X] Have not changed source names
- [X] Have explicitly kept the `id` if a source's name or language were changed
- [X] Have tested the modifications by compiling and running the extension through Android Studio
- [X] Have removed `web_hi_res_512.png` when adding a new extension
- [X] Have made sure all the icons are in png format

Reviewed-on: Kohi-den/extensions-source#1018
Co-authored-by: Mathis <mathis.quemener@gmail.com>
Co-committed-by: Mathis <mathis.quemener@gmail.com>
2025-06-20 01:04:42 -05:00
Ghost
9581c675cf Closes #1000 (#1001)
Checklist:

Closes #1000

- [ ] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [ ] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [ ] Have not changed source names
- [ ] Have explicitly kept the `id` if a source's name or language were changed
- [ ] Have tested the modifications by compiling and running the extension through Android Studio
- [ ] Have removed `web_hi_res_512.png` when adding a new extension
- [ ] Have made sure all the icons are in png format

Co-authored-by: GraveEaterMadison <GraveEaterMadison@users.noreply.github.com>
Co-authored-by: Thinker <170967310+GraveEaterMadison@users.noreply.github.com>
Co-authored-by: Zero <170967310+GraveEaterMadison@users.noreply.github.com>
Reviewed-on: Kohi-den/extensions-source#1001
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2025-06-20 01:03:32 -05:00
Ghost
8e5f14d70e Added a sort by codec feature to animetorrentio extension (#961)
Checklist:

- [x] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [ ] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [x] Have not changed source names
- [x] Have explicitly kept the `id` if a source's name or language were changed
- [x] Have tested the modifications by compiling and running the extension through Android Studio
- [ ] Have removed `web_hi_res_512.png` when adding a new extension
- [ ] Have made sure all the icons are in png format

Co-authored-by: worldInColors <almonzer1234567@gmail.com>
Reviewed-on: Kohi-den/extensions-source#961
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2025-06-20 01:03:02 -05:00
546ad886f2 ci: added Increase buffer size
this is needed to deploy the repo,
2025-06-20 00:42:21 -05:00
Kohi-den-Bot
29c3b5978d
[skip ci] chore: Mass-bump on extensions 2025-06-20 05:13:49 +00:00
Kohi-den-Bot
2b3563ab20
[skip ci] chore: Mass-bump on extensions 2025-06-20 04:39:52 +00:00
Ghost
0ba38fb4a7 Voe: adapt to changes in obfuscation (Kohi-den#959) (#960)
fixes #959

Checklist:

- [x] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [x] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [x] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [x] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [x] Have not changed source names
- [x] Have explicitly kept the `id` if a source's name or language were changed
- [x] Have tested the modifications by compiling and running the extension through Android Studio
- [x] Have removed `web_hi_res_512.png` when adding a new extension
- [x] Have made sure all the icons are in png format

Co-authored-by: Sphereso <spheresox@gmail.com>
Reviewed-on: Kohi-den/extensions-source#960
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2025-06-19 23:38:05 -05:00
b1d2972f9f ci: increased ci chunk size and removed (chunk ${{ matrix.chunk }}) in build extensions from name 2025-06-19 23:37:08 -05:00
72 changed files with 315 additions and 202 deletions

View file

@ -48,7 +48,7 @@ body:
required: true
- label: I have written a title with source name.
required: true
- 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.
- 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.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View file

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

View file

@ -195,6 +195,10 @@ jobs:
- name: Sync repo
run: |
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
uses: https://github.com/EndBug/add-and-commit@v9

View file

@ -18,14 +18,33 @@ class VoeExtractor(private val client: OkHttpClient) {
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
data class VideoLinkDTO(val file: String)
data class VideoLinkDTO(val source: 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> {
var document = clientDdos.newCall(GET(url)).execute().asJsoup()
@ -38,25 +57,12 @@ class VoeExtractor(private val client: OkHttpClient) {
document = clientDdos.newCall(GET(originalUrl)).execute().asJsoup()
}
val alternativeScript = document.select("script").find { scriptBase64Regex.containsMatchIn(it.data()) }?.data()
val script = document.selectFirst("script:containsData(const sources), script:containsData(var sources), script:containsData(wc0)")?.data()
?: alternativeScript
?: return emptyList()
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)
val encodedVoeData = document.select("script").find { it.data().contains("MKGMa=\"")}?.data()
?.substringAfter("MKGMa=\"")
?.substringBefore('"') ?: return emptyList()
val playlistUrl = decodeVoeData(encodedVoeData)
json.decodeFromString<VideoLinkDTO>(if (alternativeScript != null) decoded.reversed() else decoded).file
}
else -> return emptyList()
}
return playlistUtils.extractFromHls(playlistUrl,
videoNameGen = { quality -> "${prefix}Voe:$quality" }
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMap
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
@ -49,12 +51,11 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular ===============================
override fun popularAnimeParse(response: Response): AnimesPage {
val doc = response.body.string()
val doc = response.asJsoup()
val page = response.request.url.fragment?.toInt() ?: 0
val regex = Regex("^\\s*carteClassique\\(\\s*.*?\\s*,\\s*\"(.*?)\".*\\)", RegexOption.MULTILINE)
val chunks = regex.findAll(doc).chunked(5).toList()
val chunks = doc.select("#containerPepites > div a").chunked(5)
val seasons = chunks.getOrNull(page - 1)?.flatMap {
val animeUrl = "$baseUrl/catalogue/${it.groupValues[1]}"
val animeUrl = "$baseUrl${it.attr("href")}"
fetchAnimeSeasons(animeUrl)
}?.toList().orEmpty()
return AnimesPage(seasons, page < chunks.size)
@ -72,7 +73,7 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
.removePathSegment(animeUrl.pathSize - 3)
.build()
fetchAnimeSeasons(url.toString())
}
}.distinctBy { it.url }
return AnimesPage(seasons, false)
}
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl)
@ -80,29 +81,26 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
// =============================== Search ===============================
override fun getFilterList() = AnimeSamaFilters.FILTER_LIST
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
if (query.startsWith(PREFIX_SEARCH)) {
return AnimesPage(fetchAnimeSeasons("$baseUrl/catalogue/${query.removePrefix(PREFIX_SEARCH)}/"), false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val url = "$baseUrl/catalogue/".toHttpUrl().newBuilder()
val params = AnimeSamaFilters.getSearchFilters(filters)
val elements = database
.asSequence()
.filter { it.select("h1, p").fold(false) { v, e -> v || e.text().contains(query, true) } }
.filter { params.include.all { p -> it.className().contains(p) } }
.filter { params.exclude.none { p -> it.className().contains(p) } }
.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)
params.types.forEach { url.addQueryParameter("type[]", it) }
params.language.forEach { url.addQueryParameter("langue[]", it) }
params.genres.forEach { url.addQueryParameter("genre[]", it) }
url.addQueryParameter("search", query)
url.addQueryParameter("page", "$page")
return GET(url.build(), headers)
}
override fun searchAnimeParse(response: Response): AnimesPage = throw UnsupportedOperationException()
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw UnsupportedOperationException()
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
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 ============================
override suspend fun getAnimeDetails(anime: SAnime): SAnime = anime
@ -121,6 +119,10 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> = throw UnsupportedOperationException()
// ============================ 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> {
val playerUrls = json.decodeFromString<List<List<String>>>(episode.url)
val videos = playerUrls.flatMapIndexed { i, it ->
@ -128,9 +130,9 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
it.parallelCatchingFlatMap { playerUrl ->
with(playerUrl) {
when {
contains("sibnet.ru") -> SibnetExtractor(client).videosFromUrl(playerUrl, prefix)
contains("vk.") -> VkExtractor(client, headers).videosFromUrl(playerUrl, prefix)
contains("sendvid.com") -> SendvidExtractor(client, headers).videosFromUrl(playerUrl, prefix)
contains("sibnet.ru") -> sibnetExtractor.videosFromUrl(playerUrl, prefix)
contains("vk.") -> vkExtractor.videosFromUrl(playerUrl, prefix)
contains("sendvid.com") -> sendvidExtractor.videosFromUrl(playerUrl, prefix)
else -> emptyList()
}
}
@ -140,21 +142,16 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
}
// ============================ 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> {
val voices = preferences.getString(PREF_VOICES_KEY, PREF_VOICES_DEFAULT)!!
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val player = preferences.getString(PREF_PLAYER_KEY, PREF_PLAYER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(voices, true) },
{ it.quality.contains(quality) },
{ it.quality.contains(player, true) },
),
).reversed()
}
@ -164,14 +161,17 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
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> {
val animeDoc = response.asJsoup()
val animeUrl = response.request.url
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 animes = seasonRegex.findAll(scripts).flatMapIndexed { animeIndex, seasonMatch ->
val uncommented = commentRegex.replace(scripts, "")
val animes = seasonRegex.findAll(uncommented).flatMapIndexed { animeIndex, seasonMatch ->
val (seasonName, seasonStem) = seasonMatch.destructured
if (seasonStem.contains("film", true)) {
val moviesUrl = "$animeUrl/$seasonStem"
@ -219,23 +219,16 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
private fun fetchPlayers(url: String): List<List<String>> {
val docUrl = "$url/episodes.js"
val players = mutableListOf<List<String>>()
val doc = client.newCall(GET(docUrl)).execute().run {
if (code != 200) {
close()
return listOf()
}
body.string()
val doc = client.newCall(GET(docUrl)).execute().use {
if (!it.isSuccessful) return emptyList()
it.body.string()
}
val sanitizedDoc = sanitizeEpisodesJs(doc)
for (i in 1..8) {
val numPlayers = getPlayers("eps$i", sanitizedDoc)
if (numPlayers != null) players.add(numPlayers)
val urls = QuickJs.create().use { qjs ->
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)
}
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() }
return List(urls[0].size) { i -> urls.mapNotNull { it.getOrNull(i) }.distinct() }
}
private fun getPlayers(playerName: String, doc: String): List<String>? {
@ -276,6 +269,22 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit()
}
}.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 {
@ -291,10 +300,25 @@ class AnimeSama : ConfigurableAnimeSource, AnimeHttpSource() {
"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_DEFAULT = "vostfr"
private const val PREF_QUALITY_KEY = "preferred_quality"
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,10 +9,6 @@ object AnimeSamaFilters {
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 {
return this.filterIsInstance<R>().first()
}
@ -30,20 +26,6 @@ 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(
"Type",
AnimeSamaFiltersData.TYPES.map { CheckBoxVal(it.first, false) },
@ -54,9 +36,9 @@ object AnimeSamaFilters {
AnimeSamaFiltersData.LANGUAGES.map { CheckBoxVal(it.first, false) },
)
class GenresFilter : TriStateFilterList(
class GenresFilter : CheckBoxFilterList(
"Genre",
AnimeSamaFiltersData.GENRES.map { TriFilter(it.first) },
AnimeSamaFiltersData.GENRES.map { CheckBoxVal(it.first, false) },
)
val FILTER_LIST get() = AnimeFilterList(
@ -68,19 +50,15 @@ object AnimeSamaFilters {
data class SearchFilters(
val types: List<String> = emptyList(),
val language: List<String> = emptyList(),
val include: List<String> = emptyList(),
val exclude: List<String> = emptyList(),
val genres: List<String> = emptyList(),
)
fun getSearchFilters(filters: AnimeFilterList): SearchFilters {
if (filters.isEmpty()) return SearchFilters()
val (include, exclude) = filters.parseTriFilter<GenresFilter>(AnimeSamaFiltersData.GENRES)
return SearchFilters(
filters.parseCheckbox<TypesFilter>(AnimeSamaFiltersData.TYPES),
filters.parseCheckbox<LangFilter>(AnimeSamaFiltersData.LANGUAGES),
include,
exclude,
filters.parseCheckbox<GenresFilter>(AnimeSamaFiltersData.GENRES),
)
}
@ -94,31 +72,111 @@ object AnimeSamaFilters {
val LANGUAGES = arrayOf(
Pair("VF", "VF"),
Pair("VOSTFR", "VOSTFR"),
Pair("VASTFR", "VASTFR"),
)
val GENRES = arrayOf(
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("Combats", "Combats"),
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("Ecchi", "Ecchi"),
Pair("École", "School-Life"),
Pair("Fantaisie", "Fantasy"),
Pair("Ecole", "Ecole"),
Pair("Enquête", "Enquête"),
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("Isekai", "Isekai"),
Pair("Jeunesse", "Jeunesse"),
Pair("Jeux", "Jeux"),
Pair("Jeux vidéo", "Jeux vidéo"),
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("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("Quotidien", "Slice-of-Life"),
Pair("Quotidien", "Quotidien"),
Pair("Religion", "Religion"),
Pair("Réincarnation / Transmigration", "Réincarnation / Transmigration"),
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("Shônen", "Shônen"),
Pair("Shôjo", "Shôjo"),
Pair("Sports", "Sports"),
Pair("Shônen", "Shônen"),
Pair("Shônen-Ai", "Shônen-Ai"),
Pair("Slice of Life", "Slice of Life"),
Pair("Société", "Société"),
Pair("Sport", "Sport"),
Pair("Super pouvoirs", "Super pouvoirs"),
Pair("Super-héros", "Super-héros"),
Pair("Surnaturel", "Surnaturel"),
Pair("Survie", "Survie"),
Pair("Survival game", "Survival game"),
Pair("Technologies", "Technologies"),
Pair("Thriller", "Thriller"),
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("Yokai", "Yokai"),
Pair("Yuri", "Yuri"),
)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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