diff --git a/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt b/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt index 40878ebc..1e30fb57 100644 --- a/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt +++ b/lib/voe-extractor/src/main/java/eu/kanade/tachiyomi/lib/voeextractor/VoeExtractor.kt @@ -22,6 +22,8 @@ class VoeExtractor(private val client: OkHttpClient) { 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) @@ -36,8 +38,9 @@ class VoeExtractor(private val client: OkHttpClient) { document = clientDdos.newCall(GET(originalUrl)).execute().asJsoup() } - val script = document.selectFirst("script:containsData(const sources), script:containsData(var sources), script:containsData(wc0)") - ?.data() + 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 @@ -46,10 +49,11 @@ class VoeExtractor(private val client: OkHttpClient) { if (linkRegex.matches(link)) link else String(Base64.decode(link, Base64.DEFAULT)) } // Layout 2 - script.contains("wc0") -> { + script.contains("wc0") || alternativeScript != null -> { val base64 = base64Regex.find(script)!!.value val decoded = Base64.decode(base64, Base64.DEFAULT).let(::String) - json.decodeFromString(decoded).file + + json.decodeFromString(if (alternativeScript != null) decoded.reversed() else decoded).file } else -> return emptyList() } diff --git a/src/all/chineseanime/build.gradle b/src/all/chineseanime/build.gradle index 70141c43..0ef78665 100644 --- a/src/all/chineseanime/build.gradle +++ b/src/all/chineseanime/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.ChineseAnime' themePkg = 'animestream' baseUrl = 'https://www.chineseanime.vip' - overrideVersionCode = 8 + overrideVersionCode = 9 } apply from: "$rootDir/common.gradle" diff --git a/src/all/javguru/build.gradle b/src/all/javguru/build.gradle index 4d328596..7da90918 100644 --- a/src/all/javguru/build.gradle +++ b/src/all/javguru/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Jav Guru' extClass = '.JavGuru' - extVersionCode = 15 + extVersionCode = 16 isNsfw = true } diff --git a/src/all/lmanime/build.gradle b/src/all/lmanime/build.gradle index 4710fec1..bafe69d0 100644 --- a/src/all/lmanime/build.gradle +++ b/src/all/lmanime/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.LMAnime' themePkg = 'animestream' baseUrl = 'https://lmanime.com' - overrideVersionCode = 6 + overrideVersionCode = 7 } apply from: "$rootDir/common.gradle" diff --git a/src/all/supjav/build.gradle b/src/all/supjav/build.gradle index b3bad3c1..beb008ea 100644 --- a/src/all/supjav/build.gradle +++ b/src/all/supjav/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'SupJav' extClass = '.SupJavFactory' - extVersionCode = 9 + extVersionCode = 10 isNsfw = true } diff --git a/src/ar/anime4up/build.gradle b/src/ar/anime4up/build.gradle index 54b07aca..c78aef15 100644 --- a/src/ar/anime4up/build.gradle +++ b/src/ar/anime4up/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Anime4up' extClass = '.Anime4Up' - extVersionCode = 57 + extVersionCode = 58 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/animerco/build.gradle b/src/ar/animerco/build.gradle index dad3de1c..7676a28e 100644 --- a/src/ar/animerco/build.gradle +++ b/src/ar/animerco/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Animerco' extClass = '.Animerco' - extVersionCode = 37 + extVersionCode = 38 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/arabseed/build.gradle b/src/ar/arabseed/build.gradle index 7f6ac620..cc4d4962 100644 --- a/src/ar/arabseed/build.gradle +++ b/src/ar/arabseed/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Arab Seed' extClass = '.ArabSeed' - extVersionCode = 13 + extVersionCode = 14 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/asia2tv/build.gradle b/src/ar/asia2tv/build.gradle index 14987244..eea0e809 100644 --- a/src/ar/asia2tv/build.gradle +++ b/src/ar/asia2tv/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'asia2tv' extClass = '.Asia2TV' - extVersionCode = 18 + extVersionCode = 19 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/egydead/build.gradle b/src/ar/egydead/build.gradle index f55a495f..2ad2e37e 100644 --- a/src/ar/egydead/build.gradle +++ b/src/ar/egydead/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Egy Dead' extClass = '.EgyDead' - extVersionCode = 12 + extVersionCode = 13 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/okanime/build.gradle b/src/ar/okanime/build.gradle index 6fa1f114..a574966d 100644 --- a/src/ar/okanime/build.gradle +++ b/src/ar/okanime/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Okanime' extClass = '.Okanime' - extVersionCode = 10 + extVersionCode = 11 } apply from: "$rootDir/common.gradle" diff --git a/src/de/animebase/build.gradle b/src/de/animebase/build.gradle index 5feedbe2..2a1a3e80 100644 --- a/src/de/animebase/build.gradle +++ b/src/de/animebase/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Anime-Base' extClass = '.AnimeBase' - extVersionCode = 24 + extVersionCode = 25 isNsfw = true } diff --git a/src/de/animeloads/build.gradle b/src/de/animeloads/build.gradle index 69a8bb45..443c1111 100644 --- a/src/de/animeloads/build.gradle +++ b/src/de/animeloads/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Anime-Loads' extClass = '.AnimeLoads' - extVersionCode = 15 + extVersionCode = 16 isNsfw = true } diff --git a/src/de/animetoast/build.gradle b/src/de/animetoast/build.gradle index e3f402e4..b753c5ca 100644 --- a/src/de/animetoast/build.gradle +++ b/src/de/animetoast/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'AnimeToast' extClass = '.AnimeToast' - extVersionCode = 14 + extVersionCode = 15 } apply from: "$rootDir/common.gradle" diff --git a/src/de/aniworld/build.gradle b/src/de/aniworld/build.gradle index 6734fb8a..c7486c8e 100644 --- a/src/de/aniworld/build.gradle +++ b/src/de/aniworld/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'AniWorld' extClass = '.AniWorld' - extVersionCode = 23 + extVersionCode = 24 } apply from: "$rootDir/common.gradle" diff --git a/src/de/cineclix/build.gradle b/src/de/cineclix/build.gradle index 2854333f..069ad729 100644 --- a/src/de/cineclix/build.gradle +++ b/src/de/cineclix/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'CineClix' extClass = '.CineClix' - extVersionCode = 14 + extVersionCode = 15 } apply from: "$rootDir/common.gradle" diff --git a/src/de/cinemathek/build.gradle b/src/de/cinemathek/build.gradle index 31a467a6..84230da7 100644 --- a/src/de/cinemathek/build.gradle +++ b/src/de/cinemathek/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Cinemathek' themePkg = 'dooplay' baseUrl = 'https://cinemathek.net' - overrideVersionCode = 20 + overrideVersionCode = 21 isNsfw = true } diff --git a/src/de/einfach/build.gradle b/src/de/einfach/build.gradle index 1ad2b145..868eeabd 100644 --- a/src/de/einfach/build.gradle +++ b/src/de/einfach/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Einfach' extClass = '.Einfach' - extVersionCode = 10 + extVersionCode = 11 } apply from: "$rootDir/common.gradle" diff --git a/src/de/filmpalast/build.gradle b/src/de/filmpalast/build.gradle index 58ba652a..1b8b6531 100644 --- a/src/de/filmpalast/build.gradle +++ b/src/de/filmpalast/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'FilmPalast' extClass = '.FilmPalast' - extVersionCode = 17 + extVersionCode = 18 } apply from: "$rootDir/common.gradle" diff --git a/src/de/kinoking/build.gradle b/src/de/kinoking/build.gradle index 12567fbb..be32a930 100644 --- a/src/de/kinoking/build.gradle +++ b/src/de/kinoking/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Kinoking' themePkg = 'dooplay' baseUrl = 'https://kinoking.cc' - overrideVersionCode = 21 + overrideVersionCode = 22 } apply from: "$rootDir/common.gradle" diff --git a/src/de/kool/build.gradle b/src/de/kool/build.gradle index 2fd2a972..c8735707 100644 --- a/src/de/kool/build.gradle +++ b/src/de/kool/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Kool' extClass = '.Kool' - extVersionCode = 11 + extVersionCode = 12 } apply from: "$rootDir/common.gradle" diff --git a/src/de/moflixstream/build.gradle b/src/de/moflixstream/build.gradle index 50b1262d..4d8ddf91 100644 --- a/src/de/moflixstream/build.gradle +++ b/src/de/moflixstream/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Moflix-Stream' extClass = '.MoflixStream' - extVersionCode = 8 + extVersionCode = 9 } apply from: "$rootDir/common.gradle" diff --git a/src/de/movie4k/build.gradle b/src/de/movie4k/build.gradle index 46b5a639..f77bcc76 100644 --- a/src/de/movie4k/build.gradle +++ b/src/de/movie4k/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Movie4k' extClass = '.Movie4k' - extVersionCode = 10 + extVersionCode = 11 } apply from: "$rootDir/common.gradle" diff --git a/src/de/serienstream/build.gradle b/src/de/serienstream/build.gradle index 2cdfacc9..dc4557e5 100644 --- a/src/de/serienstream/build.gradle +++ b/src/de/serienstream/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Serienstream' extClass = '.Serienstream' - extVersionCode = 18 + extVersionCode = 19 } apply from: "$rootDir/common.gradle" diff --git a/src/en/allanimechi/build.gradle b/src/en/allanimechi/build.gradle index de36a8a8..9880bacb 100644 --- a/src/en/allanimechi/build.gradle +++ b/src/en/allanimechi/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'AllAnimeChi' extClass = '.AllAnimeChi' - extVersionCode = 7 + extVersionCode = 8 } apply from: "$rootDir/common.gradle" diff --git a/src/en/animegg/build.gradle b/src/en/animegg/build.gradle new file mode 100644 index 00000000..e86f9ede --- /dev/null +++ b/src/en/animegg/build.gradle @@ -0,0 +1,7 @@ +ext { + extName = 'AnimeGG' + extClass = '.AnimeGG' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/animegg/res/mipmap-hdpi/ic_launcher.png b/src/en/animegg/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..c11322d3 Binary files /dev/null and b/src/en/animegg/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/animegg/res/mipmap-mdpi/ic_launcher.png b/src/en/animegg/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..973ed6b9 Binary files /dev/null and b/src/en/animegg/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/animegg/res/mipmap-xhdpi/ic_launcher.png b/src/en/animegg/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..a04ead97 Binary files /dev/null and b/src/en/animegg/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/animegg/res/mipmap-xxhdpi/ic_launcher.png b/src/en/animegg/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..68181b55 Binary files /dev/null and b/src/en/animegg/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/animegg/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/animegg/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..5f9ac33b Binary files /dev/null and b/src/en/animegg/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/animegg/src/eu/kanade/tachiyomi/animeextension/en/animegg/AnimeGG.kt b/src/en/animegg/src/eu/kanade/tachiyomi/animeextension/en/animegg/AnimeGG.kt new file mode 100644 index 00000000..463c226c --- /dev/null +++ b/src/en/animegg/src/eu/kanade/tachiyomi/animeextension/en/animegg/AnimeGG.kt @@ -0,0 +1,279 @@ +package eu.kanade.tachiyomi.animeextension.en.animegg + +import android.annotation.SuppressLint +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.util.Locale + +class AnimeGG : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "AnimeGG" + + override val baseUrl = "https://www.animegg.org" + + override val lang = "en" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + companion object { + private const val PREF_LANGUAGE_KEY = "preferred_language" + private const val PREF_LANGUAGE_DEFAULT = "[SUBBED]" + private val LANGUAGE_LIST = arrayOf("[SUBBED]", "[DUBBED]", "[RAW]") + + private const val PREF_QUALITY_KEY = "preferred_quality" + private const val PREF_QUALITY_DEFAULT = "1080" + private val QUALITY_LIST = arrayOf("1080", "720", "480", "360") + + private const val PREF_SERVER_KEY = "preferred_server" + private const val PREF_SERVER_DEFAULT = "AnimeGG" + private val SERVER_LIST = arrayOf("AnimeGG") + } + + override fun animeDetailsParse(response: Response): SAnime { + val document = response.asJsoup() + val animeDetails = SAnime.create().apply { + title = document.selectFirst(".media-body h1")?.text()?.trim() ?: "" + status = if (document.location().contains("/series/")) SAnime.UNKNOWN else SAnime.COMPLETED + description = document.selectFirst(".ptext")?.text() + genre = document.select(".tagscat a").joinToString { it.text() } + thumbnail_url = document.selectFirst(".media .media-object")?.attr("abs:src") + document.select(".infoami span").map { it.text() }.map { textContent -> + when { + "Status" in textContent -> status = parseStatus(textContent) + } + } + } + return animeDetails + } + + private fun parseStatus(span: String): Int { + val status = span.substringAfter("Status:").trim() + return when { + "Completed" in status -> SAnime.COMPLETED + "Ongoing" in status -> SAnime.ONGOING + else -> SAnime.UNKNOWN + } + } + + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/popular-series?sortBy=hits&sortDirection=DESC&ongoing&limit=50&start=0", headers) + + override fun popularAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + val elements = document.select(".fea") + val animeList = elements.map { element -> + SAnime.create().apply { + title = element.selectFirst(".rightpop a")?.text()?.trim() ?: "" + thumbnail_url = element.selectFirst("img")?.attr("abs:src") + setUrlWithoutDomain(element.select(".rightpop a").attr("abs:href")) + } + } + return AnimesPage(animeList, false) + } + + override fun latestUpdatesParse(response: Response) = popularAnimeParse(response) + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/popular-series?sortBy=createdAt&sortDirection=DESC&ongoing&limit=50&start=0", headers) + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val genreFilter = filterList.find { it is GenreFilter } as GenreFilter + + return when { + query.isNotBlank() -> GET("$baseUrl/search/?q=$query", headers) + genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page", headers) + else -> popularAnimeRequest(page) + } + } + + override fun searchAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + val elements = document.select(".mse") + val animeList = elements.map { element -> + SAnime.create().apply { + title = element.selectFirst(".first h2")?.text()?.trim() ?: "" + thumbnail_url = element.selectFirst("img")?.attr("abs:src") + setUrlWithoutDomain(element.attr("abs:href")) + } + } + return AnimesPage(animeList, false) + } + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + return document.select(".newmanga li div").mapIndexed { idx, episode -> + val episodeNumber = episode.selectFirst(".anm_det_pop strong")?.getEpNumber() ?: (idx + 1F) + val title = episode.select(".anititle").text() + SEpisode.create().apply { + episode_number = episodeNumber + name = when { + episodeNumber.formatEp() in title -> episode.select(".anititle").text() + else -> "Episode ${episodeNumber.formatEp()} - ${episode.select(".anititle").text()}" + } + scanlator = episode.select(".btn-xs").joinToString { it.text() } + setUrlWithoutDomain(episode.select(".anm_det_pop").attr("abs:href")) + } + } + } + + override fun videoListParse(response: Response): List