Split VyvyManga and VyvyManga.org extensions (#5704)

* Revert "VyvyManga: Migrate theme (#5573)"

This reverts commit bba2693814d306417366fc03dcc8b21bf2e1d740.

* VyvyManga: bump version

* VyvyManga.org: move to other extension
This commit is contained in:
Vetle Ledaal 2024-10-27 08:06:19 +01:00 committed by Draff
parent da0bb1b8eb
commit 17fc1acd6c
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
10 changed files with 301 additions and 11 deletions

View File

@ -1,9 +1,7 @@
ext {
extName = 'VyvyManga'
extClass = '.VyvyManga'
themePkg = 'madara'
baseUrl = 'https://vyvymanga.org'
overrideVersionCode = 0
extVersionCode = 37
isNsfw = true
}

View File

@ -1,13 +1,176 @@
package eu.kanade.tachiyomi.extension.en.vyvymanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
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.ParsedHttpSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class VyvyManga : Madara(
name = "VyvyManga",
baseUrl = "https://vyvymanga.org",
lang = "en",
) {
override val versionId = 2
class VyvyManga : ParsedHttpSource() {
override val name = "VyvyManga"
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val baseUrl = "https://vymanga.net"
override val lang = "en"
override val supportsLatest = true
private val dateFormat = SimpleDateFormat("MMM dd, yyy", Locale.US)
// Popular
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/search" + if (page != 1) "?page=$page" else "", headers)
override fun popularMangaSelector(): String =
searchMangaSelector()
override fun popularMangaFromElement(element: Element): SManga =
searchMangaFromElement(element)
override fun popularMangaNextPageSelector(): String =
searchMangaNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("page", page.toString())
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is SearchType -> url.addQueryParameter("search_po", filter.selected)
is SearchDescription -> if (filter.state) url.addQueryParameter("check_search_desc", "1")
is AuthorSearchType -> url.addQueryParameter("author_po", filter.selected)
is AuthorFilter -> url.addQueryParameter("author", filter.state)
is StatusFilter -> url.addQueryParameter("completed", filter.selected)
is SortFilter -> url.addQueryParameter("sort", filter.selected)
is SortType -> url.addQueryParameter("sort_type", filter.selected)
is GenreFilter -> {
filter.state.forEach {
if (!it.isIgnored()) url.addQueryParameter(if (it.isIncluded()) "genre[]" else "exclude_genre[]", it.id)
}
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector(): String = ".comic-item"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
title = element.selectFirst(".comic-title")!!.text()
thumbnail_url = element.selectFirst(".comic-image")!!.absUrl("data-background-image")
}
override fun searchMangaNextPageSelector(): String = "[rel=next]"
// Latest
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/search?sort=updated_at" + if (page != 1) "&page=$page" else "", headers)
override fun latestUpdatesSelector(): String =
searchMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga =
searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() =
searchMangaNextPageSelector()
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.selectFirst("h1")!!.text()
artist = document.selectFirst(".pre-title:contains(Artist) ~ a")?.text()
author = document.selectFirst(".pre-title:contains(Author) ~ a")?.text()
description = document.selectFirst(".summary > .content")!!.text()
genre = document.select(".pre-title:contains(Genres) ~ a").joinToString { it.text() }
status = when (document.selectFirst(".pre-title:contains(Status) ~ span:not(.space)")?.text()) {
"Ongoing" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.selectFirst(".img-manga")!!.absUrl("src")
}
// Chapters
override fun chapterListSelector(): String =
".list-group > a"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
url = element.absUrl("href")
name = element.selectFirst("span")!!.text()
date_upload = parseChapterDate(element.selectFirst("> p")?.text())
}
// Pages
override fun pageListRequest(chapter: SChapter): Request =
GET(chapter.url, headers)
override fun pageListParse(document: Document): List<Page> {
return document.select("img.d-block").mapIndexed { index, element ->
Page(index, "", element.absUrl("data-src"))
}
}
override fun imageUrlParse(document: Document): String {
throw UnsupportedOperationException()
}
// Other
// Date logic lifted from Madara
private fun parseChapterDate(date: String?): Long {
date ?: return 0
fun SimpleDateFormat.tryParse(string: String): Long {
return try {
parse(string)?.time ?: 0
} catch (_: ParseException) {
0
}
}
return when {
"ago".endsWith(date) -> {
parseRelativeDate(date)
}
else -> dateFormat.tryParse(date)
}
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
date.contains("day") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
date.contains("hour") -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
date.contains("minute") -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
date.contains("second") -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
else -> 0
}
}
override fun getFilterList(): FilterList {
launchIO { fetchGenres(baseUrl, headers, client) }
return FilterList(
SearchType(),
SearchDescription(),
AuthorSearchType(),
AuthorFilter(),
StatusFilter(),
SortFilter(),
SortType(),
GenreFilter(),
)
}
}

View File

@ -0,0 +1,108 @@
package eu.kanade.tachiyomi.extension.en.vyvymanga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jsoup.nodes.Document
abstract class SelectFilter(displayName: String, private val options: Array<Pair<String, String>>) :
Filter.Select<String>(
displayName,
options.map { it.first }.toTypedArray(),
) {
open val selected get() = options[state].second.takeUnless { it.isEmpty() }
}
class SearchType : SelectFilter(
"Title should contain/begin/end with typed text",
arrayOf(
Pair("Contain", "0"),
Pair("Begin", "1"),
Pair("End", "2"),
),
)
class SearchDescription : Filter.CheckBox("Search In Description")
class AuthorSearchType : SelectFilter(
"Author should contain/begin/end with typed text",
arrayOf(
Pair("Contain", "0"),
Pair("Begin", "1"),
Pair("End", "2"),
),
)
class AuthorFilter : Filter.Text("Author")
class StatusFilter : SelectFilter(
"Status",
arrayOf(
Pair("All", "2"),
Pair("Ongoing", "0"),
Pair("Completed", "1"),
),
)
class SortFilter : SelectFilter(
"Sort by",
arrayOf(
Pair("Viewed", "viewed"),
Pair("Scored", "scored"),
Pair("Newest", "created_at"),
Pair("Latest Update", "updated_at"),
),
)
class SortType : SelectFilter(
"Sort order",
arrayOf(
Pair("Descending", "desc"),
Pair("Ascending", "asc"),
),
)
class Genre(name: String, val id: String) : Filter.TriState(name)
class GenreFilter : Filter.Group<Genre>("Genre", genrePairs.map { Genre(it.name, it.id) })
private var genrePairs: List<Genre> = emptyList()
private val scope = CoroutineScope(Dispatchers.IO)
fun launchIO(block: () -> Unit) = scope.launch { block() }
private var fetchGenresAttempts: Int = 0
fun fetchGenres(baseUrl: String, headers: okhttp3.Headers, client: okhttp3.OkHttpClient) {
if (fetchGenresAttempts < 3 && genrePairs.isEmpty()) {
try {
genrePairs =
client.newCall(genresRequest(baseUrl, headers)).execute()
.asJsoup()
.let(::parseGenres)
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
private fun genresRequest(baseUrl: String, headers: okhttp3.Headers) = GET("$baseUrl/search", headers)
private const val genresSelector = ".check-genre div div:has(.checkbox-genre)"
private fun parseGenres(document: Document): List<Genre> {
val items = document.select(genresSelector)
return buildList(items.size) {
items.mapTo(this) {
Genre(
it.select("label").text(),
it.select(".checkbox-genre").attr("data-value"),
)
}
}
}

View File

@ -0,0 +1,10 @@
ext {
extName = 'VyvyManga.org'
extClass = '.VyvyMangaOrg'
themePkg = 'madara'
baseUrl = 'https://vyvymanga.org'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.extension.en.vyvymangaorg
import eu.kanade.tachiyomi.multisrc.madara.Madara
class VyvyMangaOrg : Madara(
name = "VyvyManga.org",
baseUrl = "https://vyvymanga.org",
lang = "en",
) {
override val useLoadMoreRequest = LoadMoreStrategy.Always
}