Initial commit
This commit is contained in:
commit
98ed7e8839
2263 changed files with 108711 additions and 0 deletions
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>
|
18
src/all/javguru/build.gradle
Normal file
18
src/all/javguru/build.gradle
Normal 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'))
|
||||
}
|
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,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()
|
||||
}
|
||||
}
|
|
@ -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,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*\'([^\']+)""")
|
||||
}
|
||||
}
|
|
@ -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" })
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue