diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 2ea41585..d984a6c6 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -157,7 +157,7 @@ jobs: - name: Sync repo run: | - rsync -a --delete --exclude .git --exclude .gitignore main/repo/ repo --exclude README.md + rsync -a --delete --exclude .git --exclude .gitignore main/repo/ repo --exclude README.md --exclude repo.json - name: Deploy repo uses: EndBug/add-and-commit@v9 diff --git a/src/id/minioppai/build.gradle b/src/id/minioppai/build.gradle index 5021025a..dd8ee9ee 100644 --- a/src/id/minioppai/build.gradle +++ b/src/id/minioppai/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'MiniOppai' extClass = '.MiniOppai' themePkg = 'animestream' - baseUrl = 'https://minioppai.org' - overrideVersionCode = 5 + baseUrl = 'https://minioppai.biz' + overrideVersionCode = 6 isNsfw = true } @@ -12,4 +12,4 @@ apply from: "$rootDir/common.gradle" dependencies { implementation(project(":lib:gdriveplayer-extractor")) implementation(project(":lib:unpacker")) -} \ No newline at end of file +} diff --git a/src/id/minioppai/src/eu/kanade/tachiyomi/animeextension/id/minioppai/MiniOppai.kt b/src/id/minioppai/src/eu/kanade/tachiyomi/animeextension/id/minioppai/MiniOppai.kt index 1687f8e1..945532f3 100644 --- a/src/id/minioppai/src/eu/kanade/tachiyomi/animeextension/id/minioppai/MiniOppai.kt +++ b/src/id/minioppai/src/eu/kanade/tachiyomi/animeextension/id/minioppai/MiniOppai.kt @@ -18,7 +18,7 @@ import java.util.Locale class MiniOppai : AnimeStream( "id", "MiniOppai", - "https://minioppai.org", + "https://minioppai.biz", ) { override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl) diff --git a/src/it/animeworld/build.gradle b/src/it/animeworld/build.gradle index ee2b1985..29043c33 100644 --- a/src/it/animeworld/build.gradle +++ b/src/it/animeworld/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'ANIMEWORLD.tv' extClass = '.ANIMEWORLD' - extVersionCode = 38 + extVersionCode = 39 } apply from: "$rootDir/common.gradle" diff --git a/src/it/animeworld/src/eu/kanade/tachiyomi/animeextension/it/animeworld/ANIMEWORLD.kt b/src/it/animeworld/src/eu/kanade/tachiyomi/animeextension/it/animeworld/ANIMEWORLD.kt index d0043b39..6725ce77 100644 --- a/src/it/animeworld/src/eu/kanade/tachiyomi/animeextension/it/animeworld/ANIMEWORLD.kt +++ b/src/it/animeworld/src/eu/kanade/tachiyomi/animeextension/it/animeworld/ANIMEWORLD.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.Serializable @@ -148,6 +149,10 @@ class ANIMEWORLD : ConfigurableAnimeSource, ParsedAnimeHttpSource() { DoodExtractor(client).videoFromUrl(url, redirect = true) ?.let(::listOf) } + url.contains("streamtape") -> { + StreamTapeExtractor(client).videoFromUrl(url.replace("/v/", "/e/")) + ?.let(::listOf) + } url.contains("filemoon") -> { FilemoonExtractor(client).videosFromUrl(url, prefix = "${server.first} - ", headers = headers) } diff --git a/src/pt/animesonlinecc/build.gradle b/src/pt/animesonlinecc/build.gradle new file mode 100644 index 00000000..3320ba0c --- /dev/null +++ b/src/pt/animesonlinecc/build.gradle @@ -0,0 +1,14 @@ +ext { + extName = 'AnimesOnlineCC' + extClass = '.AnimesOnlineCC' + themePkg = 'dooplay' + baseUrl = 'https://animesonlinecc.to' + overrideVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(":lib:blogger-extractor")) +} diff --git a/src/pt/animesonlinecc/res/mipmap-hdpi/ic_launcher.png b/src/pt/animesonlinecc/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..619e8cfb Binary files /dev/null and b/src/pt/animesonlinecc/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecc/res/mipmap-mdpi/ic_launcher.png b/src/pt/animesonlinecc/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..27710f91 Binary files /dev/null and b/src/pt/animesonlinecc/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecc/res/mipmap-xhdpi/ic_launcher.png b/src/pt/animesonlinecc/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..1ce84eda Binary files /dev/null and b/src/pt/animesonlinecc/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecc/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/animesonlinecc/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..9554722c Binary files /dev/null and b/src/pt/animesonlinecc/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecc/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/animesonlinecc/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..23a1c86f Binary files /dev/null and b/src/pt/animesonlinecc/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecc/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCC.kt b/src/pt/animesonlinecc/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCC.kt new file mode 100644 index 00000000..fb30c871 --- /dev/null +++ b/src/pt/animesonlinecc/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCC.kt @@ -0,0 +1,137 @@ +package eu.kanade.tachiyomi.animeextension.pt.animesonlinecc + +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.lib.bloggerextractor.BloggerExtractor +import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class AnimesOnlineCC : DooPlay( + "pt-BR", + "Animes Online CC", + "https://animesonlinecc.to", +) { + + // ============================== Popular =============================== + override fun popularAnimeSelector() = "article.w_item_b > a" + + override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers) + + // =============================== Latest =============================== + override fun latestUpdatesNextPageSelector() = + "div.pagination > a.arrow_pag > i.icon-caret-right" + + // =============================== Search =============================== + override fun searchAnimeSelector() = "div#animation-2 > article > div.poster > a" + override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) + + // =========================== Anime Details ============================ + override val additionalInfoSelector = "div.wp-content" + + override fun animeDetailsParse(document: Document): SAnime { + val doc = getRealAnimeDoc(document) + val sheader = doc.selectFirst("div.sheader")!! + return SAnime.create().apply { + setUrlWithoutDomain(doc.location()) + sheader.selectFirst("div.poster > img")!!.let { + thumbnail_url = it.getImageUrl() + title = it.attr("alt").ifEmpty { + sheader.selectFirst("div.data > h1")!!.text() + } + } + + genre = sheader.select("div.data div.sgeneros > a") + .eachText() + .joinToString() + + description = doc.getDescription() + } + } + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List<Video> { + val document = response.asJsoup() + val players = document.select("#playex iframe") + return players.parallelCatchingFlatMapBlocking(::getPlayerVideos) + } + + override val prefQualityValues = arrayOf("360p", "720p") + override val prefQualityEntries = prefQualityValues + + private val bloggerExtractor by lazy { BloggerExtractor(client) } + + private fun getPlayerVideos(player: Element): List<Video> { + val url = player.attr("src") + + val id = player.parent()!!.attr("id") + var language = + player.ownerDocument()!! + .selectFirst("a.options[href=\"#$id\"]") + ?.text() + ?.trim().takeIf { + it?.lowercase() == "legendado" || it?.lowercase() == "dublado" + } ?: "" + + return when { + "blogger.com" in url -> bloggerExtractor.videosFromUrl(url, headers, language) + else -> emptyList() + } + } + + // ============================== Filters =============================== + override fun genresListRequest() = GET("$baseUrl/generos/", headers) + override fun genresListSelector() = "a.genre-link" + + // ============================== Settings ============================== + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val videoLanguagePref = ListPreference(screen.context).apply { + key = PREF_LANGUAGE_KEY + title = PREF_LANGUAGE_TITLE + entries = PREF_LANGUAGE_ENTRIES + entryValues = PREF_LANGUAGE_VALUES + setDefaultValue(PREF_LANGUAGE_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() + } + } + + screen.addPreference(videoLanguagePref) + super.setupPreferenceScreen(screen) + } + + // ============================= Utilities ============================== + override val animeMenuSelector = "div.pag_episodes div.item a[href] i.icon-bars" + + override fun List<Video>.sort(): List<Video> { + val quality = preferences.getString(videoSortPrefKey, videoSortPrefDefault)!! + val language = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!! + return sortedWith( + compareBy( + { it.quality.lowercase().contains(language.lowercase()) }, + { it.quality.lowercase().contains(quality.lowercase()) }, + { REGEX_QUALITY.find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, + ), + ).reversed() + } + + companion object { + private val REGEX_QUALITY by lazy { Regex("""(\d+)p""") } + + private const val PREF_LANGUAGE_KEY = "preferred_language" + private const val PREF_LANGUAGE_DEFAULT = "Legendado" + private const val PREF_LANGUAGE_TITLE = "Língua preferida" + private val PREF_LANGUAGE_VALUES = arrayOf("Legendado", "Dublado") + private val PREF_LANGUAGE_ENTRIES = PREF_LANGUAGE_VALUES + } +} diff --git a/src/pt/animesonlinecloud/build.gradle b/src/pt/animesonlinecloud/build.gradle new file mode 100644 index 00000000..37537b83 --- /dev/null +++ b/src/pt/animesonlinecloud/build.gradle @@ -0,0 +1,14 @@ +ext { + extName = 'AnimesOnlineCloud' + extClass = '.AnimesOnlineCloud' + themePkg = 'dooplay' + baseUrl = 'https://animesonline.cloud/' + overrideVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(":lib:blogger-extractor")) +} diff --git a/src/pt/animesonlinecloud/res/mipmap-hdpi/ic_launcher.png b/src/pt/animesonlinecloud/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..2294724b Binary files /dev/null and b/src/pt/animesonlinecloud/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecloud/res/mipmap-mdpi/ic_launcher.png b/src/pt/animesonlinecloud/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..841a4ec7 Binary files /dev/null and b/src/pt/animesonlinecloud/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecloud/res/mipmap-xhdpi/ic_launcher.png b/src/pt/animesonlinecloud/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..de057588 Binary files /dev/null and b/src/pt/animesonlinecloud/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecloud/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/animesonlinecloud/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..cf82a8ec Binary files /dev/null and b/src/pt/animesonlinecloud/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecloud/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/animesonlinecloud/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..8d8ab6d7 Binary files /dev/null and b/src/pt/animesonlinecloud/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/animesonlinecloud/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCloud.kt b/src/pt/animesonlinecloud/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCloud.kt new file mode 100644 index 00000000..0cc08369 --- /dev/null +++ b/src/pt/animesonlinecloud/src/eu/kanade/tachiyomi/animeextension/pt/animesgratis/AnimesOnlineCloud.kt @@ -0,0 +1,164 @@ +package eu.kanade.tachiyomi.animeextension.pt.animesonlinecloud + +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.lib.bloggerextractor.BloggerExtractor +import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class AnimesOnlineCloud : DooPlay( + "pt-BR", + "Animes Online Cloud", + "https://animesonline.cloud/", +) { + + // ============================== Popular =============================== + override fun popularAnimeSelector() = "article.w_item_b > a" + + override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers) + + // =============================== Latest =============================== + override fun latestUpdatesNextPageSelector() = "div.pagination > a.arrow_pag > i.fa-caret-right" + + // =============================== Search =============================== + + // =========================== Anime Details ============================ + override val additionalInfoSelector = "div.wp-content" + + override fun animeDetailsParse(document: Document): SAnime { + val doc = getRealAnimeDoc(document) + val sheader = doc.selectFirst("div.sheader")!! + return SAnime.create().apply { + setUrlWithoutDomain(doc.location()) + sheader.selectFirst("div.poster > img")!!.let { + thumbnail_url = it.getImageUrl() + title = it.attr("alt").ifEmpty { + sheader.selectFirst("div.data > h1")!!.text() + }.replace("Todos os Episódios", "").trim() + } + + genre = sheader.select("div.data div.sgeneros > a") + .eachText() + .joinToString() + + description = doc.getDescription() + } + } + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List<Video> { + val document = response.asJsoup() + val players = document.select("ul#playeroptionsul li") + return players.parallelCatchingFlatMapBlocking(::getPlayerVideos) + } + + override val prefQualityValues = arrayOf("360p", "720p") + override val prefQualityEntries = prefQualityValues + + private val bloggerExtractor by lazy { BloggerExtractor(client) } + + private fun getPlayerVideos(player: Element): List<Video> { + val name = player.selectFirst("span.title")!!.text() + .run { + when (this) { + "SD" -> "360p" + "HD" -> "720p" + "SD/HD" -> "720p" + "FHD", "FULLHD" -> "1080p" + else -> this + } + } + + val url = getPlayerUrl(player) + + return when { + "blogger.com" in url -> bloggerExtractor.videosFromUrl(url, headers) + "jwplayer?source=" in url -> { + val videoUrl = url.toHttpUrl().queryParameter("source") ?: return emptyList() + + val videoHeaders = headers.newBuilder() + .add("Accept", "*/*") + .add("Host", videoUrl.toHttpUrl().host) + .add("Origin", "https://${url.toHttpUrl().host}") + .add("Referer", "https://${url.toHttpUrl().host}/") + .build() + + return listOf( + Video(videoUrl, name, videoUrl, videoHeaders), + ) + } + + else -> emptyList() + } + } + + private fun getPlayerUrl(player: Element): String { + val type = player.attr("data-type") + val id = player.attr("data-post") + val num = player.attr("data-nume") + return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num")) + .execute() + .let { response -> + response.body.string() + .substringAfter("\"embed_url\":\"") + .substringBefore("\",") + .replace("\\", "") + } + } + + // ============================== Filters =============================== + override fun genresListRequest() = GET("$baseUrl/generos/", headers) + override fun genresListSelector() = "a.genre-link" + + // ============================== Settings ============================== + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val videoLanguagePref = ListPreference(screen.context).apply { + key = PREF_LANGUAGE_KEY + title = PREF_LANGUAGE_TITLE + entries = PREF_LANGUAGE_ENTRIES + entryValues = PREF_LANGUAGE_VALUES + setDefaultValue(PREF_LANGUAGE_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() + } + } + + screen.addPreference(videoLanguagePref) + super.setupPreferenceScreen(screen) + } + + // ============================= Utilities ============================== + override fun List<Video>.sort(): List<Video> { + val quality = preferences.getString(videoSortPrefKey, videoSortPrefDefault)!! + val language = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!! + return sortedWith( + compareBy( + { it.quality.lowercase().contains(language.lowercase()) }, + { it.quality.lowercase().contains(quality.lowercase()) }, + { REGEX_QUALITY.find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, + ), + ).reversed() + } + + companion object { + private val REGEX_QUALITY by lazy { Regex("""(\d+)p""") } + + private const val PREF_LANGUAGE_KEY = "preferred_language" + private const val PREF_LANGUAGE_DEFAULT = "Legendado" + private const val PREF_LANGUAGE_TITLE = "Língua preferida" + private val PREF_LANGUAGE_VALUES = arrayOf("Legendado", "Dublado") + private val PREF_LANGUAGE_ENTRIES = PREF_LANGUAGE_VALUES + } +}