diff --git a/src/en/vyvymanga/build.gradle b/src/en/vyvymanga/build.gradle index 5b28fc048..706bef44e 100644 --- a/src/en/vyvymanga/build.gradle +++ b/src/en/vyvymanga/build.gradle @@ -1,7 +1,9 @@ ext { extName = 'VyvyManga' extClass = '.VyvyManga' - extVersionCode = 5 + themePkg = 'madara' + baseUrl = 'https://vyvymanga.org' + overrideVersionCode = 0 isNsfw = true } diff --git a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt index 6841c0e61..49a597873 100644 --- a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt +++ b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt @@ -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 { - 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 } diff --git a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt b/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt deleted file mode 100644 index 4785fc530..000000000 --- a/src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyMangaFilters.kt +++ /dev/null @@ -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>) : - Filter.Select( - 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", genrePairs.map { Genre(it.name, it.id) }) - -private var genrePairs: List = 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 { - val items = document.select(genresSelector) - return buildList(items.size) { - items.mapTo(this) { - Genre( - it.select("label").text(), - it.select(".checkbox-genre").attr("data-value"), - ) - } - } -}