Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View 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>

View file

@ -0,0 +1,18 @@
ext {
extName = 'Jav Guru'
extClass = '.JavGuru'
extVersionCode = 15
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:streamwish-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'))
implementation(project(':lib:javcoverfetcher'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -0,0 +1,381 @@
package eu.kanade.tachiyomi.animeextension.all.javguru
import android.app.Application
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.all.javguru.extractors.EmTurboExtractor
import eu.kanade.tachiyomi.animeextension.all.javguru.extractors.MaxStreamExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
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.javcoverfetcher.JavCoverFetcher
import eu.kanade.tachiyomi.lib.javcoverfetcher.JavCoverFetcher.fetchHDCovers
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
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 uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.math.min
class JavGuru : AnimeHttpSource(), ConfigurableAnimeSource {
override val name = "Jav Guru"
override val baseUrl = "https://jav.guru"
override val lang = "all"
override val supportsLatest = true
private val noRedirectClient = client.newBuilder()
.followRedirects(false)
.build()
private val preference by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private lateinit var popularElements: Elements
override suspend fun getPopularAnime(page: Int): AnimesPage {
return if (page == 1) {
client.newCall(popularAnimeRequest(page))
.awaitSuccess()
.use(::popularAnimeParse)
} else {
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()
.pageNumberFromUrlOrNull() ?: 1
val lastPage = document.select("div.wp-pagenavi a")
.last()
?.attr("href")
.pageNumberFromUrlOrNull() ?: 1
return AnimesPage(entries, page < lastPage)
}
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
if (query.startsWith(PREFIX_ID)) {
val id = query.substringAfter(PREFIX_ID)
if (id.toIntOrNull() == null) {
return AnimesPage(emptyList(), false)
}
val url = "/$id/"
val tempAnime = SAnime.create().apply { this.url = url }
return getAnimeDetails(tempAnime).let {
val anime = it.apply { this.url = url }
AnimesPage(listOf(anime), false)
}
} else if (query.isNotEmpty()) {
return client.newCall(searchAnimeRequest(page, query, filters))
.awaitSuccess()
.use(::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)
.awaitSuccess()
.use(::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)
.awaitIgnoreCode(404)
.use(::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()
val javId = document.selectFirst(".infoleft li:contains(code)")?.ownText()
val siteCover = document.select(".large-screenshot img").attr("abs:src")
return SAnime.create().apply {
title = document.select(".titl").text()
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") }
}
thumbnail_url = if (preference.fetchHDCovers) {
javId?.let { JavCoverFetcher.getCoverById(it) } ?: siteCover
} else {
siteCover
}
}
}
override suspend fun getEpisodeList(anime: SAnime): List<SEpisode> {
return 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(::resolveHosterUrl)
.parallelCatchingFlatMapBlocking(::getVideos)
}
private fun resolveHosterUrl(iframeUrl: String): String? {
val iframeResponse = client.newCall(GET(iframeUrl, headers)).execute()
if (iframeResponse.isSuccessful.not()) {
iframeResponse.close()
return null
}
val iframeDocument = iframeResponse.asJsoup()
val script = iframeDocument.selectFirst("script:containsData(start_player)")
?.html() ?: return null
val olid = IFRAME_OLID_REGEX.find(script)?.groupValues?.get(1)?.reversed()
?: return null
val olidUrl = IFRAME_OLID_URL.find(script)?.groupValues?.get(1)
?.substringBeforeLast("=")?.let { "$it=$olid" }
?: return null
val newHeaders = headersBuilder()
.set("Referer", iframeUrl)
.build()
val redirectUrl = noRedirectClient.newCall(GET(olidUrl, newHeaders))
.execute().use { it.header("location") }
?: return null
if (redirectUrl.toHttpUrlOrNull() == null) {
return null
}
return redirectUrl
}
private val streamWishExtractor by lazy {
val swHeaders = headersBuilder()
.set("Referer", "$baseUrl/")
.build()
StreamWishExtractor(client, swHeaders)
}
private val streamTapeExtractor by lazy { StreamTapeExtractor(client) }
private val doodExtractor by lazy { DoodExtractor(client) }
private val mixDropExtractor by lazy { MixDropExtractor(client) }
private val maxStreamExtractor by lazy { MaxStreamExtractor(client, headers) }
private val emTurboExtractor by lazy { EmTurboExtractor(client, headers) }
private fun getVideos(hosterUrl: String): List<Video> {
return when {
listOf("javplaya", "javclan").any { it in hosterUrl } -> {
streamWishExtractor.videosFromUrl(hosterUrl)
}
hosterUrl.contains("streamtape") -> {
streamTapeExtractor.videoFromUrl(hosterUrl).let(::listOfNotNull)
}
listOf("dood", "ds2play").any { it in hosterUrl } -> {
doodExtractor.videosFromUrl(hosterUrl)
}
listOf("mixdrop", "mixdroop").any { it in hosterUrl } -> {
mixDropExtractor.videoFromUrl(hosterUrl)
}
hosterUrl.contains("maxstream") -> {
maxStreamExtractor.videoFromUrl(hosterUrl)
}
hosterUrl.contains("emturbovid") -> {
emTurboExtractor.getVideos(hosterUrl)
}
else -> emptyList()
}
}
override fun List<Video>.sort(): List<Video> {
val quality = preference.getString(PREF_QUALITY, PREF_QUALITY_DEFAULT)!!
return sortedWith(
compareBy { it.quality.contains(quality) },
).reversed()
}
private fun getIDFromUrl(element: Elements): String? {
return element.attr("abs:href")
.toHttpUrlOrNull()
?.pathSegments
?.firstOrNull()
?.toIntOrNull()
?.toString()
?.let { "/$it/" }
}
private fun String?.pageNumberFromUrlOrNull() =
this
?.substringBeforeLast("/")
?.toHttpUrlOrNull()
?.pathSegments
?.last()
?.toIntOrNull()
private suspend fun Call.awaitIgnoreCode(code: Int): Response {
return await().also { response ->
if (!response.isSuccessful && response.code != code) {
response.close()
throw Exception("HTTP error ${response.code}")
}
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY
title = PREF_QUALITY_TITLE
entries = arrayOf("1080p", "720p", "480p", "360p")
entryValues = arrayOf("1080", "720", "480", "360")
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
}.also(screen::addPreference)
JavCoverFetcher.addPreferenceToScreen(screen)
}
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 const val PREF_QUALITY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "720"
}
override fun episodeListParse(response: Response): List<SEpisode> {
throw UnsupportedOperationException()
}
}

View file

@ -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/"),
)

View file

@ -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)
}
}

View file

@ -0,0 +1,36 @@
package eu.kanade.tachiyomi.animeextension.all.javguru.extractors
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.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
class EmTurboExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playlistExtractor by lazy { PlaylistUtils(client, headers) }
fun getVideos(url: String): List<Video> {
val document = client.newCall(GET(url, headers)).execute().asJsoup()
val script = document.selectFirst("script:containsData(urlplay)")
?.data()
?: return emptyList()
val urlPlay = URLPLAY.find(script)?.groupValues?.get(1)
?: return emptyList()
if (urlPlay.toHttpUrlOrNull() == null) {
return emptyList()
}
return playlistExtractor.extractFromHls(urlPlay, url, videoNameGen = { quality -> "EmTurboVid: $quality" })
.distinctBy { it.url } // they have the same stream repeated twice in the playlist file
}
companion object {
private val URLPLAY = Regex("""urlPlay\s*=\s*\'([^\']+)""")
}
}

View file

@ -0,0 +1,32 @@
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.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
class MaxStreamExtractor(private val client: OkHttpClient, private val headers: Headers) {
private val playListUtils by lazy { PlaylistUtils(client, headers) }
fun videoFromUrl(url: String): List<Video> {
val document = client.newCall(GET(url, headers)).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" })
}
}