animerco : update layout selectors and video extractor (#994)

* Update build.gradle

* animerco : update layout selectors and video extractor

big fix as the site changed their layout and video url method
add GdrivePlayerExtractor
This commit is contained in:
Ahmed gamal 2022-11-01 19:06:52 +02:00 committed by GitHub
parent e5898e37b2
commit a5d70d8fc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 52 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'Animerco'
pkgNameSuffix = 'ar.animerco'
extClass = '.Animerco'
extVersionCode = 16
extVersionCode = 17
libVersion = '13'
}
@ -14,6 +14,7 @@ dependencies {
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}
apply from: "$rootDir/common.gradle"

View File

@ -4,7 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.MpforuploadExtractor
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.GdrivePlayerExtractor
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.SharedExtractor
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.UQLoadExtractor
import eu.kanade.tachiyomi.animeextension.ar.animerco.extractors.VidBomExtractor
@ -19,7 +19,9 @@ import eu.kanade.tachiyomi.lib.fembedextractor.FembedExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
@ -52,19 +54,19 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// Popular Anime
override fun popularAnimeSelector(): String = "div.items article.item"
override fun popularAnimeSelector(): String = "div.media-block"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes/page/$page/") // page/$page
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("div.data a").attr("href"))
anime.thumbnail_url = element.select("div.poster img").attr("src")
anime.title = element.select("div.data a").text()
anime.setUrlWithoutDomain(element.select("a").attr("href"))
anime.thumbnail_url = element.select("a").attr("data-src")
anime.title = element.select("div.info a h3").text()
return anime
}
override fun popularAnimeNextPageSelector(): String = "i#nextpagination"
override fun popularAnimeNextPageSelector(): String = "a.ti-arrow-left-c"
// Episodes
@ -73,60 +75,56 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
val seriesLink1 = document.select("ol[itemscope] li:last-child a").attr("href")
val seriesLink = document.select("input[name=red]").attr("value")
val type = document.select("div.dtsingle").attr("itemtype").substringAfterLast("/")
if (type.contains("Series")) {
// val seriesLink1 = document.select("ol[itemscope] li:last-child a").attr("href")
val seriesLink = document.select("link[rel=canonical]").attr("href")
val type = document.select("link[rel=canonical]").attr("href")
if (type.contains("animes")) {
val seasonsHtml = client.newCall(
GET(
seriesLink
// headers = Headers.headersOf("Referer", document.location())
)
).execute().asJsoup()
val seasonsElements = seasonsHtml.select("div.seasontitle a")
seasonsElements.forEach {
val seasonsElements = seasonsHtml.select("ul.chapters-list li a.title")
seasonsElements.reversed().forEach {
val seasonEpList = parseEpisodesFromSeries(it)
episodeList.addAll(seasonEpList)
}
} else {
val movieUrl = seriesLink
val episode = SEpisode.create()
episode.name = document.select("div.data h1").text()
episode.name = document.select("span.alt-title").text()
episode.episode_number = 1F
episode.setUrlWithoutDomain(movieUrl)
episodeList.add(episode)
}
return episodeList.reversed()
return episodeList
}
// override fun episodeFromElement(element: Element): SEpisode = throw Exception("not used")
private fun parseEpisodesFromSeries(element: Element): List<SEpisode> {
// val seasonId = element.attr("abs:href")
val seasonName = element.text()
// Log.i("seasonname", seasonName)
val episodesUrl = element.attr("abs:href")
val episodesHtml = client.newCall(
GET(
episodesUrl,
)
).execute().asJsoup()
val episodeElements = episodesHtml.select("ul.episodios li")
val episodeElements = episodesHtml.select("ul.chapters-list li")
return episodeElements.map { episodeFromElement(it) }
}
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
val epNum = getNumberFromEpsString(element.select("div.episodiotitle a").text())
val epNum = getNumberFromEpsString(element.select("a.title h3").text())
episode.episode_number = when {
(epNum.isNotEmpty()) -> epNum.toFloat()
else -> 1F
}
// element.select("td > span.Num").text().toFloat()
// val SeasonNum = element.ownerDocument().select("div.Title span").text()
val seasonName = element.ownerDocument().select("span.tagline").text()
episode.name = "$seasonName : " + element.select("div.episodiotitle a").text()
episode.setUrlWithoutDomain(element.select("div.episodiotitle a").attr("abs:href"))
val seasonName = element.ownerDocument().select("div.media-title h1").text()
episode.name = "$seasonName : " + element.select("a.title h3").text()
episode.setUrlWithoutDomain(element.select("a.title").attr("abs:href"))
return episode
}
@ -136,18 +134,12 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// Video urls
override fun videoListRequest(episode: SEpisode): Request {
val document = client.newCall(GET(baseUrl + episode.url)).execute().asJsoup()
val iframe = baseUrl + episode.url
return GET(iframe)
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return videosFromElement(document)
}
override fun videoListSelector() = "div.pframe" // ul#playeroptionsul
override fun videoListSelector() = "li.dooplay_player_option" // ul#playeroptionsul
private fun videosFromElement(document: Document): List<Video> {
val videoList = mutableListOf<Video>()
@ -155,7 +147,20 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
for (element in elements) {
val location = element.ownerDocument().location()
val videoHeaders = Headers.headersOf("Referer", location)
val embedUrl = element.select("iframe").attr("src")
val qualityy = element.text()
val post = element.attr("data-post")
val num = element.attr("data-nume")
val type = element.attr("data-type")
val pageData = FormBody.Builder()
.add("action", "doo_player_ajax")
.add("nume", num)
.add("post", post)
.add("type", type)
.build()
val ajaxUrl = "https://animerco.com/wp-admin/admin-ajax.php"
val callAjax = client.newCall(POST(ajaxUrl, videoHeaders, pageData)).execute().asJsoup()
val embedUrlT = callAjax.text().substringAfter("embed_url\":\"").substringBefore("\"")
val embedUrl = embedUrlT.replace("\\/", "/")
when {
embedUrl.contains("sbembed.com") || embedUrl.contains("sbembed1.com") || embedUrl.contains("sbplay.org") ||
@ -164,7 +169,10 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
embedUrl.contains("sbplay1.com") || embedUrl.contains("embedsb.com") || embedUrl.contains("watchsb.com") ||
embedUrl.contains("sbplay2.com") || embedUrl.contains("japopav.tv") || embedUrl.contains("viewsb.com") ||
embedUrl.contains("sbfast") || embedUrl.contains("sbfull.com") || embedUrl.contains("javplaya.com") ||
embedUrl.contains("ssbstream.net") || embedUrl.contains("p1ayerjavseen.com") || embedUrl.contains("sbthe.com")
embedUrl.contains("ssbstream.net") || embedUrl.contains("p1ayerjavseen.com") || embedUrl.contains("sbthe.com") ||
embedUrl.contains("vidmovie.xyz") || embedUrl.contains("sbspeed.com") || embedUrl.contains("streamsss.net") ||
embedUrl.contains("sblanh.com") || embedUrl.contains("tvmshow.com") || embedUrl.contains("sbanh.com") ||
embedUrl.contains("streamovies.xyz")
-> {
val videos = StreamSBExtractor(client).videosFromUrl(embedUrl, headers)
videoList.addAll(videos)
@ -179,7 +187,7 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
videoList.add(video)
}
}
embedUrl.contains("fembed.com") ||
embedUrl.contains("fembed") ||
embedUrl.contains("anime789.com") || embedUrl.contains("24hd.club") || embedUrl.contains("fembad.org") ||
embedUrl.contains("vcdn.io") || embedUrl.contains("sharinglink.club") || embedUrl.contains("moviemaniac.org") ||
embedUrl.contains("votrefiles.club") || embedUrl.contains("femoload.xyz") || embedUrl.contains("albavido.xyz") ||
@ -196,7 +204,10 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
embedUrl.contains("youvideos.ru") || embedUrl.contains("streamm4u.club") || // embedUrl.contains("") ||
embedUrl.contains("moviepl.xyz") || embedUrl.contains("asianclub.tv") || // embedUrl.contains("") ||
embedUrl.contains("vidcloud.fun") || embedUrl.contains("fplayer.info") || // embedUrl.contains("") ||
embedUrl.contains("diasfem.com") || embedUrl.contains("javpoll.com") // embedUrl.contains("")
embedUrl.contains("diasfem.com") || embedUrl.contains("javpoll.com") || embedUrl.contains("reeoov.tube") ||
embedUrl.contains("suzihaza.com") || embedUrl.contains("ezsubz.com") || embedUrl.contains("vidsrc.xyz") ||
embedUrl.contains("diampokusy.com") || embedUrl.contains("diampokusy.com") || embedUrl.contains("i18n.pw") ||
embedUrl.contains("vanfem.com") || embedUrl.contains("fembed9hd.com") || embedUrl.contains("votrefilms.xyz") || embedUrl.contains("watchjavnow.xyz")
-> {
val fUrl = embedUrl.replace("\\/", "/")
@ -204,6 +215,12 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val videos = FembedExtractor(client).videosFromUrl(fUrl)
videoList.addAll(videos)
}
embedUrl.contains("drive.google")
-> {
val embedUrlG = "https://gdriveplayer.to/embed2.php?link=" + embedUrl
val videos = GdrivePlayerExtractor(client).videosFromUrl(embedUrlG)
videoList.addAll(videos)
}
embedUrl.contains("streamtape") -> {
val video = StreamTapeExtractor(client).videoFromUrl(embedUrl)
if (video != null) {
@ -217,13 +234,6 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
videoList.add(video)
}
}
embedUrl.contains("4shared") -> {
val qualityy = "4shared"
val video = MpforuploadExtractor(client).videoFromUrl(embedUrl, qualityy)
if (video != null) {
videoList.add(video)
}
}
embedUrl.contains("uqload") -> {
val qualityy = "uqload"
val video = UQLoadExtractor(client).videoFromUrl(embedUrl, qualityy)
@ -280,15 +290,15 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.attr("href"))
anime.thumbnail_url = element.select("img").attr("src")
anime.title = element.select("img").attr("alt")
anime.setUrlWithoutDomain(element.select("a.image").attr("href"))
anime.thumbnail_url = element.select("a.image").attr("data-src")
anime.title = element.select("a.image").attr("alt")
return anime
}
override fun searchAnimeNextPageSelector(): String = "i#nextpagination"
override fun searchAnimeNextPageSelector(): String = "a.ti-arrow-left-c"
override fun searchAnimeSelector(): String = "div.image a"
override fun searchAnimeSelector(): String = "div.media-block"
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/page/$page/?s=$query")
@ -296,11 +306,12 @@ class Animerco : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = document.select("div.poster img").attr("src")
anime.title = document.select("div.data h1").text()
anime.genre = document.select("div.sgeneros a").joinToString(", ") { it.text() }
anime.description = document.select("div[itemprop=description] p").text()
anime.author = document.select("div.extra span a").joinToString(", ") { it.text() }
anime.thumbnail_url = document.select(".poster").attr("data-src")
anime.title = document.select("div.media-title h1").text()
anime.genre = document.select("nav.Nvgnrs a, ul.media-info li:contains(النوع) a").joinToString(", ") { it.text() }
anime.description = document.select("div.media-story p").text()
anime.author = document.select("ul.media-info li:contains(الشبكات) a").joinToString(", ") { it.text() }
anime.artist = document.select("ul.media-info li:contains(الأستوديو) a").joinToString(", ") { it.text() }
// anime.status = parseStatus(document.select("div.row-line:contains(Status)").text().replace("Status: ", ""))
return anime
}

View File

@ -0,0 +1,126 @@
package eu.kanade.tachiyomi.animeextension.ar.animerco.extractors
import android.util.Base64
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient
import java.security.DigestException
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class GdrivePlayerExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
val body = client.newCall(GET(url.replace(".me", ".to"))).execute()
.body!!.string()
val eval = JsUnpacker.unpackAndCombine(body)!!.replace("\\", "")
val json = Json.decodeFromString<JsonObject>(REGEX_DATAJSON.getFirst(eval))
val sojson = REGEX_SOJSON.getFirst(eval)
.split(Regex("\\D+"))
.joinToString("") {
Char(it.toInt()).toString()
}
val password = REGEX_PASSWORD.getFirst(sojson).toByteArray()
val decrypted = decryptAES(password, json)!!
val secondEval = JsUnpacker.unpackAndCombine(decrypted)!!.replace("\\", "")
return REGEX_VIDEOURL.findAll(secondEval)
.distinctBy { it.groupValues[2] } // remove duplicates by quality
.map {
val qualityStr = it.groupValues[2]
val quality = "$PLAYER_NAME - ${qualityStr}p"
val videoUrl = "https:" + it.groupValues[1] + "&res=$qualityStr"
Video(videoUrl, quality, videoUrl)
}.toList()
}
private fun decryptAES(password: ByteArray, json: JsonObject): String? {
val salt = json["s"]!!.jsonPrimitive.content
val encodedCiphetext = json["ct"]!!.jsonPrimitive.content
val ciphertext = Base64.decode(encodedCiphetext, Base64.DEFAULT)
val (key, iv) = GenerateKeyAndIv(password, salt.decodeHex())
?: return null
val keySpec = SecretKeySpec(key, "AES")
val ivSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
val decryptedData = String(cipher.doFinal(ciphertext))
return decryptedData
}
// https://stackoverflow.com/a/41434590/8166854
private fun GenerateKeyAndIv(
password: ByteArray,
salt: ByteArray,
hashAlgorithm: String = "MD5",
keyLength: Int = 32,
ivLength: Int = 16,
iterations: Int = 1
): List<ByteArray>? {
val md = MessageDigest.getInstance(hashAlgorithm)
val digestLength = md.getDigestLength()
val targetKeySize = keyLength + ivLength
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
var generatedData = ByteArray(requiredLength)
var generatedLength = 0
try {
md.reset()
while (generatedLength < targetKeySize) {
if (generatedLength > 0)
md.update(
generatedData,
generatedLength - digestLength,
digestLength
)
md.update(password)
md.update(salt, 0, 8)
md.digest(generatedData, generatedLength, digestLength)
for (i in 1 until iterations) {
md.update(generatedData, generatedLength, digestLength)
md.digest(generatedData, generatedLength, digestLength)
}
generatedLength += digestLength
}
val result = listOf(
generatedData.copyOfRange(0, keyLength),
generatedData.copyOfRange(keyLength, targetKeySize)
)
return result
} catch (e: DigestException) {
return null
}
}
private fun Regex.getFirst(item: String): String {
return find(item)?.groups?.elementAt(1)?.value!!
}
// Stolen from AnimixPlay(EN) / GogoCdnExtractor
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
private const val PLAYER_NAME = "GDRIVE"
private val REGEX_DATAJSON = Regex("data='(\\S+?)'")
private val REGEX_PASSWORD = Regex("var pass = \"(\\S+?)\"")
private val REGEX_SOJSON = Regex("null,['|\"](\\w+)['|\"]")
private val REGEX_VIDEOURL = Regex("file\":\"(\\S+?)\".*?res=(\\d+)")
}
}