chore(en/pobmovies): Remove extension (#2414)

This commit is contained in:
Secozzi
2023-10-25 20:42:37 +00:00
committed by GitHub
parent e08c6db300
commit a058205201
13 changed files with 0 additions and 683 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,17 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'PobMovies'
pkgNameSuffix = 'en.holamovies'
extClass = '.PobMovies'
extVersionCode = 6
libVersion = '13'
}
dependencies {
implementation(project(':lib-googledrive-extractor'))
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,331 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.holamovies
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.en.holamovies.extractors.GDBotExtractor
import eu.kanade.tachiyomi.animeextension.en.holamovies.extractors.GDFlixExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
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.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class PobMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "PobMovies"
override val id = 8957317977172284857
override val baseUrl = "https://pobmovies.cam"
override val lang = "en"
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/page/$page/")
override fun popularAnimeSelector(): String = "div#content > div > div.row > div"
override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
thumbnail_url = element.selectFirst("img[src]")?.attr("src") ?: ""
title = element.selectFirst("h1")!!.text()
}
override fun popularAnimeNextPageSelector(): String = "nav.gridlove-pagination > span.current + a"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
override fun latestUpdatesSelector(): String = throw Exception("Not used")
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used")
override fun latestUpdatesNextPageSelector(): String = throw Exception("Not used")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
// val FILTER_LIST = if (filters.isEmpty()) getFilterList() else filters
// val genreFilter = FILTER_LIST.find { it is GenreFilter } as GenreFilter
// val recentFilter = FILTER_LIST.find { it is RecentFilter } as RecentFilter
// val seasonFilter = FILTER_LIST.find { it is SeasonFilter } as SeasonFilter
val cleanQuery = query.replace(" ", "+")
return when {
query.isNotBlank() -> GET("$baseUrl/page/$page/?s=$cleanQuery", headers)
// genreFilter.state != 0 -> GET("$baseUrl/genre/${genreFilter.toUriPart()}?page=$page")
// recentFilter.state != 0 -> GET("https://ajax.gogo-load.com/ajax/page-recent-release.html?page=$page&type=${recentFilter.toUriPart()}")
else -> GET("$baseUrl/popular.html?page=$page")
}
}
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
// ============================== FILTERS ===============================
// Todo - add these when the site starts working again
// override fun getFilterList(): AnimeFilterList = AnimeFilterList(
// AnimeFilter.Header("Text search ignores filters"),
// GenreFilter(),
// )
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
description = document.selectFirst("div.entry-content > p")?.text()
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val sizeRegex = Regex("""[\[\(](\d+\.?\d* ?[KMGT]B)[\]\)]""")
val zipRegex = Regex("""\bZIP\b""")
document.select("div.entry-content:has(h3,h4,p) > p:has(a[href]):not(:has(span.mb-text))").forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val info = it.previousElementSiblings().firstOrNull { prevTag ->
arrayOf("h3", "h4", "p").contains(prevTag.normalName())
}?.text()
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = link.text()
episode_number = 1F
date_upload = -1L
url = link.attr("href")
scanlator = "${if (size == null) "" else "$size • "}$info"
},
)
}
}
// We don't want to parse multiple times
if (episodeList.isEmpty()) {
document.select("div.entry-content:has(pre:contains(episode)) > p:has(a[href])").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val info = it.previousElementSiblings().firstOrNull { prevTag ->
prevTag.normalName() == "p" && prevTag.selectFirst("strong") != null
}?.text()
val episodeNumber = it.previousElementSiblings().firstOrNull { prevTag ->
prevTag.normalName() == "pre" && prevTag.text().contains("episode", true)
}?.text()?.substringAfter(" ")?.toFloatOrNull() ?: 1F
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = "Ep. $episodeNumber - ${link.text()}"
episode_number = episodeNumber
date_upload = -1L
url = link.attr("href")
scanlator = "${if (size == null) "" else "$size • "}$info"
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content > p:has(a[href]:has(span.mb-text)), div.entry-content > em p:has(a[href]:has(span.mb-text))").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val title = it.previousElementSiblings().firstOrNull { prevTag ->
arrayOf("p", "h5").contains(prevTag.normalName()) && prevTag.text().isNotBlank()
}?.text() ?: "Item"
val size = sizeRegex.find(title)?.groupValues?.get(1)
?: sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = "$title - ${link.text()}"
episode_number = 1F
date_upload = -1L
scanlator = size
url = link.attr("href")
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content:has(pre:has(em)) > p:has(a[href])").reversed().forEach {
it.select("em a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
if (link.text().contains("click here", true)) return@forEach
val title = it.previousElementSiblings().firstOrNull { prevTag ->
prevTag.normalName() == "pre"
}?.text()
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = "$title - ${link.text()}"
episode_number = 1F
date_upload = -1L
scanlator = size
url = link.attr("href")
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content:has(p:has(em)) > p:has(a[href])").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val info = it.previousElementSiblings().firstOrNull { prevTag ->
prevTag.normalName() == "p" && prevTag.text().isNotBlank() && prevTag.selectFirst("a") == null
}?.text() ?: "Item"
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = link.text()
episode_number = 1F
date_upload = -1L
scanlator = "${if (size == null) "" else "$size • "}$info"
url = link.attr("href")
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content > div.wp-block-buttons").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val info = it.previousElementSiblings().firstOrNull { prevTag ->
prevTag.normalName() == "pre" && prevTag.text().isNotBlank()
}?.text() ?: ""
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = link.text()
episode_number = 1F
date_upload = -1L
scanlator = "${if (size == null) "" else "$size • "}$info"
url = link.attr("href")
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content > figure.wp-block-embed").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = link.text()
episode_number = 1F
date_upload = -1L
scanlator = size
url = link.attr("href")
},
)
}
}
}
if (episodeList.isEmpty()) {
document.select("div.entry-content > a[role=button][href]").reversed().forEach {
it.select("a").forEach { link ->
if (zipRegex.find(link.text()) != null) return@forEach
val size = sizeRegex.find(link.text())?.groupValues?.get(1)
episodeList.add(
SEpisode.create().apply {
name = link.text()
episode_number = 1F
date_upload = -1L
scanlator = size
url = link.attr("href")
},
)
}
}
}
return episodeList
}
override fun episodeListSelector(): String = throw Exception("Not used")
override fun episodeFromElement(element: Element): SEpisode = throw Exception("Not used")
// ============================ Video Links =============================
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
val videoList = when {
episode.url.toHttpUrl().host.contains("gdflix") -> {
GDFlixExtractor(client, headers).videosFromUrl(episode.url)
}
episode.url.toHttpUrl().host.contains("gdtot") ||
episode.url.toHttpUrl().host.contains("gdbot") -> {
GDBotExtractor(client, headers, preferences).videosFromUrl(episode.url)
}
else -> { throw Exception("Unsupported url: ${episode.url}") }
}
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
return Observable.just(videoList.sort())
}
override fun videoListSelector(): String = throw Exception("Not Used")
override fun videoFromElement(element: Element): Video = throw Exception("Not Used")
override fun videoUrlParse(document: Document): String = throw Exception("Not Used")
// ============================= Utilities ==============================
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {}
}

View File

@ -1,76 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.holamovies.extractors
import android.content.SharedPreferences
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class GDBotExtractor(private val client: OkHttpClient, private val headers: Headers, private val preferences: SharedPreferences) {
private val prefBotUrlKey = "bot_url"
private val defaultUrl = "https://gdtot.pro"
fun videosFromUrl(serverUrl: String, maxTries: Int = 1): List<Video> {
val botUrl = preferences.getString(prefBotUrlKey, defaultUrl)!!
val videoList = mutableListOf<Video>()
if (maxTries == 3) throw Exception("Video extraction catastrophically failed")
val docHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", botUrl.toHttpUrl().host)
.build()
val fileId = serverUrl.substringAfter("/file/")
val resp = try {
client.newCall(
GET("$botUrl/file/$fileId", headers = docHeaders),
).execute()
} catch (a: Exception) {
val newHost = OkHttpClient().newCall(GET(botUrl)).execute().request.url.host
preferences.edit().putString(prefBotUrlKey, "https://$newHost").apply()
return videosFromUrl(serverUrl, maxTries + 1)
}
if (resp.code == 421) {
val newHost = OkHttpClient().newCall(GET(botUrl)).execute().request.url.host
preferences.edit().putString(prefBotUrlKey, "https://$newHost").apply()
return videosFromUrl(serverUrl, maxTries + 1)
}
val document = resp.asJsoup()
videoList.addAll(
document.select("li.py-6 > a[href]").parallelMap { server ->
runCatching {
val url = server.attr("href")
when {
url.toHttpUrl().host.contains("gdflix") -> {
GDFlixExtractor(client, headers).videosFromUrl(url)
}
url.toHttpUrl().host.contains("gdtot") -> {
GDTotExtractor(client, headers).videosFromUrl(url)
}
else -> null
}
}.getOrNull()
}.filterNotNull().flatten(),
)
return videoList
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
}

View File

@ -1,130 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.holamovies.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.googledriveextractor.GoogleDriveExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
class GDFlixExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val json: Json by injectLazy()
fun videosFromUrl(serverUrl: String): List<Video> {
val videoList = mutableListOf<Video>()
videoList.addAll(
listOf("direct", "drivebot").parallelMap { type ->
runCatching {
when (type) {
"direct" -> {
extractGDriveLink(serverUrl)
}
"drivebot" -> {
extractDriveBotLink(serverUrl)
}
else -> null
}
}.getOrNull()
}.filterNotNull().flatten(),
)
return videoList
}
// From Dopebox
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
private fun extractGDriveLink(mediaUrl: String): List<Video> {
val tokenClient = client.newBuilder().addInterceptor(TokenInterceptor()).build()
val response = tokenClient.newCall(GET(mediaUrl)).execute().asJsoup()
val gdBtn = response.selectFirst("div.card-body a.btn")!!
val gdLink = gdBtn.attr("href")
return GoogleDriveExtractor(client, headers).videosFromUrl(gdLink, "Gdrive")
}
private fun extractDriveBotLink(mediaUrl: String): List<Video> {
val response = client.newCall(GET(mediaUrl)).execute().asJsoup()
val flixUrlPath = response.selectFirst("script:containsData(file)")?.data() ?: return emptyList()
val flixUrl = "https://${mediaUrl.toHttpUrl().host}${flixUrlPath.substringAfter("replace(\"").substringBefore("\"")}"
val flixDocument = client.newCall(GET(flixUrl)).execute().asJsoup()
val driveBotUrl = flixDocument.selectFirst("div.card-body a.btn[href~=drivebot]")?.attr("href") ?: return emptyList()
val docHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", driveBotUrl.toHttpUrl().host)
.build()
val documentResp = OkHttpClient().newCall(
GET(driveBotUrl, headers = docHeaders),
).execute()
val document = documentResp.asJsoup()
val sessId = documentResp.headers.firstOrNull {
it.first.startsWith("set-cookie", true) && it.second.startsWith("PHPSESSID", true)
}?.second?.substringBefore(";") ?: ""
val script = document.selectFirst("script:containsData(token)")?.data() ?: return emptyList()
val token = script.substringAfter("'token', '").substringBefore("'")
val postUrl = "https://${driveBotUrl.toHttpUrl().host}${script.substringAfter("fetch('").substringBefore("'")}"
val postHeaders = headers.newBuilder()
.add("Accept", "*/*")
.add("Cookie", sessId)
.add("Host", driveBotUrl.toHttpUrl().host)
.add("Origin", "https://${driveBotUrl.toHttpUrl().host}")
.add("Referer", mediaUrl)
.add("Sec-Fetch-Site", "same-origin")
.build()
val postBody = FormBody.Builder()
.addEncoded("token", token)
.build()
val postResp = OkHttpClient().newCall(
POST(postUrl, body = postBody, headers = postHeaders),
).execute()
val url = try {
json.decodeFromString<DriveBotResp>(postResp.body.string()).url
} catch (a: Exception) {
return emptyList()
}
val videoHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", url.toHttpUrl().host)
.add("Referer", "https://${driveBotUrl.toHttpUrl().host}/")
.build()
return listOf(
Video(url, "DriveBot", url, headers = videoHeaders),
)
}
@Serializable
data class DriveBotResp(
val url: String,
)
}

View File

@ -1,40 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.holamovies.extractors
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.googledriveextractor.GoogleDriveExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class GDTotExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(serverUrl: String): List<Video> {
val docHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", serverUrl.toHttpUrl().host)
.build()
val docResp = client.newCall(
GET(serverUrl, headers = docHeaders),
).execute()
val sessId = docResp.headers.firstOrNull {
it.first.startsWith("set-cookie", true) && it.second.startsWith("PHPSESSID", true)
}?.second?.substringBefore(";") ?: ""
val ddlUrl = serverUrl.replace("/file/", "/ddl/")
val ddlHeaders = docHeaders.newBuilder()
.add("Cookie", sessId)
.add("Referer", serverUrl)
.build()
val document = client.newCall(
GET(ddlUrl, headers = ddlHeaders),
).execute().asJsoup()
val btn = document.selectFirst("button[onclick~=drive.google.com]")?.attr("onclick") ?: return emptyList()
return GoogleDriveExtractor(client, headers).videosFromUrl(btn.substringAfter("myDl('").substringBefore("'"), "GDToT")
}
}

View File

@ -1,87 +0,0 @@
package eu.kanade.tachiyomi.animeextension.en.holamovies.extractors
import android.annotation.SuppressLint
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.network.GET
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.CountDownLatch
class TokenInterceptor : Interceptor {
private val context = Injekt.get<Application>()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch, var payload: String = "") {
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newRequest = resolveWithWebView(originalRequest) ?: originalRequest
return chain.proceed(newRequest)
}
@SuppressLint("SetJavaScriptEnabled")
private fun resolveWithWebView(request: Request): Request? {
val latch = CountDownLatch(1)
var webView: WebView? = null
val origRequestUrl = request.url.toString()
val jsinterface = JsObject(latch)
// Get url with token with promise
val jsScript = """
(async () => {
var data = await generate("direct");
window.android.passPayload(data.url);
})();""".trim()
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0"
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.evaluateJavascript(jsScript) {}
}
}
webView?.loadUrl(origRequestUrl, headers)
}
}
latch.await()
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return if (jsinterface.payload.isNotBlank()) GET(jsinterface.payload) else null
}
}