diff --git a/src/tr/hentaizm/AndroidManifest.xml b/src/tr/hentaizm/AndroidManifest.xml new file mode 100644 index 000000000..f7e34e55b --- /dev/null +++ b/src/tr/hentaizm/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/tr/hentaizm/build.gradle b/src/tr/hentaizm/build.gradle new file mode 100644 index 000000000..63cda2eb3 --- /dev/null +++ b/src/tr/hentaizm/build.gradle @@ -0,0 +1,16 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + +} + +ext { + extName = 'HentaiZM' + pkgNameSuffix = 'tr.hentaizm' + extClass = '.HentaiZM' + extVersionCode = 1 + libVersion = '13' + containsNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/tr/hentaizm/res/mipmap-hdpi/ic_launcher.png b/src/tr/hentaizm/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..84d096943 Binary files /dev/null and b/src/tr/hentaizm/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/tr/hentaizm/res/mipmap-mdpi/ic_launcher.png b/src/tr/hentaizm/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..e4098e5ed Binary files /dev/null and b/src/tr/hentaizm/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/tr/hentaizm/res/mipmap-xhdpi/ic_launcher.png b/src/tr/hentaizm/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..57a90381a Binary files /dev/null and b/src/tr/hentaizm/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/tr/hentaizm/res/mipmap-xxhdpi/ic_launcher.png b/src/tr/hentaizm/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..a1e2b98dd Binary files /dev/null and b/src/tr/hentaizm/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/tr/hentaizm/res/mipmap-xxxhdpi/ic_launcher.png b/src/tr/hentaizm/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4dc7eb3e6 Binary files /dev/null and b/src/tr/hentaizm/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/tr/hentaizm/src/eu/kanade/tachiyomi/animeextension/tr/hentaizm/HentaiZM.kt b/src/tr/hentaizm/src/eu/kanade/tachiyomi/animeextension/tr/hentaizm/HentaiZM.kt new file mode 100644 index 000000000..83c414bb1 --- /dev/null +++ b/src/tr/hentaizm/src/eu/kanade/tachiyomi/animeextension/tr/hentaizm/HentaiZM.kt @@ -0,0 +1,228 @@ +package eu.kanade.tachiyomi.animeextension.tr.hentaizm + +import android.app.Application +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.tr.hentaizm.extractors.VideaExtractor +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.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class HentaiZM : ParsedAnimeHttpSource(), ConfigurableAnimeSource { + + override val name = "HentaiZM" + + override val baseUrl = "https://www.hentaizm.fun" + + override val lang = "tr" + + override val supportsLatest = true + + override fun headersBuilder() = super.headersBuilder() + .add("Origin", baseUrl) + .add("Referer", "$baseUrl/") + + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + init { + runBlocking { + withContext(Dispatchers.IO) { + val body = FormBody.Builder() + .add("user", "demo") + .add("pass", "demo") // peak security + .add("redirect_to", baseUrl) + .build() + + val headers = headersBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() + + client.newCall(POST("$baseUrl/giris", headers, body)).execute() + .close() + } + } + } + + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/en-cok-izlenenler/page/$page", headers) + + override fun popularAnimeParse(response: Response) = + super.popularAnimeParse(response).let { page -> + val animes = page.animes.distinctBy { it.url } + AnimesPage(animes, page.hasNextPage) + } + + override fun popularAnimeSelector() = "div.moviefilm" + + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + title = element.selectFirst("div.movief > a")!!.text() + .substringBefore(". Bölüm") + .substringBeforeLast(" ") + element.selectFirst("img")!!.attr("abs:src").also { + thumbnail_url = it + val slug = it.substringAfterLast("/").substringBefore(".") + setUrlWithoutDomain("/hentai-detay/$slug") + } + } + + override fun popularAnimeNextPageSelector() = "span.current + a" + + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/yeni-eklenenler?c=${page - 1}", headers) + + override fun latestUpdatesParse(response: Response) = + super.latestUpdatesParse(response).let { page -> + val animes = page.animes.distinctBy { it.url } + AnimesPage(animes, page.hasNextPage) + } + + override fun latestUpdatesSelector() = popularAnimeSelector() + + override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) + + override fun latestUpdatesNextPageSelector() = "a[rel=next]:contains(Sonraki Sayfa)" + + // =============================== Search =============================== + override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { + return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler + val id = query.removePrefix(PREFIX_SEARCH) + client.newCall(GET("$baseUrl/hentai-detay/$id")) + .asObservableSuccess() + .map(::searchAnimeByIdParse) + } else { + super.fetchSearchAnime(page, query, filters) + } + } + + private fun searchAnimeByIdParse(response: Response): AnimesPage { + val details = animeDetailsParse(response.use { it.asJsoup() }) + return AnimesPage(listOf(details), false) + } + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + return GET("$baseUrl/page/$page/?s=$query", headers) + } + + override fun searchAnimeParse(response: Response) = popularAnimeParse(response) + + override fun searchAnimeSelector() = throw UnsupportedOperationException("Not used.") + + override fun searchAnimeFromElement(element: Element) = throw UnsupportedOperationException("Not used.") + + override fun searchAnimeNextPageSelector() = null + + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document) = SAnime.create().apply { + setUrlWithoutDomain(document.location()) + val content = document.selectFirst("div.filmcontent")!! + title = content.selectFirst("h1")!!.text() + thumbnail_url = content.selectFirst("img")!!.attr("abs:src") + genre = content.select("tr:contains(Hentai Türü) > td > a").eachText().joinToString() + description = content.selectFirst("tr:contains(Özet) + tr > td") + ?.text() + ?.takeIf(String::isNotBlank) + } + + // ============================== Episodes ============================== + override fun episodeListSelector() = "div#Bolumler li > a" + + override fun episodeFromElement(element: Element) = SEpisode.create().apply { + setUrlWithoutDomain(element.attr("href")) + element.text().also { + val num = it.substringBeforeLast(". Bölüm", "") + .substringAfterLast(" ") + .ifBlank { "1" } + + episode_number = num.toFloatOrNull() ?: 1F + name = "$num. Bölüm" + } + } + + // ============================ Video Links ============================= + private val videaExtractor by lazy { VideaExtractor(client) } + + override fun videoListParse(response: Response): List