Hentaidexy: move to individual extension (#15766)

* Hentaidexy: rebuild with new api

* small changes

* image url patch

* bunch of refactoring

* remove from Madara

* lint

* add alternative names

* make some things nullable
This commit is contained in:
mobi2002 2023-03-24 07:33:57 +05:00 committed by GitHub
parent cebf2a01a4
commit f862fe6f12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 330 additions and 14 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.extension.en.hentaidexy
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Hentaidexy : Madara("Hentaidexy", "https://hentaidexy.net", "en") {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 1, TimeUnit.SECONDS)
.build()
}

View File

@ -127,7 +127,6 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("Hentai Manga", "https://hentaimanga.me", "en", isNsfw = true, overrideVersionCode = 1),
SingleLang("Hentai Teca", "https://hentaiteca.net", "pt-BR", isNsfw = true, overrideVersionCode = 1),
SingleLang("Hentai20", "https://hentai20.io", "en", isNsfw = true, overrideVersionCode = 3),
SingleLang("Hentaidexy", "https://hentaidexy.net", "en", isNsfw = true, overrideVersionCode = 3),
SingleLang("HentaiRead", "https://hentairead.com", "en", isNsfw = true, className = "Hentairead", overrideVersionCode = 3),
SingleLang("HentaiWebtoon", "https://hentaiwebtoon.com", "en", isNsfw = true, overrideVersionCode = 1),
SingleLang("HentaiXComic", "https://hentaixcomic.com", "en", isNsfw = true),

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.extension">
<application>
<activity
android:name=".en.hentaidexy.HentaidexyUrlActivity"
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="hentaidexy.net"/>
<data android:host="www.hentaidexy.net"/>
<data
android:pathPattern="/manga/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Hentaidexy'
pkgNameSuffix = 'en.hentaidexy'
extClass = '.Hentaidexy'
extVersionCode = 32
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,206 @@
package eu.kanade.tachiyomi.extension.en.hentaidexy
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Hentaidexy : HttpSource() {
override val name = "Hentaidexy"
override val baseUrl = "https://hentaidexy.net"
private val apiUrl = "https://backend.hentaidexy.net"
override val lang = "en"
override val supportsLatest = true
override val versionId = 2
private val json: Json by injectLazy()
override val client: OkHttpClient = super.client.newBuilder()
.rateLimitHost(apiUrl.toHttpUrl(), 1)
.build()
override fun headersBuilder() = Headers.Builder().apply {
add("Referer", "$baseUrl/")
}
// popular
override fun popularMangaRequest(page: Int): Request {
return GET("$apiUrl/api/v1/mangas?page=$page&limit=100&sort=-views", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<ApiMangaResponse>(response.body.string())
return MangasPage(
result.mangas.map { manga ->
toSManga(manga).apply {
initialized = true
}
},
hasNextPage = result.totalPages > result.page,
)
}
// latest
override fun latestUpdatesRequest(page: Int): Request {
return GET("$apiUrl/api/v1/mangas?page=$page&limit=100&sort=-updatedAt", headers)
}
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
// search
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (!query.startsWith(ID_SEARCH_PREFIX)) {
return super.fetchSearchManga(page, query, filters)
}
val id = query.substringAfter(ID_SEARCH_PREFIX)
return fetchMangaDetails(SManga.create().apply { url = id }).map {
MangasPage(listOf(it), false)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$apiUrl/api/v1/mangas?page=$page&altTitles=$query&sort=createdAt", headers)
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
// manga details
override fun mangaDetailsRequest(manga: SManga): Request {
return GET("$apiUrl/api/v1/mangas/${manga.url}", headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val mangaDetails = json.decodeFromString<MangaDetails>(response.body.string())
return toSManga(mangaDetails.manga)
}
override fun getMangaUrl(manga: SManga): String {
val mangaId = manga.url
val slug = manga.title.replace(' ', '-')
return "$baseUrl/manga/$mangaId/$slug"
}
// chapter list
override fun chapterListRequest(manga: SManga): Request {
return paginatedChapterListRequest(manga.url, 1)
}
private fun paginatedChapterListRequest(mangaID: String, page: Int): Request {
return GET("$apiUrl/api/v1/mangas/$mangaID/chapters?sort=-serialNumber&limit=100&page=$page", headers)
}
override fun chapterListParse(response: Response): List<SChapter> {
val chapterListResponse = json.decodeFromString<ApiChapterResponse>(response.body.string())
val mangaId = response.request.url.toString()
.substringAfter("/mangas/")
.substringBefore("/chapters")
val totalPages = chapterListResponse.totalPages
var currentPage = 1
while (totalPages > currentPage) {
currentPage++
val newRequest = paginatedChapterListRequest(mangaId, currentPage)
val newResponse = client.newCall(newRequest).execute()
val newChapterListResponse = json.decodeFromString<ApiChapterResponse>(newResponse.body.string())
chapterListResponse.chapters += newChapterListResponse.chapters
}
return chapterListResponse.chapters.map { chapter ->
SChapter.create().apply {
url = "/manga/$mangaId/chapter/${chapter._id}"
name = "Chapter " + chapter.serialNumber.parseChapterNumber()
date_upload = chapter.createdAt.parseDate()
}
}
}
override fun getChapterUrl(chapter: SChapter): String {
return "$baseUrl${chapter.url}"
}
// page list
override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringAfterLast('/')
return GET("$apiUrl/api/v1/chapters/$chapterId", headers)
}
override fun pageListParse(response: Response): List<Page> {
val result = json.decodeFromString<PageList>(response.body.string())
return result.chapter.images.mapIndexed { index, image ->
Page(index = index, imageUrl = image)
}
}
// unused
override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException("Not Used")
}
// Helpers
private fun toSManga(manga: Manga): SManga {
return SManga.create().apply {
url = manga._id
title = manga.title
author = manga.authors?.joinToString { it.trim() }
artist = author
description = manga.summary.trim() + "\n\nAlternative Names: ${manga.altTitles?.joinToString { it.trim() }}"
genre = manga.genres?.joinToString { it.trim() }
status = manga.status.parseStatus()
thumbnail_url = manga.coverImage
}
}
private fun String.parseStatus(): Int {
return when {
this.contains("ongoing", true) -> SManga.ONGOING
this.contains("complete", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
private fun Float.parseChapterNumber(): String {
return if (this.toInt().toFloat() == this) {
this.toInt().toString()
} else {
this.toString()
}
}
private fun String.parseDate(): Long {
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
}
const val ID_SEARCH_PREFIX = "id:"
}
}

View File

@ -0,0 +1,51 @@
package eu.kanade.tachiyomi.extension.en.hentaidexy
import kotlinx.serialization.Serializable
@Serializable
data class ApiMangaResponse(
val page: Int,
val totalPages: Int,
val mangas: Array<Manga>,
)
@Serializable
data class Manga(
val _id: String,
val title: String,
val altTitles: Array<String>?,
val coverImage: String,
val summary: String = "Unknown",
val authors: Array<String>?,
val genres: Array<String>?,
val status: String,
)
@Serializable
data class MangaDetails(
val manga: Manga,
)
@Serializable
data class ApiChapterResponse(
val page: Int,
val totalPages: Int,
val chapters: MutableList<Chapter>,
)
@Serializable
data class Chapter(
val _id: String,
val serialNumber: Float,
val createdAt: String,
)
@Serializable
data class PageList(
val chapter: ChapterPageData,
)
@Serializable
data class ChapterPageData(
val images: Array<String>,
)

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.extension.en.hentaidexy
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 HentaidexyUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val id = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${Hentaidexy.ID_SEARCH_PREFIX}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("HentaidexyUrlActivity", e.toString())
}
} else {
Log.e("HentaidexyUrlActivity", "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}