feat(multisrc/dooplay): New source: GoAnimes (#1477)

* feat(multisrc/dooplay): Create GoAnimes base

* fix: Change latest anime updates path

* fix: Change popular anime selector

* fix: Fix episodes list

* feat: Implement some video extractors

* chore: Add source icon

* fix: Fix video quality pref
This commit is contained in:
Claudemirovsky
2023-04-10 13:14:54 -03:00
committed by GitHub
parent 96da5bfc50
commit 2de35f226c
13 changed files with 245 additions and 0 deletions

View File

@ -0,0 +1,3 @@
dependencies {
implementation(project(":lib-fembed-extractor"))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,95 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes
import eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors.BloggerJWPlayerExtractor
import eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors.GoAnimesExtractor
import eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors.JsDecoder
import eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors.PlaylistExtractor
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.fembedextractor.FembedExtractor
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import org.jsoup.nodes.Element
class GoAnimes : DooPlay(
"pt-BR",
"GoAnimes",
"https://goanimes.net",
) {
// ============================== Popular ===============================
override fun popularAnimeSelector() = "div#featured-titles article.item.tvshows > div.poster"
// ============================== Episodes ==============================
override val seasonListSelector = "div#seasons > *"
override fun getSeasonEpisodes(season: Element): List<SEpisode> {
// All episodes are listed under a single page
season.selectFirst(episodeListSelector())?.let {
return super.getSeasonEpisodes(season)
}
// Episodes are listed at another page
val url = season.attr("href")
return client.newCall(GET(url))
.execute()
.asJsoup()
.let { super.getSeasonEpisodes(it) }
}
// ============================ Video Links =============================
override val PREF_QUALITY_VALUES = arrayOf("240p", "360p", "480p", "720p", "1080p")
override val PREF_QUALITY_ENTRIES = PREF_QUALITY_VALUES
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val players = document.select("ul#playeroptionsul li")
return players.flatMap(::getPlayerVideos)
}
private fun getPlayerVideos(player: Element): List<Video> {
val url = getPlayerUrl(player)
return when {
"player5.goanimes.net" in url ->
GoAnimesExtractor(client).videosFromUrl(url)
"/v/" in url ->
runCatching {
FembedExtractor(client).videosFromUrl(url)
}.getOrDefault(emptyList<Video>())
listOf("/bloggerjwplayer", "/m3u8", "/multivideo").any { it in url } -> {
val script = client.newCall(GET(url)).execute()
.body.string()
.let(JsDecoder::decodeScript)
when {
"/bloggerjwplayer" in url ->
BloggerJWPlayerExtractor.videosFromScript(script)
"/m3u8" in url ->
PlaylistExtractor.videosFromScript(script)
"/multivideo" in url ->
script.substringAfter("attr")
.substringAfter(" \"")
.substringBefore('"')
.let { GoAnimesExtractor(client).videosFromUrl(it) }
else -> emptyList<Video>()
}
}
else -> emptyList<Video>()
}
}
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()
.body.string()
.substringAfter("\"embed_url\":\"")
.substringBefore("\",")
.replace("\\", "")
}
// =============================== Latest ===============================
override val latestUpdatesPath = "lancamentos"
}

View File

@ -0,0 +1,18 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors
import eu.kanade.tachiyomi.animesource.model.Video
object BloggerJWPlayerExtractor {
fun videosFromScript(script: String): List<Video> {
val sources = script.substringAfter("sources: [").substringBefore("],")
return sources.split("{").drop(1).map {
val label = it.substringAfter("label").substringAfter(":\"").substringBefore('"')
val videoUrl = it.substringAfter("file")
.substringAfter(":\"")
.substringBefore('"')
.replace("\\", "")
Video(videoUrl, "BloggerJWPlayer - $label", videoUrl)
}
}
}

View File

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import okhttp3.OkHttpClient
class GoAnimesExtractor(private val client: OkHttpClient) {
private val REGEX_PLAYER = Regex("""player\('(\S+?)','\S+'\)""")
fun videosFromUrl(url: String): List<Video> {
val playlistUrl = client.newCall(GET(url)).execute()
.body.string()
.let(JsUnpacker::unpack)
.let(REGEX_PLAYER::find)
?.groupValues
?.get(1)
?: return emptyList<Video>()
val playlistData = client.newCall(GET(playlistUrl)).execute()
.body.string()
val separator = "#EXT-X-STREAM-INF:"
return playlistData.substringAfter(separator).split(separator).map {
val quality = it.substringAfter("RESOLUTION=")
.substringAfter("x")
.substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(videoUrl, "GoAnimes - $quality", videoUrl)
}
}
}

View File

@ -0,0 +1,39 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors
import kotlin.math.pow
object JsDecoder {
private fun convertToNum(thing: String, limit: Float): Int {
return thing.split("")
.reversed()
.map { it.toIntOrNull() ?: 0 }
.reduceIndexed { index: Int, acc, num ->
acc + (num * limit.pow(index - 1)).toInt()
}
}
fun decodeScript(encodedString: String, magicStr: String, offset: Int, limit: Int): String {
val regex = "\\w".toRegex()
return encodedString
.split(magicStr[limit])
.dropLast(1)
.map { str ->
val replaced = regex.replace(str) { magicStr.indexOf(it.value).toString() }
val charInt = convertToNum(replaced, limit.toFloat()) - offset
Char(charInt)
}.joinToString("")
}
fun decodeScript(script: String): String {
val regex = """\}\("(\w+)",.*?"(\w+)",(\d+),(\d+),.*?\)""".toRegex()
return regex.find(script)
?.run {
decodeScript(
groupValues[1], // encoded data
groupValues[2], // magic string
groupValues[3].toIntOrNull() ?: 0, // offset
groupValues[4].toIntOrNull() ?: 0, // limit
)
} ?: ""
}
}

View File

@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors
object JsUnpacker {
private val REGEX_REPLACE = "\\b\\w+\\b".toRegex()
private val REGEX_EVAL = """\}\('(.*)',(\d+),(\d+),'(.*)'\.split""".toRegex()
private fun hasPacker(js: String): Boolean = REGEX_EVAL.containsMatchIn(js)
private fun getPackerArgs(js: String): List<String> = REGEX_EVAL.findAll(js)
.last().groupValues
private fun convert(base: Int, num: Int): String {
val firstPart = if (num < base) "" else (num / base).toString()
val calc = num % base
if (calc > 35) {
return firstPart + (calc + 29).toChar().toString()
}
return firstPart + calc.toString(36)
}
fun unpack(js: String): String {
if (!hasPacker(js)) return js
val args = getPackerArgs(js)
val origJS = args[1]
val base = args[2].toInt()
val count = args[3].toInt()
val origList = args[4].split("|")
val replaceMap = (0..(count - 1)).map {
val key = convert(base, it)
key to try { origList[it] } catch (e: Exception) { key }
}.toMap()
val result = origJS.replace(REGEX_REPLACE) {
replaceMap.get(it.value) ?: it.value
}.replace("\\", "")
return unpack(result)
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.tachiyomi.animeextension.pt.goanimes.extractors
import eu.kanade.tachiyomi.animesource.model.Video
object PlaylistExtractor {
fun videosFromScript(script: String): List<Video> {
val sources = script.substringAfter("sources: [").substringBefore("],")
return sources.split("file:\"").drop(1).mapNotNull { source ->
val url = source.substringBefore("\"").ifEmpty { return@mapNotNull null }
val label = source.substringAfter("label:\"").substringBefore("\"")
.replace("FHD", "1080p")
.replace("HD", "720p")
.replace("SD", "480p")
Video(url, "Playlist - $label", url)
}
}
}

View File

@ -14,6 +14,7 @@ class DooPlayGenerator : ThemeSourceGenerator {
SingleLang("Animes House", "https://animeshouse.net", "pt-BR", isNsfw = false, overrideVersionCode = 4),
SingleLang("Cinemathek", "https://cinemathek.net", "de", isNsfw = true, overrideVersionCode = 10),
SingleLang("CineVision", "https://cinevision.vc", "pt-BR", isNsfw = true, overrideVersionCode = 4),
SingleLang("GoAnimes", "https://goanimes.net", "pt-BR", isNsfw = true),
SingleLang("pactedanime", "https://pactedanime.com", "en", isNsfw = false, overrideVersionCode = 4),
SingleLang("Pi Fansubs", "https://pifansubs.org", "pt-BR", isNsfw = true, overrideVersionCode = 13),
)