Initial commit of new Doujins extension (#5261)
This commit is contained in:
parent
fc5024e7a3
commit
cfaca77d60
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,13 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'Doujins'
|
||||
pkgNameSuffix = 'en.doujins'
|
||||
extClass = '.Doujins'
|
||||
extVersionCode = 1
|
||||
libVersion = '1.2'
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,253 @@
|
|||
package eu.kanade.tachiyomi.extension.en.doujins
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.get
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
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 eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Nsfw
|
||||
class Doujins : HttpSource() {
|
||||
|
||||
override val baseUrl: String = "https://doujins.com"
|
||||
|
||||
override val lang: String = "en"
|
||||
|
||||
override val name: String = "Doujins"
|
||||
|
||||
override val supportsLatest: Boolean = true
|
||||
|
||||
private val gson = Gson()
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return listOf(
|
||||
SChapter.create().apply {
|
||||
name = "Chapter"
|
||||
setUrlWithoutDomain(response.request().url().toString())
|
||||
|
||||
val date = response.asJsoup().select(".folder-message").last().text().substringBefore(" • ")
|
||||
for (dateFormat in MANGA_DETAILS_DATE_FORMAT) {
|
||||
if (date_upload == 0L)
|
||||
date_upload = dateFormat.parseOrNull(date)?.time ?: 0L
|
||||
else
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
return MangasPage(
|
||||
gson.fromJson<JsonObject>(response.body()!!.string())["folders"].asJsonArray.map {
|
||||
SManga.create().apply {
|
||||
setUrlWithoutDomain(it["link"].asString)
|
||||
title = it["name"].asString
|
||||
artist = it["artistList"].asString
|
||||
author = artist
|
||||
genre = it["tags"].asJsonArray.joinToString(", ") { it["tag"].asString }
|
||||
thumbnail_url = it["thumbnail2"].asString
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
private fun getLatestPageUrl(page: Int): String {
|
||||
val endDate = Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, 1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
add(Calendar.DATE, -1 * PAGE_DAYS * (page - 1))
|
||||
}
|
||||
|
||||
val endDateSec = endDate.timeInMillis / 1000
|
||||
val startDateSec = endDate.apply {
|
||||
add(Calendar.DATE, -1 * PAGE_DAYS)
|
||||
}.timeInMillis / 1000
|
||||
|
||||
return "$baseUrl/folders?start=$startDateSec&end=$endDateSec"
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET(getLatestPageUrl(page))
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val document = response.asJsoup()
|
||||
return SManga.create().apply {
|
||||
title = document.select(".folder-title a").last().text()
|
||||
artist = document.select(".gallery-artist a").joinToString(", ") { it.text() }
|
||||
author = artist
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.asJsoup()
|
||||
val pageUrl = response.request().url().toString()
|
||||
return document.select(".doujin").mapIndexed { i, page ->
|
||||
Page(i, "$pageUrl${page.attr("data-link")}", page.attr("data-file").replace("amp;", ""))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response) = parseGalleryPage(response.asJsoup())
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/top/month")
|
||||
|
||||
override fun searchMangaParse(response: Response) = parseGalleryPage(response.asJsoup())
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val seriesFilter = filterList.findInstance<SeriesFilter>()!!
|
||||
val sortFilter = filterList.findInstance<SortFilter>()!!
|
||||
val popularityPeriodFilter = filterList.findInstance<PopularityPeriodFilter>()!!
|
||||
|
||||
return when {
|
||||
query != "" -> {
|
||||
GET("$baseUrl/searches?words=$query&page=$page&sort=${sortFilter.toUriPart()}")
|
||||
}
|
||||
seriesFilter.toUriPart() != "" -> {
|
||||
GET("$baseUrl${seriesFilter.toUriPart()}?sort=${sortFilter.toUriPart()}")
|
||||
}
|
||||
else -> {
|
||||
GET("$baseUrl${popularityPeriodFilter.toUriPart()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseGalleryPage(document: Document): MangasPage {
|
||||
|
||||
val pagination = document.select(".pagination").first()
|
||||
return MangasPage(
|
||||
document.select("a.gallery-visited-from-favorites").map {
|
||||
SManga.create().apply {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.select("div.title .text").text()
|
||||
artist = it.parent().nextElementSibling().select(".single-line strong")?.last()?.text()?.substringAfter("Artist: ")
|
||||
author = artist
|
||||
thumbnail_url = it.select("img").attr("srcset")
|
||||
}
|
||||
},
|
||||
if (pagination != null) {
|
||||
!pagination.select("li.page-item:last-child").hasClass("disabled")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("Text search ignores series and period filters"),
|
||||
Filter.Separator(),
|
||||
|
||||
Filter.Header("Series filter overrides period filter"),
|
||||
SeriesFilter(),
|
||||
Filter.Separator(),
|
||||
|
||||
Filter.Header("Period filter only applies at initial page"),
|
||||
PopularityPeriodFilter(),
|
||||
Filter.Separator(),
|
||||
|
||||
Filter.Header("Sort only works with text search and series filter"),
|
||||
SortFilter()
|
||||
)
|
||||
|
||||
private class SeriesFilter : UriPartFilter(
|
||||
"Series",
|
||||
arrayOf(
|
||||
Pair("None", ""),
|
||||
Pair("Doujins - Original Series", "/doujins-original-series-19934"),
|
||||
Pair("Hentai Magazine Chapters", "/hentai-magazine-chapters-2766"),
|
||||
Pair("Hentai Manga", "/hentai-manga-19"),
|
||||
Pair("Fate Grand Order", "/fate-grand-order-doujins-28615"),
|
||||
Pair("CG Sets - Original Series", "/cg-sets-original-series-14865"),
|
||||
Pair("Touhou", "/touhou-doujins-7748"),
|
||||
Pair("Naruto", "/naruto-doujins-5761"),
|
||||
Pair("Kantai Collection", "/kantai-collection-doujins-22720"),
|
||||
Pair("Hentai Game CG-Sets", "/hentai-game-cg-sets-2422"),
|
||||
Pair("One Piece", "/one-piece-doujins-6080"),
|
||||
Pair("Granblue Fantasy", "/granblue-fantasy-doujins-28177"),
|
||||
Pair("Azur Lane", "/azur-lane-doujins-34298"),
|
||||
Pair("Sword Art Online", "/sword-art-online-doujins-7246"),
|
||||
Pair("Idolmaster", "/idolmaster-4281"),
|
||||
Pair("My Hero Academia", "/my-hero-academia-doujins-28744"),
|
||||
Pair("Love Live", "/love-live-doujins-21865"),
|
||||
Pair("Pokemon", "/pokemon-doujins-6393"),
|
||||
Pair("Dragon Ball", "/dragon-ball-doujins-1238"),
|
||||
Pair("CGs - Mixed Series", "/cgs-mixed-series-35311"),
|
||||
Pair("Doujins - Mixed Series", "/doujins-mixed-series-20091"),
|
||||
Pair("Hentai Magazine Chapters", "/hentai-magazine-chapters-2766"),
|
||||
Pair("Hentai Magazine Chapters - Super-Shorts", "/hentai-magazine-chapters-super-shorts-19933"),
|
||||
Pair("Hentai Manga", "/hentai-manga-19")
|
||||
)
|
||||
)
|
||||
|
||||
private class SortFilter : UriPartFilter(
|
||||
"Sort",
|
||||
arrayOf(
|
||||
Pair("Newest First", ""),
|
||||
Pair("Oldest First", "created_at"),
|
||||
Pair("Alphabetical", "name"),
|
||||
Pair("Rating", "-cached_score"),
|
||||
Pair("Popularity", "-cached_views")
|
||||
)
|
||||
)
|
||||
|
||||
private class PopularityPeriodFilter : UriPartFilter(
|
||||
"Period",
|
||||
arrayOf(
|
||||
Pair("This Month", "/top"),
|
||||
Pair("This Year", "/top/year"),
|
||||
Pair("All Time", "/top/all"),
|
||||
)
|
||||
)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private fun SimpleDateFormat.parseOrNull(string: String): Date? {
|
||||
return try {
|
||||
parse(string)
|
||||
} catch (e: ParseException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
||||
|
||||
companion object {
|
||||
private const val PAGE_DAYS = 3
|
||||
private val ORDINAL_SUFFIXES = listOf("th", "st", "nd", "rd")
|
||||
private val MANGA_DETAILS_DATE_FORMAT = ORDINAL_SUFFIXES.map {
|
||||
SimpleDateFormat("MMMM dd'$it', yyyy", Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue