mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-25 11:42:47 +01:00
parent
80f5d2461e
commit
bba2693814
@ -1,7 +1,9 @@
|
||||
ext {
|
||||
extName = 'VyvyManga'
|
||||
extClass = '.VyvyManga'
|
||||
extVersionCode = 5
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://vyvymanga.org'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -1,176 +1,13 @@
|
||||
package eu.kanade.tachiyomi.extension.en.vyvymanga
|
||||
|
||||
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
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class VyvyManga : ParsedHttpSource() {
|
||||
override val name = "VyvyManga"
|
||||
class VyvyManga : Madara(
|
||||
name = "VyvyManga",
|
||||
baseUrl = "https://vyvymanga.org",
|
||||
lang = "en",
|
||||
) {
|
||||
override val versionId = 2
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||
}
|
||||
|
@ -1,108 +0,0 @@
|
||||
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"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user