feat(arc/all): New source Jav Guru (#1930)
This commit is contained in:
22
src/all/javguru/AndroidManifest.xml
Normal file
22
src/all/javguru/AndroidManifest.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".all.javguru.JavGuruUrlActivity"
|
||||
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="jav.guru"
|
||||
android:pathPattern="/.*/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
23
src/all/javguru/build.gradle
Normal file
23
src/all/javguru/build.gradle
Normal file
@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
ext {
|
||||
extName = 'Jav Guru'
|
||||
pkgNameSuffix = 'all.javguru'
|
||||
extClass = '.JavGuru'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-streamsb-extractor'))
|
||||
implementation(project(':lib-streamtape-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
implementation(project(':lib-mixdrop-extractor'))
|
||||
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
|
||||
implementation(project(':lib-playlist-utils'))
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/all/javguru/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/all/javguru/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/all/javguru/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/all/javguru/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
src/all/javguru/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/all/javguru/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
src/all/javguru/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/all/javguru/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
BIN
src/all/javguru/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/all/javguru/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
src/all/javguru/res/web_hi_res_512.png
Normal file
BIN
src/all/javguru/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -0,0 +1,335 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.javguru
|
||||
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.animeextension.all.javguru.extractors.MaxStreamExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
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.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
|
||||
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.asObservable
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Call
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.select.Elements
|
||||
import rx.Observable
|
||||
import kotlin.math.min
|
||||
|
||||
class JavGuru : AnimeHttpSource() {
|
||||
|
||||
override val name = "Jav Guru"
|
||||
|
||||
override val baseUrl = "https://jav.guru"
|
||||
|
||||
override val lang = "all"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val streamSbExtractor: StreamSBExtractor by lazy {
|
||||
StreamSBExtractor(client)
|
||||
}
|
||||
|
||||
private val streamTapeExtractor: StreamTapeExtractor by lazy {
|
||||
StreamTapeExtractor(client)
|
||||
}
|
||||
|
||||
private val doodExtractor: DoodExtractor by lazy {
|
||||
DoodExtractor(client)
|
||||
}
|
||||
|
||||
private val mixDropExtractor: MixDropExtractor by lazy {
|
||||
MixDropExtractor(client)
|
||||
}
|
||||
|
||||
private val maxStreamExtractor: MaxStreamExtractor by lazy {
|
||||
MaxStreamExtractor(client)
|
||||
}
|
||||
|
||||
private lateinit var popularElements: Elements
|
||||
|
||||
override fun fetchPopularAnime(page: Int): Observable<AnimesPage> {
|
||||
return if (page == 1) {
|
||||
client.newCall(popularAnimeRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map(::popularAnimeParse)
|
||||
} else {
|
||||
Observable.just(cachedPopularAnimeParse(page))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularAnimeRequest(page: Int) =
|
||||
GET("$baseUrl/most-watched-rank/", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
popularElements = response.asJsoup().select(".tabcontent li")
|
||||
|
||||
return cachedPopularAnimeParse(1)
|
||||
}
|
||||
|
||||
private fun cachedPopularAnimeParse(page: Int): AnimesPage {
|
||||
val end = min(page * 20, popularElements.size)
|
||||
val entries = popularElements.subList((page - 1) * 20, end).map { element ->
|
||||
SAnime.create().apply {
|
||||
element.select("a").let { a ->
|
||||
getIDFromUrl(a)?.let { url = it }
|
||||
?: setUrlWithoutDomain(a.attr("href"))
|
||||
|
||||
title = a.text()
|
||||
thumbnail_url = a.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
}
|
||||
return AnimesPage(entries, end < popularElements.size)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val url = baseUrl + if (page > 1) "/page/$page/" else ""
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val entries = document.select("div.site-content div.inside-article:not(:contains(nothing))").map { element ->
|
||||
SAnime.create().apply {
|
||||
element.select("a").let { a ->
|
||||
getIDFromUrl(a)?.let { url = it }
|
||||
?: setUrlWithoutDomain(a.attr("href"))
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
title = element.select("h2 > a").text()
|
||||
}
|
||||
}
|
||||
|
||||
val page = document.location()
|
||||
.substringBeforeLast("/").toHttpUrlOrNull()
|
||||
?.pathSegments?.last()?.toIntOrNull() ?: 1
|
||||
|
||||
val lastPage = document.select("div.wp-pagenavi a")
|
||||
.last()?.attr("href")?.substringBeforeLast("/")
|
||||
?.toHttpUrlOrNull()?.pathSegments?.last()?.toIntOrNull() ?: 1
|
||||
|
||||
return AnimesPage(entries, page < lastPage)
|
||||
}
|
||||
|
||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
||||
if (query.startsWith(PREFIX_ID)) {
|
||||
val id = query.substringAfter(PREFIX_ID)
|
||||
if (id.toIntOrNull() == null) {
|
||||
return Observable.just(AnimesPage(emptyList(), false))
|
||||
}
|
||||
val url = "/$id/"
|
||||
val tempAnime = SAnime.create().apply { this.url = url }
|
||||
return fetchAnimeDetails(tempAnime).map {
|
||||
val anime = it.apply { this.url = url }
|
||||
AnimesPage(listOf(anime), false)
|
||||
}
|
||||
} else if (query.isNotEmpty()) {
|
||||
return client.newCall(searchAnimeRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map(::searchAnimeParse)
|
||||
} else {
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is TagFilter,
|
||||
is CategoryFilter,
|
||||
-> {
|
||||
if (filter.state != 0) {
|
||||
val url = "$baseUrl${filter.toUrlPart()}" + if (page > 1) "page/$page/" else ""
|
||||
val request = GET(url, headers)
|
||||
return client.newCall(request)
|
||||
.asObservableIgnoreCode(404)
|
||||
.map(::searchAnimeParse)
|
||||
}
|
||||
}
|
||||
is ActressFilter,
|
||||
is ActorFilter,
|
||||
is StudioFilter,
|
||||
is MakerFilter,
|
||||
-> {
|
||||
if ((filter.state as String).isNotEmpty()) {
|
||||
val url = "$baseUrl${filter.toUrlPart()}" + if (page > 1) "page/$page/" else ""
|
||||
val request = GET(url, headers)
|
||||
return client.newCall(request)
|
||||
.asObservableIgnoreCode(404)
|
||||
.map(::searchAnimeParse)
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw Exception("Select at least one Filter")
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
if (page > 1) addPathSegments("page/$page/")
|
||||
addQueryParameter("s", query)
|
||||
}.build().toString()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun getFilterList() = getFilters()
|
||||
|
||||
override fun searchAnimeParse(response: Response) = latestUpdatesParse(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
|
||||
return SAnime.create().apply {
|
||||
title = document.select(".titl").text()
|
||||
thumbnail_url = document.select(".large-screenshot img").attr("abs:src")
|
||||
genre = document.select(".infoleft a[rel*=tag]").joinToString { it.text() }
|
||||
author = document.selectFirst(".infoleft li:contains(studio) a")?.text()
|
||||
artist = document.selectFirst(".infoleft li:contains(label) a")?.text()
|
||||
status = SAnime.COMPLETED
|
||||
description = buildString {
|
||||
document.selectFirst(".infoleft li:contains(code)")?.text()?.let { append("$it\n") }
|
||||
document.selectFirst(".infoleft li:contains(director)")?.text()?.let { append("$it\n") }
|
||||
document.selectFirst(".infoleft li:contains(studio)")?.text()?.let { append("$it\n") }
|
||||
document.selectFirst(".infoleft li:contains(label)")?.text()?.let { append("$it\n") }
|
||||
document.selectFirst(".infoleft li:contains(actor)")?.text()?.let { append("$it\n") }
|
||||
document.selectFirst(".infoleft li:contains(actress)")?.text()?.let { append("$it\n") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||
return Observable.just(
|
||||
listOf(
|
||||
SEpisode.create().apply {
|
||||
url = anime.url
|
||||
name = "Episode"
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val iframeData = document.selectFirst("script:containsData(iframe_url)")?.html()
|
||||
?: return emptyList()
|
||||
|
||||
val iframeUrls = IFRAME_B64_REGEX.findAll(iframeData)
|
||||
.map { it.groupValues[1] }
|
||||
.map { Base64.decode(it, Base64.DEFAULT).let(::String) }
|
||||
.toList()
|
||||
|
||||
return iframeUrls.mapNotNull { url ->
|
||||
runCatching {
|
||||
val iframeResponse = client.newCall(GET(url, headers)).execute()
|
||||
|
||||
if (iframeResponse.isSuccessful.not()) {
|
||||
iframeResponse.close()
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val iframeDocument = iframeResponse.asJsoup()
|
||||
|
||||
val script = iframeDocument.selectFirst("script:containsData(start_player)")
|
||||
?.html() ?: return@mapNotNull null
|
||||
|
||||
val olid = IFRAME_OLID_REGEX.find(script)?.groupValues?.get(1)?.reversed()
|
||||
?: return@mapNotNull null
|
||||
|
||||
val olidUrl = IFRAME_OLID_URL.find(script)?.groupValues?.get(1)?.substringBeforeLast("=")?.let { "$it=$olid" }
|
||||
?: return@mapNotNull null
|
||||
|
||||
val newHeaders = headersBuilder()
|
||||
.set("Referer", url)
|
||||
.build()
|
||||
|
||||
val redirectUrl = client.newCall(GET(olidUrl, newHeaders))
|
||||
.execute().request.url.toString()
|
||||
|
||||
when {
|
||||
STREAM_SB_DOMAINS.any { it in redirectUrl } -> {
|
||||
streamSbExtractor.videosFromUrl(redirectUrl, headers)
|
||||
}
|
||||
redirectUrl.contains("streamtape") -> {
|
||||
streamTapeExtractor.videoFromUrl(redirectUrl)?.let { listOf(it) }
|
||||
}
|
||||
redirectUrl.contains("dood") -> {
|
||||
doodExtractor.videosFromUrl(redirectUrl)
|
||||
}
|
||||
MIXDROP_DOMAINS.any { it in redirectUrl } -> {
|
||||
mixDropExtractor.videoFromUrl(redirectUrl)
|
||||
}
|
||||
redirectUrl.contains("maxstream") -> {
|
||||
maxStreamExtractor.videoFromUrl(redirectUrl)
|
||||
}
|
||||
else -> { null }
|
||||
}
|
||||
}.getOrNull()
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private fun getIDFromUrl(element: Elements): String? {
|
||||
return element.attr("abs:href")
|
||||
.toHttpUrlOrNull()
|
||||
?.pathSegments
|
||||
?.firstOrNull()
|
||||
?.toIntOrNull()
|
||||
?.toString()
|
||||
?.let { "/$it/" }
|
||||
}
|
||||
|
||||
private fun Call.asObservableIgnoreCode(code: Int): Observable<Response> {
|
||||
return asObservable().doOnNext { response ->
|
||||
if (!response.isSuccessful && response.code != code) {
|
||||
response.close()
|
||||
throw Exception("HTTP error ${response.code}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREFIX_ID = "id:"
|
||||
|
||||
private val IFRAME_B64_REGEX = Regex(""""iframe_url":"([^"]+)"""")
|
||||
private val IFRAME_OLID_REGEX = Regex("""var OLID = '([^']+)'""")
|
||||
private val IFRAME_OLID_URL = Regex("""src="([^"]+)"""")
|
||||
|
||||
private val STREAM_SB_DOMAINS = listOf(
|
||||
"sbhight", "sbrity", "sbembed.com", "sbembed1.com", "sbplay.org",
|
||||
"sbvideo.net", "streamsb.net", "sbplay.one", "cloudemb.com",
|
||||
"playersb.com", "tubesb.com", "sbplay1.com", "embedsb.com",
|
||||
"watchsb.com", "sbplay2.com", "japopav.tv", "viewsb.com",
|
||||
"sbfast", "sbfull.com", "javplaya.com", "ssbstream.net",
|
||||
"p1ayerjavseen.com", "sbthe.com", "vidmovie.xyz", "sbspeed.com",
|
||||
"streamsss.net", "sblanh.com", "tvmshow.com", "sbanh.com",
|
||||
"streamovies.xyz", "sblona.com",
|
||||
)
|
||||
private val MIXDROP_DOMAINS = listOf(
|
||||
"mixdrop",
|
||||
"mixdroop",
|
||||
)
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
throw UnsupportedOperationException("Not used")
|
||||
}
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.javguru
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
fun getFilters() = AnimeFilterList(
|
||||
AnimeFilter.Header("Only One Filter Works at a time!!"),
|
||||
AnimeFilter.Header("Ignored With Text Search!!"),
|
||||
TagFilter(),
|
||||
CategoryFilter(),
|
||||
AnimeFilter.Separator(),
|
||||
ActressFilter(),
|
||||
ActorFilter(),
|
||||
StudioFilter(),
|
||||
MakerFilter(),
|
||||
)
|
||||
|
||||
class UriPartFilter(val name: String, val urlPart: String)
|
||||
|
||||
abstract class UriPartFilters(name: String, private val tags: List<UriPartFilter>) :
|
||||
AnimeFilter.Select<String>(name, tags.map { it.name }.toTypedArray()) {
|
||||
fun toUrlPart() = tags[state].urlPart
|
||||
}
|
||||
|
||||
class TagFilter : UriPartFilters("Tags", TAGS)
|
||||
|
||||
class CategoryFilter : UriPartFilters("Categories", CATEGORIES)
|
||||
|
||||
abstract class TextFilter(name: String, private val urlSubDirectory: String) : AnimeFilter.Text(name) {
|
||||
fun toUrlPart() = state.trim()
|
||||
.lowercase()
|
||||
.replace(SPECIAL_CHAR_REGEX, "-")
|
||||
.replace(TRAILING_HIPHEN_REGEX, "")
|
||||
.let { "/$urlSubDirectory/$it/" }
|
||||
|
||||
companion object {
|
||||
private val SPECIAL_CHAR_REGEX = "[^a-z0-9]+".toRegex()
|
||||
private val TRAILING_HIPHEN_REGEX = "-+$".toRegex()
|
||||
}
|
||||
}
|
||||
|
||||
class ActressFilter : TextFilter("Actress", "actress")
|
||||
|
||||
class ActorFilter : TextFilter("Actor", "actor")
|
||||
|
||||
class StudioFilter : TextFilter("Studio", "studio")
|
||||
|
||||
class MakerFilter : TextFilter("Maker", "maker")
|
||||
|
||||
fun <T> AnimeFilter<T>.toUrlPart(): String? {
|
||||
return when (this) {
|
||||
is TagFilter -> this.toUrlPart()
|
||||
is CategoryFilter -> this.toUrlPart()
|
||||
is ActressFilter -> this.toUrlPart()
|
||||
is ActorFilter -> this.toUrlPart()
|
||||
is StudioFilter -> this.toUrlPart()
|
||||
is MakerFilter -> this.toUrlPart()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val TAGS = listOf(
|
||||
UriPartFilter("", "/"),
|
||||
UriPartFilter("Solowork", "/tag/solowork/"),
|
||||
UriPartFilter("Creampie", "/tag/creampie/"),
|
||||
UriPartFilter("Big tits", "/tag/big-tits/"),
|
||||
UriPartFilter("Beautiful Girl", "/tag/beautiful-girl/"),
|
||||
UriPartFilter("Married Woman", "/tag/married-woman/"),
|
||||
UriPartFilter("Amateur", "/tag/amateur/"),
|
||||
UriPartFilter("Digital Mosaic", "/tag/digital-mosaic/"),
|
||||
UriPartFilter("Slut", "/tag/slut/"),
|
||||
UriPartFilter("Mature Woman", "/tag/mature-woman/"),
|
||||
UriPartFilter("Cuckold", "/tag/cuckold/"),
|
||||
UriPartFilter("3P", "/tag/3p/"),
|
||||
UriPartFilter("Slender", "/tag/slender/"),
|
||||
UriPartFilter("Blow", "/tag/blow/"),
|
||||
UriPartFilter("Squirting", "/tag/squirting/"),
|
||||
UriPartFilter("Drama", "/tag/drama/"),
|
||||
UriPartFilter("Nasty", "/tag/nasty/"),
|
||||
UriPartFilter("Hardcore", "/tag/hardcore/"),
|
||||
UriPartFilter("School Girls", "/tag/school-girls/"),
|
||||
UriPartFilter("4P", "/tag/4p/"),
|
||||
UriPartFilter("Titty fuck", "/tag/titty-fuck/"),
|
||||
UriPartFilter("Cowgirl", "/tag/cowgirl/"),
|
||||
UriPartFilter("Incest", "/tag/incest/"),
|
||||
UriPartFilter("Facials", "/tag/facials/"),
|
||||
UriPartFilter("breasts", "/tag/breasts/"),
|
||||
UriPartFilter("abuse", "/tag/abuse/"),
|
||||
UriPartFilter("Risky Mosaic", "/tag/risky-mosaic/"),
|
||||
UriPartFilter("Debut Production", "/tag/debut-production/"),
|
||||
UriPartFilter("Older sister", "/tag/older-sister/"),
|
||||
UriPartFilter("Huge Butt", "/tag/huge-butt/"),
|
||||
UriPartFilter("4HR+", "/tag/4hr/"),
|
||||
UriPartFilter("Affair", "/tag/affair/"),
|
||||
UriPartFilter("Kiss", "/tag/kiss/"),
|
||||
UriPartFilter("Deep Throating", "/tag/deep-throating/"),
|
||||
UriPartFilter("Documentary", "/tag/documentary/"),
|
||||
UriPartFilter("Mini", "/tag/mini/"),
|
||||
UriPartFilter("Entertainer", "/tag/entertainer/"),
|
||||
UriPartFilter("Dirty Words", "/tag/dirty-words/"),
|
||||
UriPartFilter("Cosplay", "/tag/cosplay/"),
|
||||
UriPartFilter("POV", "/tag/pov/"),
|
||||
UriPartFilter("Shaved", "/tag/shaved/"),
|
||||
UriPartFilter("butt", "/tag/butt/"),
|
||||
UriPartFilter("OL", "/tag/ol/"),
|
||||
UriPartFilter("Tits", "/tag/tits/"),
|
||||
UriPartFilter("Promiscuity", "/tag/promiscuity/"),
|
||||
UriPartFilter("Restraint", "/tag/restraint/"),
|
||||
UriPartFilter("Gal", "/tag/gal/"),
|
||||
UriPartFilter("planning", "/tag/planning/"),
|
||||
UriPartFilter("Subjectivity", "/tag/subjectivity/"),
|
||||
UriPartFilter("Handjob", "/tag/handjob/"),
|
||||
UriPartFilter("Uniform", "/tag/uniform/"),
|
||||
UriPartFilter("Sister", "/tag/sister/"),
|
||||
UriPartFilter("Humiliation", "/tag/humiliation/"),
|
||||
UriPartFilter("Prostitutes", "/tag/prostitutes/"),
|
||||
UriPartFilter("School Uniform", "/tag/school-uniform/"),
|
||||
UriPartFilter("Rape", "/tag/rape/"),
|
||||
UriPartFilter("Lesbian", "/tag/lesbian/"),
|
||||
UriPartFilter("Anal", "/tag/anal/"),
|
||||
UriPartFilter("Image video", "/tag/image-video/"),
|
||||
UriPartFilter("Pantyhose", "/tag/pantyhose/"),
|
||||
UriPartFilter("Other fetish", "/tag/other-fetish/"),
|
||||
UriPartFilter("Female College Student", "/tag/female-college-student/"),
|
||||
UriPartFilter("Female teacher", "/tag/female-teacher/"),
|
||||
UriPartFilter("Bukkake", "/tag/bukkake/"),
|
||||
UriPartFilter("Training", "/tag/training/"),
|
||||
UriPartFilter("Cum", "/tag/cum/"),
|
||||
UriPartFilter("Masturbation", "/tag/masturbation/"),
|
||||
UriPartFilter("Sweat", "/tag/sweat/"),
|
||||
UriPartFilter("Omnibus", "/tag/omnibus/"),
|
||||
UriPartFilter("Best", "/tag/best/"),
|
||||
UriPartFilter("Lotion", "/tag/lotion/"),
|
||||
UriPartFilter("Girl", "/tag/girl/"),
|
||||
UriPartFilter("Submissive Men", "/tag/submissive-men/"),
|
||||
UriPartFilter("Outdoors", "/tag/outdoors/"),
|
||||
UriPartFilter("Beauty Shop", "/tag/beauty-shop/"),
|
||||
UriPartFilter("Busty fetish", "/tag/busty-fetish/"),
|
||||
UriPartFilter("Toy", "/tag/toy/"),
|
||||
UriPartFilter("Urination", "/tag/urination/"),
|
||||
UriPartFilter("huge cock", "/tag/huge-cock/"),
|
||||
UriPartFilter("Gangbang", "/tag/gangbang/"),
|
||||
UriPartFilter("Massage", "/tag/massage/"),
|
||||
UriPartFilter("Tall", "/tag/tall/"),
|
||||
UriPartFilter("Hot Spring", "/tag/hot-spring/"),
|
||||
UriPartFilter("virgin man", "/tag/virgin-man/"),
|
||||
UriPartFilter("Various Professions", "/tag/various-professions/"),
|
||||
UriPartFilter("Bride", "/tag/bride/"),
|
||||
UriPartFilter("Leg Fetish", "/tag/leg-fetish/"),
|
||||
UriPartFilter("Young wife", "/tag/young-wife/"),
|
||||
UriPartFilter("Maid", "/tag/maid/"),
|
||||
UriPartFilter("BBW", "/tag/bbw/"),
|
||||
UriPartFilter("SM", "/tag/sm/"),
|
||||
UriPartFilter("Restraints", "/tag/restraints/"),
|
||||
UriPartFilter("Lesbian Kiss", "/tag/lesbian-kiss/"),
|
||||
UriPartFilter("Voyeur", "/tag/voyeur/"),
|
||||
UriPartFilter("Mother", "/tag/mother/"),
|
||||
UriPartFilter("Evil", "/tag/evil/"),
|
||||
UriPartFilter("Underwear", "/tag/underwear/"),
|
||||
UriPartFilter("Nurse", "/tag/nurse/"),
|
||||
UriPartFilter("Glasses", "/tag/glasses/"),
|
||||
UriPartFilter("Lingerie", "/tag/lingerie/"),
|
||||
UriPartFilter("Drug", "/tag/drug/"),
|
||||
UriPartFilter("Nampa", "/tag/nampa/"),
|
||||
UriPartFilter("School Swimsuit", "/tag/school-swimsuit/"),
|
||||
UriPartFilter("Stepmother", "/tag/stepmother/"),
|
||||
UriPartFilter("Sailor suit", "/tag/sailor-suit/"),
|
||||
UriPartFilter("Prank", "/tag/prank/"),
|
||||
UriPartFilter("Cunnilingus", "/tag/cunnilingus/"),
|
||||
UriPartFilter("Electric Massager", "/tag/electric-massager/"),
|
||||
UriPartFilter("Molester", "/tag/molester/"),
|
||||
UriPartFilter("Black Actor", "/tag/black-actor/"),
|
||||
UriPartFilter("Ultra-Huge Tits", "/tag/ultra-huge-tits/"),
|
||||
UriPartFilter("Original Collaboration", "/tag/original-collaboration/"),
|
||||
UriPartFilter("Confinement", "/tag/confinement/"),
|
||||
UriPartFilter("Shotacon", "/tag/shotacon/"),
|
||||
UriPartFilter("Footjob", "/tag/footjob/"),
|
||||
UriPartFilter("Female Boss", "/tag/female-boss/"),
|
||||
UriPartFilter("Female investigator", "/tag/female-investigator/"),
|
||||
UriPartFilter("Swimsuit", "/tag/swimsuit/"),
|
||||
UriPartFilter("Bloomers", "/tag/bloomers/"),
|
||||
UriPartFilter("Facesitting", "/tag/facesitting/"),
|
||||
UriPartFilter("Kimono", "/tag/kimono/"),
|
||||
UriPartFilter("Mourning", "/tag/mourning/"),
|
||||
UriPartFilter("White Actress", "/tag/white-actress/"),
|
||||
UriPartFilter("Acme · Orgasm", "/tag/acme-%c2%b7-orgasm/"),
|
||||
UriPartFilter("Sun tan", "/tag/sun-tan/"),
|
||||
UriPartFilter("Finger Fuck", "/tag/finger-fuck/"),
|
||||
UriPartFilter("Transsexual", "/tag/transsexual/"),
|
||||
UriPartFilter("Blu-ray", "/tag/blu-ray/"),
|
||||
UriPartFilter("VR", "/tag/vr/"),
|
||||
UriPartFilter("Cross Dressing", "/tag/cross-dressing/"),
|
||||
UriPartFilter("Soapland", "/tag/soapland/"),
|
||||
UriPartFilter("Fan Appreciation", "/tag/fan-appreciation/"),
|
||||
UriPartFilter("AV Actress", "/tag/av-actress/"),
|
||||
UriPartFilter("School Stuff", "/tag/school-stuff/"),
|
||||
UriPartFilter("Love", "/tag/love/"),
|
||||
UriPartFilter("Close Up", "/tag/close-up/"),
|
||||
UriPartFilter("Submissive Woman", "/tag/submissive-woman/"),
|
||||
UriPartFilter("Mini Skirt", "/tag/mini-skirt/"),
|
||||
UriPartFilter("Impromptu Sex", "/tag/impromptu-sex/"),
|
||||
UriPartFilter("Vibe", "/tag/vibe/"),
|
||||
UriPartFilter("Bitch", "/tag/bitch/"),
|
||||
UriPartFilter("Enema", "/tag/enema/"),
|
||||
UriPartFilter("Hypnosis", "/tag/hypnosis/"),
|
||||
UriPartFilter("Childhood Friend", "/tag/childhood-friend/"),
|
||||
UriPartFilter("Erotic Wear", "/tag/erotic-wear/"),
|
||||
UriPartFilter("Tutor", "/tag/tutor/"),
|
||||
UriPartFilter("Male Squirting", "/tag/male-squirting/"),
|
||||
UriPartFilter("Bath", "/tag/bath/"),
|
||||
UriPartFilter("Conceived", "/tag/conceived/"),
|
||||
UriPartFilter("Stewardess", "/tag/stewardess/"),
|
||||
UriPartFilter("Sport", "/tag/sport/"),
|
||||
UriPartFilter("Bunny Girl", "/tag/bunny-girl/"),
|
||||
UriPartFilter("Piss Drinking", "/tag/piss-drinking/"),
|
||||
UriPartFilter("Shibari", "/tag/shibari/"),
|
||||
UriPartFilter("Couple", "/tag/couple/"),
|
||||
UriPartFilter("Anchorwoman", "/tag/anchorwoman/"),
|
||||
UriPartFilter("Delusion", "/tag/delusion/"),
|
||||
UriPartFilter("69", "/tag/69/"),
|
||||
UriPartFilter("Secretary", "/tag/secretary/"),
|
||||
UriPartFilter("Idol", "/tag/idol/"),
|
||||
UriPartFilter("Elder Male", "/tag/elder-male/"),
|
||||
UriPartFilter("Cervix", "/tag/cervix/"),
|
||||
UriPartFilter("Leotard", "/tag/leotard/"),
|
||||
UriPartFilter("Miss", "/tag/miss/"),
|
||||
UriPartFilter("Back", "/tag/back/"),
|
||||
UriPartFilter("blog", "/tag/blog/"),
|
||||
UriPartFilter("virgin", "/tag/virgin/"),
|
||||
UriPartFilter("Female Doctor", "/tag/female-doctor/"),
|
||||
UriPartFilter("No Bra", "/tag/no-bra/"),
|
||||
UriPartFilter("Tsundere", "/tag/tsundere/"),
|
||||
UriPartFilter("Race Queen", "/tag/race-queen/"),
|
||||
UriPartFilter("Multiple Story", "/tag/multiple-story/"),
|
||||
UriPartFilter("Widow", "/tag/widow/"),
|
||||
UriPartFilter("Actress Best", "/tag/actress-best/"),
|
||||
UriPartFilter("Bondage", "/tag/bondage/"),
|
||||
UriPartFilter("Muscle", "/tag/muscle/"),
|
||||
UriPartFilter("User Submission", "/tag/user-submission/"),
|
||||
UriPartFilter("Breast Milk", "/tag/breast-milk/"),
|
||||
UriPartFilter("Sexy", "/tag/sexy/"),
|
||||
UriPartFilter("Travel", "/tag/travel/"),
|
||||
UriPartFilter("Knee Socks", "/tag/knee-socks/"),
|
||||
UriPartFilter("Date", "/tag/date/"),
|
||||
UriPartFilter("For Women", "/tag/for-women/"),
|
||||
UriPartFilter("Premature Ejaculation", "/tag/premature-ejaculation/"),
|
||||
UriPartFilter("Hi-Def", "/tag/hi-def/"),
|
||||
UriPartFilter("Time Stop", "/tag/time-stop/"),
|
||||
UriPartFilter("Subordinates / Colleagues", "/tag/subordinates-colleagues/"),
|
||||
UriPartFilter("Adopted Daughter", "/tag/adopted-daughter/"),
|
||||
UriPartFilter("Instructor", "/tag/instructor/"),
|
||||
UriPartFilter("Catgirl", "/tag/catgirl/"),
|
||||
UriPartFilter("Body Conscious", "/tag/body-conscious/"),
|
||||
UriPartFilter("Fighting Action", "/tag/fighting-action/"),
|
||||
UriPartFilter("Featured Actress", "/tag/featured-actress/"),
|
||||
UriPartFilter("Hostess", "/tag/hostess/"),
|
||||
UriPartFilter("Dead Drunk", "/tag/dead-drunk/"),
|
||||
UriPartFilter("Landlady", "/tag/landlady/"),
|
||||
UriPartFilter("Business Attire", "/tag/business-attire/"),
|
||||
UriPartFilter("Dildo", "/tag/dildo/"),
|
||||
UriPartFilter("Reversed Role", "/tag/reversed-role/"),
|
||||
UriPartFilter("Foreign Objects", "/tag/foreign-objects/"),
|
||||
UriPartFilter("Athlete", "/tag/athlete/"),
|
||||
UriPartFilter("Aunt", "/tag/aunt/"),
|
||||
UriPartFilter("Model", "/tag/model/"),
|
||||
UriPartFilter("Big Breasts", "/tag/big-breasts/"),
|
||||
UriPartFilter("Oversea Import", "/tag/oversea-import/"),
|
||||
UriPartFilter("Drinking Party", "/tag/drinking-party/"),
|
||||
UriPartFilter("Booth Girl", "/tag/booth-girl/"),
|
||||
UriPartFilter("Car Sex", "/tag/car-sex/"),
|
||||
UriPartFilter("Blowjob", "/tag/blowjob/"),
|
||||
UriPartFilter("Other Asian", "/tag/other-asian/"),
|
||||
UriPartFilter("Special Effects", "/tag/special-effects/"),
|
||||
UriPartFilter("Spanking", "/tag/spanking/"),
|
||||
UriPartFilter("Club Activities / Manager", "/tag/club-activities-manager/"),
|
||||
UriPartFilter("Naked Apron", "/tag/naked-apron/"),
|
||||
UriPartFilter("Fantasy", "/tag/fantasy/"),
|
||||
UriPartFilter("Female Warrior", "/tag/female-warrior/"),
|
||||
UriPartFilter("Anime Characters", "/tag/anime-characters/"),
|
||||
UriPartFilter("Sex Conversion / Feminized", "/tag/sex-conversion-feminized/"),
|
||||
UriPartFilter("Flexible", "/tag/flexible/"),
|
||||
UriPartFilter("Schoolgirl", "/tag/schoolgirl/"),
|
||||
UriPartFilter("Long Boots", "/tag/long-boots/"),
|
||||
UriPartFilter("No Undies", "/tag/no-undies/"),
|
||||
UriPartFilter("Immediate Oral", "/tag/immediate-oral/"),
|
||||
UriPartFilter("Hospital / Clinic", "/tag/hospital-clinic/"),
|
||||
UriPartFilter("Dance", "/tag/dance/"),
|
||||
UriPartFilter("Breast Peeker", "/tag/breast-peeker/"),
|
||||
UriPartFilter("Waitress", "/tag/waitress/"),
|
||||
UriPartFilter("Futanari", "/tag/futanari/"),
|
||||
UriPartFilter("Rolling Back Eyes / Fainting", "/tag/rolling-back-eyes-fainting/"),
|
||||
UriPartFilter("Hotel", "/tag/hotel/"),
|
||||
UriPartFilter("Exposure", "/tag/exposure/"),
|
||||
UriPartFilter("Torture", "/tag/torture/"),
|
||||
UriPartFilter("Office Lady", "/tag/office-lady/"),
|
||||
UriPartFilter("Masturbation Support", "/tag/masturbation-support/"),
|
||||
UriPartFilter("facial", "/tag/facial/"),
|
||||
UriPartFilter("Egg Vibrator", "/tag/egg-vibrator/"),
|
||||
UriPartFilter("Fisting", "/tag/fisting/"),
|
||||
UriPartFilter("Vomit", "/tag/vomit/"),
|
||||
UriPartFilter("Orgy", "/tag/orgy/"),
|
||||
UriPartFilter("Cruel Expression", "/tag/cruel-expression/"),
|
||||
UriPartFilter("Doll", "/tag/doll/"),
|
||||
UriPartFilter("Loose Socks", "/tag/loose-socks/"),
|
||||
UriPartFilter("Best of 2021", "/tag/best-of-2021/"),
|
||||
UriPartFilter("Reserved Role", "/tag/reserved-role/"),
|
||||
UriPartFilter("Best of 2019", "/tag/best-of-2019/"),
|
||||
UriPartFilter("Mother-in-law", "/tag/mother-in-law/"),
|
||||
UriPartFilter("Gay", "/tag/gay/"),
|
||||
UriPartFilter("Swingers", "/tag/swingers/"),
|
||||
UriPartFilter("Best of 2020", "/tag/best-of-2020/"),
|
||||
UriPartFilter("Mistress", "/tag/mistress/"),
|
||||
UriPartFilter("Shame", "/tag/shame/"),
|
||||
UriPartFilter("Yukata", "/tag/yukata/"),
|
||||
UriPartFilter("Best of 2017", "/tag/best-of-2017/"),
|
||||
UriPartFilter("Best of 2018", "/tag/best-of-2018/"),
|
||||
UriPartFilter("Nose Hook", "/tag/nose-hook/"),
|
||||
)
|
||||
|
||||
val CATEGORIES = listOf(
|
||||
UriPartFilter("", "/"),
|
||||
UriPartFilter("1080p", "/category/1080p/"),
|
||||
UriPartFilter("4K", "/category/4k/"),
|
||||
UriPartFilter("Amateur", "/category/amateur/"),
|
||||
UriPartFilter("Blog", "/category/blog/"),
|
||||
UriPartFilter("Decensored", "/category/decensored/"),
|
||||
UriPartFilter("English subbed JAV", "/category/english-subbed/"),
|
||||
UriPartFilter("FC2", "/category/fc2/"),
|
||||
UriPartFilter("HD", "/category/hd/"),
|
||||
UriPartFilter("Idol", "/category/idol/"),
|
||||
UriPartFilter("JAV", "/category/jav/"),
|
||||
UriPartFilter("LEGACY", "/category/legacy/"),
|
||||
UriPartFilter("UNCENSORED", "/category/jav-uncensored/"),
|
||||
UriPartFilter("VR AV", "/category/vr-av/"),
|
||||
)
|
@ -0,0 +1,34 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.javguru
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class JavGuruUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val id = pathSegments[0]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", "${JavGuru.PREFIX_ID}$id")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("JavGuruUrlActivity", e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e("JavGuruUrlActivity", "could not parse uri from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.javguru.extractors
|
||||
|
||||
import dev.datlag.jsunpacker.JsUnpacker
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class MaxStreamExtractor(private val client: OkHttpClient) {
|
||||
|
||||
private val playListUtils: PlaylistUtils by lazy {
|
||||
PlaylistUtils(client)
|
||||
}
|
||||
|
||||
fun videoFromUrl(url: String): List<Video> {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
|
||||
val script = document.selectFirst("script:containsData(function(p,a,c,k,e,d))")
|
||||
?.data()
|
||||
?.let(JsUnpacker::unpackAndCombine)
|
||||
?: return emptyList()
|
||||
|
||||
val videoUrl = script.substringAfter("file:\"").substringBefore("\"")
|
||||
|
||||
if (videoUrl.toHttpUrlOrNull() == null) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return playListUtils.extractFromHls(videoUrl, url, videoNameGen = { quality -> "MaxStream: $quality" })
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user