Goyabu: Fix popular animes tab, episodes list and extractor (#740)
This commit is contained in:
@ -1,2 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="eu.kanade.tachiyomi.animeextension"/>
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="eu.kanade.tachiyomi.animeextension">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".pt.goyabu.GYUrlActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="goyabu.com"
|
||||||
|
android:pathPattern="/assistir/..*"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
@ -6,7 +6,7 @@ ext {
|
|||||||
extName = 'Goyabu'
|
extName = 'Goyabu'
|
||||||
pkgNameSuffix = 'pt.goyabu'
|
pkgNameSuffix = 'pt.goyabu'
|
||||||
extClass = '.Goyabu'
|
extClass = '.Goyabu'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
libVersion = '13'
|
libVersion = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,9 @@ package eu.kanade.tachiyomi.animeextension.pt.goyabu
|
|||||||
|
|
||||||
object GYConstants {
|
object GYConstants {
|
||||||
const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
|
const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||||
const val USER_AGENT = "Mozilla/5.0 (Linux; Android 10; SM-A307GT Build/QP1A.190711.020;) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.138 Mobile Safari/537.36"
|
const val USER_AGENT = "Mozilla/5.0 (Linux; Android 10; SM-A307GT Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36"
|
||||||
const val PREFERRED_QUALITY = "preferred_quality"
|
const val PREFERRED_QUALITY = "preferred_quality"
|
||||||
const val PREFERRED_PLAYER = "preferred_player"
|
const val PREFIX_SEARCH_SLUG = "slug:"
|
||||||
val QUALITY_LIST = arrayOf("SD", "HD")
|
val QUALITY_LIST = arrayOf("SD", "HD")
|
||||||
val PLAYER_NAMES = arrayOf("Player 1", "Player 2")
|
|
||||||
val PLAYER_REGEX = Regex("""label: "(\w+)",.*file: "(.*?)"""")
|
val PLAYER_REGEX = Regex("""label: "(\w+)",.*file: "(.*?)"""")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.tachiyomi.animeextension.pt.goyabu
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Springboard that accepts https://goyabu.com/assistir/<slug> intents
|
||||||
|
* and redirects them to the main Aniyomi process.
|
||||||
|
*/
|
||||||
|
class GYUrlActivity : Activity() {
|
||||||
|
|
||||||
|
private val TAG = "GYUrlActivity"
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val pathSegments = intent?.data?.pathSegments
|
||||||
|
if (pathSegments != null && pathSegments.size > 1) {
|
||||||
|
val slug = pathSegments[1]
|
||||||
|
val searchQuery = GYConstants.PREFIX_SEARCH_SLUG + slug
|
||||||
|
val mainIntent = Intent().apply {
|
||||||
|
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||||
|
putExtra("query", searchQuery)
|
||||||
|
putExtra("filter", packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(mainIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "could not parse uri from intent $intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
finish()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.animeextension.pt.goyabu.GYFilters.applyFilterParams
|
import eu.kanade.tachiyomi.animeextension.pt.goyabu.GYFilters.applyFilterParams
|
||||||
import eu.kanade.tachiyomi.animeextension.pt.goyabu.extractors.PlayerOneExtractor
|
import eu.kanade.tachiyomi.animeextension.pt.goyabu.extractors.PlayerOneExtractor
|
||||||
import eu.kanade.tachiyomi.animeextension.pt.goyabu.extractors.PlayerTwoExtractor
|
|
||||||
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
|
||||||
@ -15,6 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@ -28,6 +28,9 @@ import rx.Observable
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||||
|
|
||||||
@ -56,7 +59,10 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
.add("Referer", baseUrl)
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
override fun popularAnimeSelector(): String = "div.item > div.anime-episode"
|
private fun popularAnimeContainerSelector(): String = "div.index-size > div.episodes-container"
|
||||||
|
|
||||||
|
override fun popularAnimeSelector(): String = "div.anime-episode"
|
||||||
|
|
||||||
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
|
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
|
||||||
|
|
||||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||||
@ -71,7 +77,7 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
val content = document.select("div.episodes-container").get(2)
|
val content = document.selectFirst(popularAnimeContainerSelector())
|
||||||
val animes = content.select(popularAnimeSelector()).map { element ->
|
val animes = content.select(popularAnimeSelector()).map { element ->
|
||||||
popularAnimeFromElement(element)
|
popularAnimeFromElement(element)
|
||||||
}
|
}
|
||||||
@ -79,7 +85,7 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
override fun episodeListSelector(): String = "div.episodes-container > div.anime-episode"
|
override fun episodeListSelector(): String = "div.episodes-container > div.row > a"
|
||||||
|
|
||||||
private fun getAllEps(response: Response): List<SEpisode> {
|
private fun getAllEps(response: Response): List<SEpisode> {
|
||||||
val epList = mutableListOf<SEpisode>()
|
val epList = mutableListOf<SEpisode>()
|
||||||
@ -100,17 +106,20 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
return getAllEps(response).reversed()
|
return getAllEps(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun episodeFromElement(element: Element): SEpisode {
|
override fun episodeFromElement(element: Element): SEpisode {
|
||||||
val episode = SEpisode.create()
|
val episode = SEpisode.create()
|
||||||
|
val info_div = element.selectFirst("div.chaps-infs")
|
||||||
episode.setUrlWithoutDomain(element.selectFirst("a").attr("href"))
|
episode.setUrlWithoutDomain(element.attr("href"))
|
||||||
val epName = element.selectFirst("h3").text().substringAfter("– ")
|
val epName = info_div.ownText()
|
||||||
episode.name = epName
|
episode.name = epName.substringAfter("– ")
|
||||||
|
episode.date_upload = info_div.selectFirst("small").ownText().toDate()
|
||||||
episode.episode_number = try {
|
episode.episode_number = try {
|
||||||
epName.substringAfter(" ").substringBefore(" ").toFloat()
|
epName.substringAfter("#")
|
||||||
|
.substringBefore(" ")
|
||||||
|
.toFloat()
|
||||||
} catch (e: NumberFormatException) { 0F }
|
} catch (e: NumberFormatException) { 0F }
|
||||||
return episode
|
return episode
|
||||||
}
|
}
|
||||||
@ -119,16 +128,21 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
override fun videoListParse(response: Response): List<Video> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
val document: Document = response.asJsoup()
|
val document: Document = response.asJsoup()
|
||||||
val html: String = document.html()
|
val html: String = document.html()
|
||||||
val videoList = PlayerOneExtractor()
|
val videoList = mutableListOf<Video>()
|
||||||
.videoListFromHtml(html)
|
val kanraElement = document.selectFirst("script:containsData(kanra.dev)")
|
||||||
.toMutableList()
|
if (kanraElement != null) {
|
||||||
val iframe = document.selectFirst("div#tab-2 > iframe")
|
val kanraUrl = kanraElement.html()
|
||||||
if (iframe != null) {
|
.substringAfter("src='")
|
||||||
val playerUrl = iframe.attr("src")
|
.substringBefore("'")
|
||||||
val video = PlayerTwoExtractor(client).videoFromPlayerUrl(playerUrl)
|
val kanraVideos =
|
||||||
if (video != null)
|
PlayerOneExtractor(client).videoListFromKanraUrl(kanraUrl)
|
||||||
videoList.add(video)
|
videoList.addAll(kanraVideos)
|
||||||
|
} else {
|
||||||
|
val extracted = PlayerOneExtractor()
|
||||||
|
.videoListFromHtml(html)
|
||||||
|
videoList.addAll(extracted)
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoList
|
return videoList
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +167,22 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
override fun searchAnimeParse(response: Response) = throw Exception("not used")
|
override fun searchAnimeParse(response: Response) = throw Exception("not used")
|
||||||
|
|
||||||
|
private fun searchAnimeBySlugParse(response: Response, slug: String): AnimesPage {
|
||||||
|
val details = animeDetailsParse(response)
|
||||||
|
details.url = "/assistir/$slug"
|
||||||
|
return AnimesPage(listOf(details), false)
|
||||||
|
}
|
||||||
|
|
||||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||||
|
if (query.startsWith(GYConstants.PREFIX_SEARCH_SLUG)) {
|
||||||
|
val slug = query.removePrefix(GYConstants.PREFIX_SEARCH_SLUG)
|
||||||
|
return client.newCall(GET("$baseUrl/assistir/$slug"))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
searchAnimeBySlugParse(response, slug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else
|
||||||
val params = GYFilters.getSearchParameters(filters)
|
val params = GYFilters.getSearchParameters(filters)
|
||||||
return Observable.just(searchAnimeRequest(page, query, params))
|
return Observable.just(searchAnimeRequest(page, query, params))
|
||||||
}
|
}
|
||||||
@ -218,22 +247,6 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
// ============================== Settings ==============================
|
// ============================== Settings ==============================
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
|
||||||
val videoPlayerPref = ListPreference(screen.context).apply {
|
|
||||||
key = GYConstants.PREFERRED_PLAYER
|
|
||||||
title = "Player preferido"
|
|
||||||
entries = GYConstants.PLAYER_NAMES
|
|
||||||
entryValues = GYConstants.PLAYER_NAMES
|
|
||||||
setDefaultValue(GYConstants.PLAYER_NAMES.first())
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val videoQualityPref = ListPreference(screen.context).apply {
|
val videoQualityPref = ListPreference(screen.context).apply {
|
||||||
key = GYConstants.PREFERRED_QUALITY
|
key = GYConstants.PREFERRED_QUALITY
|
||||||
title = "Qualidade preferida"
|
title = "Qualidade preferida"
|
||||||
@ -248,7 +261,6 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
preferences.edit().putString(key, entry).commit()
|
preferences.edit().putString(key, entry).commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(videoPlayerPref)
|
|
||||||
screen.addPreference(videoQualityPref)
|
screen.addPreference(videoQualityPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,10 +280,10 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
|
|
||||||
private fun Element.getInfo(item: String, cut: Boolean = true): String {
|
private fun Element.getInfo(item: String, cut: Boolean = true): String {
|
||||||
val text = this.selectFirst("div.anime-info-right div:contains($item)").text()
|
val text = this.selectFirst("div.anime-info-right div:contains($item)").text()
|
||||||
if (cut)
|
return when {
|
||||||
return text.substringAfter(": ")
|
cut -> text.substringAfter(": ")
|
||||||
else
|
else -> text.substringAfter(" ")
|
||||||
return text.substringAfter(" ")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseStatus(statusString: String?): Int {
|
private fun parseStatus(statusString: String?): Int {
|
||||||
@ -282,24 +294,35 @@ class Goyabu : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toDate(): Long {
|
||||||
|
return try {
|
||||||
|
DATE_FORMATTER.parse(this)?.time ?: 0L
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
val quality = preferences.getString(GYConstants.PREFERRED_QUALITY, null)
|
val quality = preferences.getString(GYConstants.PREFERRED_QUALITY, null)
|
||||||
val player = preferences.getString(GYConstants.PREFERRED_PLAYER, null)
|
if (quality != null) {
|
||||||
val newList = mutableListOf<Video>()
|
val newList = mutableListOf<Video>()
|
||||||
var preferred = 0
|
var preferred = 0
|
||||||
for (video in this) {
|
for (video in this) {
|
||||||
when {
|
if (video.quality.equals(quality)) {
|
||||||
quality != null && video.quality.contains(quality) -> {
|
|
||||||
newList.add(preferred, video)
|
newList.add(preferred, video)
|
||||||
preferred++
|
preferred++
|
||||||
|
} else {
|
||||||
|
newList.add(video)
|
||||||
}
|
}
|
||||||
player != null && video.quality.contains(player) -> {
|
|
||||||
newList.add(preferred, video)
|
|
||||||
preferred++
|
|
||||||
}
|
|
||||||
else -> newList.add(video)
|
|
||||||
}
|
}
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DATE_FORMATTER by lazy {
|
||||||
|
SimpleDateFormat("dd/MM/yy", Locale.ENGLISH)
|
||||||
}
|
}
|
||||||
return newList
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,33 @@ package eu.kanade.tachiyomi.animeextension.pt.goyabu.extractors
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.animeextension.pt.goyabu.GYConstants
|
import eu.kanade.tachiyomi.animeextension.pt.goyabu.GYConstants
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
class PlayerOneExtractor {
|
class PlayerOneExtractor(private val client: OkHttpClient? = null) {
|
||||||
|
|
||||||
|
private val KANRA_REGEX = Regex("""(?s)label: "(\w+)".*?file: "(.*?)"""")
|
||||||
private val PREFIX = "Player 1"
|
private val PREFIX = "Player 1"
|
||||||
|
|
||||||
fun videoListFromHtml(html: String): List<Video> {
|
fun videoListFromHtml(
|
||||||
return GYConstants.PLAYER_REGEX.findAll(html).map { it ->
|
html: String,
|
||||||
|
regex: Regex = GYConstants.PLAYER_REGEX,
|
||||||
|
headers: Headers? = null
|
||||||
|
): List<Video> {
|
||||||
|
return regex.findAll(html).map { it ->
|
||||||
val quality = "$PREFIX (${it.groupValues[1]})"
|
val quality = "$PREFIX (${it.groupValues[1]})"
|
||||||
val videoUrl = it.groupValues[2]
|
val videoUrl = it.groupValues[2]
|
||||||
Video(videoUrl, quality, videoUrl)
|
Video(videoUrl, quality, videoUrl, null, headers)
|
||||||
}.toList()
|
}.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun videoListFromKanraUrl(url: String): List<Video> {
|
||||||
|
val headers = Headers.headersOf(
|
||||||
|
"User-Agent", GYConstants.USER_AGENT
|
||||||
|
)
|
||||||
|
val res = client!!.newCall(GET(url, headers)).execute()
|
||||||
|
val html = res.body?.string().orEmpty()
|
||||||
|
return videoListFromHtml(html, KANRA_REGEX, headers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animeextension.pt.goyabu.extractors
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animeextension.pt.goyabu.GYConstants
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
|
|
||||||
class PlayerTwoExtractor(private val client: OkHttpClient) {
|
|
||||||
|
|
||||||
private val PREFIX = "Player 2"
|
|
||||||
|
|
||||||
fun videoFromPlayerUrl(url: String): Video? {
|
|
||||||
val headers = Headers.headersOf("User-Agent", GYConstants.USER_AGENT)
|
|
||||||
val res = client.newCall(GET(url, headers)).execute()
|
|
||||||
val html = res.body?.string().orEmpty()
|
|
||||||
val match = GYConstants.PLAYER_REGEX.find(html)
|
|
||||||
if (match == null) {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
val quality = "$PREFIX (${match.groupValues[1]})"
|
|
||||||
val videoUrl = match.groupValues[2]
|
|
||||||
return Video(videoUrl, quality, videoUrl)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user