Implement Perv Eden source.
This commit is contained in:
parent
957c50088d
commit
c42f011a05
@ -13,16 +13,14 @@ import eu.kanade.tachiyomi.source.online.all.EHentai
|
|||||||
import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
|
import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.YamlHttpSource
|
import eu.kanade.tachiyomi.source.online.YamlHttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||||
import eu.kanade.tachiyomi.source.online.english.*
|
import eu.kanade.tachiyomi.source.online.english.*
|
||||||
import eu.kanade.tachiyomi.source.online.german.WieManga
|
import eu.kanade.tachiyomi.source.online.german.WieManga
|
||||||
import eu.kanade.tachiyomi.source.online.russian.Mangachan
|
import eu.kanade.tachiyomi.source.online.russian.Mangachan
|
||||||
import eu.kanade.tachiyomi.source.online.russian.Mintmanga
|
import eu.kanade.tachiyomi.source.online.russian.Mintmanga
|
||||||
import eu.kanade.tachiyomi.source.online.russian.Readmanga
|
import eu.kanade.tachiyomi.source.online.russian.Readmanga
|
||||||
import eu.kanade.tachiyomi.util.hasPermission
|
import eu.kanade.tachiyomi.util.hasPermission
|
||||||
import exh.EH_METADATA_SOURCE_ID
|
import exh.*
|
||||||
import exh.EH_SOURCE_ID
|
|
||||||
import exh.EXH_METADATA_SOURCE_ID
|
|
||||||
import exh.EXH_SOURCE_ID
|
|
||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
import rx.functions.Action1
|
import rx.functions.Action1
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -99,6 +97,8 @@ open class SourceManager(private val context: Context) {
|
|||||||
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
||||||
exSrcs += EHentaiMetadata(EXH_METADATA_SOURCE_ID, true, context)
|
exSrcs += EHentaiMetadata(EXH_METADATA_SOURCE_ID, true, context)
|
||||||
}
|
}
|
||||||
|
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, "en")
|
||||||
|
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, "it")
|
||||||
return exSrcs
|
return exSrcs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import exh.ui.login.LoginActivity
|
import exh.ui.login.LoginActivity
|
||||||
|
import exh.util.UriFilter
|
||||||
|
import exh.util.UriGroup
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
|
||||||
class EHentai(override val id: Long,
|
class EHentai(override val id: Long,
|
||||||
@ -158,11 +160,14 @@ class EHentai(override val id: Long,
|
|||||||
override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
|
override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
|
||||||
val metdata = ExGalleryMetadata()
|
val metdata = ExGalleryMetadata()
|
||||||
with(metdata) {
|
with(metdata) {
|
||||||
val manga = SManga.create()
|
|
||||||
url = response.request().url().toString()
|
url = response.request().url().toString()
|
||||||
exh = this@EHentai.exh
|
exh = this@EHentai.exh
|
||||||
title = select("#gn").text().nullIfBlank()?.trim()
|
title = select("#gn").text().nullIfBlank()?.trim()
|
||||||
altTitle = select("#gj").text().nullIfBlank()?.trim()
|
|
||||||
|
altTitles.clear()
|
||||||
|
select("#gj").text().nullIfBlank()?.trim()?.let { newAltTitle ->
|
||||||
|
altTitles.add(newAltTitle)
|
||||||
|
}
|
||||||
|
|
||||||
thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim()
|
thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim()
|
||||||
|
|
||||||
@ -227,12 +232,13 @@ class EHentai(override val id: Long,
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Save metadata
|
//Save metadata
|
||||||
metadataHelper.writeGallery(this)
|
metadataHelper.writeGallery(this, id)
|
||||||
|
|
||||||
//Copy metadata to manga
|
//Copy metadata to manga
|
||||||
copyTo(manga)
|
SManga.create().let {
|
||||||
|
copyTo(it)
|
||||||
manga
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,9 +339,6 @@ class EHentai(override val id: Long,
|
|||||||
GenreGroup(),
|
GenreGroup(),
|
||||||
AdvancedGroup()
|
AdvancedGroup()
|
||||||
)
|
)
|
||||||
private interface UriFilter {
|
|
||||||
fun addToUri(builder: Uri.Builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter {
|
class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
@ -386,14 +389,6 @@ class EHentai(override val id: Long,
|
|||||||
RatingOption()
|
RatingOption()
|
||||||
))
|
))
|
||||||
|
|
||||||
open class UriGroup<V>(name: String, state: List<V>) : Filter.Group<V>(name, state), UriFilter {
|
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
|
||||||
state.forEach {
|
|
||||||
if(it is UriFilter) it.addToUri(builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val name = if(exh)
|
override val name = if(exh)
|
||||||
"ExHentai"
|
"ExHentai"
|
||||||
else
|
else
|
||||||
|
@ -111,7 +111,7 @@ class EHentaiMetadata(override val id: Long,
|
|||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable {
|
override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable {
|
||||||
//Hack to convert the gallery into an online gallery when favoriting it or reading it
|
//Hack to convert the gallery into an online gallery when favoriting it or reading it
|
||||||
metadataHelper.fetchMetadata(manga.url, exh)?.copyTo(manga)
|
metadataHelper.fetchEhMetadata(manga.url, exh)?.copyTo(manga)
|
||||||
manga
|
manga
|
||||||
}!!
|
}!!
|
||||||
|
|
||||||
|
@ -0,0 +1,282 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.ChapterRecognition
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import exh.metadata.MetadataHelper
|
||||||
|
import exh.metadata.copyTo
|
||||||
|
import exh.metadata.models.PervEdenGalleryMetadata
|
||||||
|
import exh.metadata.models.Tag
|
||||||
|
import exh.util.UriFilter
|
||||||
|
import exh.util.UriGroup
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import org.jsoup.nodes.TextNode
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PervEden(override val id: Long, override val lang: String) : ParsedHttpSource() {
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
override val name = "Perv Eden"
|
||||||
|
override val baseUrl = "http://www.perveden.com"
|
||||||
|
|
||||||
|
val metadataHelper by lazy { MetadataHelper() }
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = "#topManga > ul > li"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
manga.thumbnail_url = "http:" + element.select(".hottestImage > img").attr("data-src")
|
||||||
|
|
||||||
|
val titleElement = element.getElementsByClass("hottestInfo").first().child(0)
|
||||||
|
manga.url = titleElement.attr("href")
|
||||||
|
manga.title = titleElement.text()
|
||||||
|
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector(): String? = null
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = "#mangaList > tbody > tr"
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
val titleElement = element.child(0).child(0)
|
||||||
|
manga.url = titleElement.attr("href")
|
||||||
|
manga.title = titleElement.text().trim()
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = ".next"
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val urlLang = if(lang == "en")
|
||||||
|
"eng"
|
||||||
|
else "it"
|
||||||
|
return GET("$baseUrl/$urlLang/")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = ".newsManga"
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
val header = element.getElementsByClass("manga_tooltop_header").first()
|
||||||
|
val titleElement = header.child(0)
|
||||||
|
manga.url = titleElement.attr("href")
|
||||||
|
manga.title = titleElement.text().trim()
|
||||||
|
manga.thumbnail_url = "http:" + titleElement.getElementsByClass("mangaImage").first().attr("tmpsrc")
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val mangas = document.select(latestUpdatesSelector()).map { element ->
|
||||||
|
latestUpdatesFromElement(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(mangas, mangas.isNotEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon()
|
||||||
|
uri.appendQueryParameter("page", page.toString())
|
||||||
|
filters.forEach {
|
||||||
|
if(it is UriFilter) it.addToUri(uri)
|
||||||
|
}
|
||||||
|
return GET(uri.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector(): String? {
|
||||||
|
throw NotImplementedError("Unused method called!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
val metadata = PervEdenGalleryMetadata()
|
||||||
|
with(metadata) {
|
||||||
|
url = document.location()
|
||||||
|
|
||||||
|
lang = this@PervEden.lang
|
||||||
|
|
||||||
|
title = document.getElementsByClass("manga-title").first()?.text()
|
||||||
|
|
||||||
|
thumbnailUrl = "http:" + document.getElementsByClass("mangaImage2").first()?.child(0)?.attr("src")
|
||||||
|
|
||||||
|
val rightBoxElement = document.select(".rightBox:not(.info)").first()
|
||||||
|
|
||||||
|
tags.clear()
|
||||||
|
var inStatus: String? = null
|
||||||
|
rightBoxElement.childNodes().forEach {
|
||||||
|
if(it is Element && it.tagName().toLowerCase() == "h4") {
|
||||||
|
inStatus = it.text().trim()
|
||||||
|
} else {
|
||||||
|
when(inStatus) {
|
||||||
|
"Alternative name(s)" -> {
|
||||||
|
if(it is TextNode) {
|
||||||
|
val text = it.text().trim()
|
||||||
|
if(!text.isBlank())
|
||||||
|
altTitles.add(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Artist" -> {
|
||||||
|
if(it is Element && it.tagName() == "a") {
|
||||||
|
artist = it.text()
|
||||||
|
tags.getOrPut("artist", {
|
||||||
|
ArrayList()
|
||||||
|
}).add(Tag(it.text().toLowerCase(), false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Genres" -> {
|
||||||
|
if(it is Element && it.tagName() == "a")
|
||||||
|
tags.getOrPut("genre", {
|
||||||
|
ArrayList()
|
||||||
|
}).add(Tag(it.text().toLowerCase(), false))
|
||||||
|
}
|
||||||
|
"Type" -> {
|
||||||
|
if(it is TextNode) {
|
||||||
|
val text = it.text().trim()
|
||||||
|
if(!text.isBlank())
|
||||||
|
type = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Status" -> {
|
||||||
|
if(it is TextNode) {
|
||||||
|
val text = it.text().trim()
|
||||||
|
if(!text.isBlank())
|
||||||
|
status = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rating = document.getElementById("rating-score")?.attr("value")?.toFloat()
|
||||||
|
|
||||||
|
//Save metadata
|
||||||
|
Timber.d("LNG: " + metadata.lang)
|
||||||
|
metadataHelper.writeGallery(this, id)
|
||||||
|
|
||||||
|
return SManga.create().apply {
|
||||||
|
copyTo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val num = if(lang == "en") "0"
|
||||||
|
else if(lang == "it") "1"
|
||||||
|
else throw NotImplementedError("Unimplemented language!")
|
||||||
|
|
||||||
|
return GET("$baseUrl/ajax/news/$page/$num/0/")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListSelector() = "#leftContent > table > tbody > tr"
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
|
val linkElement = element.getElementsByClass("chapterLink").first()
|
||||||
|
|
||||||
|
setUrlWithoutDomain(linkElement.attr("href"))
|
||||||
|
name = "Chapter " + linkElement.getElementsByTag("b").text()
|
||||||
|
|
||||||
|
ChapterRecognition.parseChapterNumber(
|
||||||
|
this,
|
||||||
|
SManga.create().apply {
|
||||||
|
title = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim()).time
|
||||||
|
} catch(ignored: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(document: Document)
|
||||||
|
= document.getElementById("pageSelect").getElementsByTag("option").map {
|
||||||
|
Page(it.attr("data-page").toInt() - 1, baseUrl + it.attr("value"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document)
|
||||||
|
= "http:" + document.getElementById("mainImg").attr("src")!!
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList (
|
||||||
|
AuthorFilter(),
|
||||||
|
ArtistFilter(),
|
||||||
|
TypeFilterGroup(),
|
||||||
|
ReleaseYearGroup(),
|
||||||
|
StatusFilterGroup()
|
||||||
|
)
|
||||||
|
|
||||||
|
class StatusFilterGroup : UriGroup<StatusFilter>("Status", listOf(
|
||||||
|
StatusFilter("Ongoing", 1),
|
||||||
|
StatusFilter("Completed", 2),
|
||||||
|
StatusFilter("Suspended", 0)
|
||||||
|
))
|
||||||
|
|
||||||
|
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
if(state)
|
||||||
|
builder.appendQueryParameter("status", id.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Explicit type arg for listOf() to workaround this: KT-16570
|
||||||
|
class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf<Filter<*>>(
|
||||||
|
ReleaseYearRangeFilter(),
|
||||||
|
ReleaseYearYearFilter()
|
||||||
|
))
|
||||||
|
|
||||||
|
class ReleaseYearRangeFilter : Filter.Select<String>("Range", arrayOf(
|
||||||
|
"on",
|
||||||
|
"after",
|
||||||
|
"before"
|
||||||
|
)), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
builder.appendQueryParameter("releasedType", state.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReleaseYearYearFilter : Filter.Text("Year"), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
builder.appendQueryParameter("released", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthorFilter : Filter.Text("Author"), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
builder.appendQueryParameter("author", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArtistFilter : Filter.Text("Artist"), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
builder.appendQueryParameter("artist", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeFilterGroup : UriGroup<TypeFilter>("Type", listOf(
|
||||||
|
TypeFilter("Japanese Manga", 0),
|
||||||
|
TypeFilter("Korean Manhwa", 1),
|
||||||
|
TypeFilter("Chinese Manhua", 2),
|
||||||
|
TypeFilter("Comic", 3),
|
||||||
|
TypeFilter("Doujinshi", 4)
|
||||||
|
))
|
||||||
|
|
||||||
|
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
if(state)
|
||||||
|
builder.appendQueryParameter("type", id.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("GMT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -101,21 +101,10 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
|
|||||||
author != null && author!!.toLowerCase().contains(query)
|
author != null && author!!.toLowerCase().contains(query)
|
||||||
} else {
|
} else {
|
||||||
//Use gallery search engine for EH manga
|
//Use gallery search engine for EH manga
|
||||||
val source = sourceManager.get(manga.source)
|
val metadata = metadataHelper.fetchMetadata(manga.url, manga.source)
|
||||||
source?.let {
|
metadata?.let {
|
||||||
val exh: Boolean
|
searchEngine.matches(it, searchEngine.parseQuery(query))
|
||||||
if(source is EHentai)
|
} ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
|
||||||
exh = source.exh
|
|
||||||
else if(source is EHentaiMetadata)
|
|
||||||
exh = source.exh
|
|
||||||
else
|
|
||||||
return@with false
|
|
||||||
|
|
||||||
val metadata = metadataHelper.fetchMetadata(manga.url, exh)
|
|
||||||
metadata?.let {
|
|
||||||
searchEngine.matches(metadata, searchEngine.parseQuery(query))
|
|
||||||
} ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
|
|
||||||
} ?: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,16 @@ val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2
|
|||||||
val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3
|
val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3
|
||||||
val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4
|
val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4
|
||||||
|
|
||||||
fun isLewdSource(source: Long) = source >= 6900
|
val PERV_EDEN_EN_SOURCE_ID = LEWD_SOURCE_SERIES + 5
|
||||||
&& source <= 6999
|
val PERV_EDEN_IT_SOURCE_ID = LEWD_SOURCE_SERIES + 6
|
||||||
|
|
||||||
|
fun isLewdSource(source: Long) = source in 6900..6999
|
||||||
|
|
||||||
|
fun isEhSource(source: Long) = source == EH_SOURCE_ID
|
||||||
|
|| source == EH_METADATA_SOURCE_ID
|
||||||
|
|
||||||
fun isExSource(source: Long) = source == EXH_SOURCE_ID
|
fun isExSource(source: Long) = source == EXH_SOURCE_ID
|
||||||
|| source == EXH_METADATA_SOURCE_ID
|
|| source == EXH_METADATA_SOURCE_ID
|
||||||
|
|
||||||
|
fun isPervEdenSource(source: Long) = source == PERV_EDEN_IT_SOURCE_ID
|
||||||
|
|| source == PERV_EDEN_EN_SOURCE_ID
|
||||||
|
@ -44,7 +44,7 @@ class GalleryAdder {
|
|||||||
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
|
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
|
||||||
|
|
||||||
//Apply metadata
|
//Apply metadata
|
||||||
metadataHelper.fetchMetadata(url, isExSource(source))?.copyTo(manga)
|
metadataHelper.fetchEhMetadata(url, isExSource(source))?.copyTo(manga)
|
||||||
|
|
||||||
if(fav) manga.favorite = true
|
if(fav) manga.favorite = true
|
||||||
|
|
||||||
|
@ -1,20 +1,46 @@
|
|||||||
package exh.metadata
|
package exh.metadata
|
||||||
|
|
||||||
|
import exh.*
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import exh.metadata.models.PervEdenGalleryMetadata
|
||||||
|
import exh.metadata.models.SearchableGalleryMetadata
|
||||||
import io.paperdb.Paper
|
import io.paperdb.Paper
|
||||||
|
|
||||||
class MetadataHelper {
|
class MetadataHelper {
|
||||||
|
|
||||||
fun writeGallery(galleryMetadata: ExGalleryMetadata)
|
fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
|
||||||
= exGalleryBook().write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
|
= (if(isExSource(source) || isEhSource(source)) exGalleryBook()
|
||||||
|
else if(isPervEdenSource(source)) pervEdenGalleryBook()
|
||||||
|
else null)?.write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
|
||||||
|
|
||||||
fun fetchMetadata(url: String, exh: Boolean): ExGalleryMetadata?
|
fun fetchEhMetadata(url: String, exh: Boolean): ExGalleryMetadata?
|
||||||
= ExGalleryMetadata().let {
|
= ExGalleryMetadata().let {
|
||||||
it.url = url
|
it.url = url
|
||||||
it.exh = exh
|
it.exh = exh
|
||||||
return exGalleryBook().read<ExGalleryMetadata>(it.galleryUniqueIdentifier())
|
return exGalleryBook().read<ExGalleryMetadata>(it.galleryUniqueIdentifier())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fetchPervEdenMetadata(url: String, source: Long): PervEdenGalleryMetadata?
|
||||||
|
= PervEdenGalleryMetadata().let {
|
||||||
|
it.url = url
|
||||||
|
if(source == PERV_EDEN_EN_SOURCE_ID)
|
||||||
|
it.lang = "en"
|
||||||
|
else if(source == PERV_EDEN_IT_SOURCE_ID)
|
||||||
|
it.lang = "it"
|
||||||
|
else throw IllegalArgumentException("Invalid source id!")
|
||||||
|
return pervEdenGalleryBook().read<PervEdenGalleryMetadata>(it.galleryUniqueIdentifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchMetadata(url: String, source: Long): SearchableGalleryMetadata? {
|
||||||
|
if(isExSource(source) || isEhSource(source)) {
|
||||||
|
return fetchEhMetadata(url, isExSource(source))
|
||||||
|
} else if(isPervEdenSource(source)) {
|
||||||
|
return fetchPervEdenMetadata(url, source)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllGalleries() = exGalleryBook().allKeys.map {
|
fun getAllGalleries() = exGalleryBook().allKeys.map {
|
||||||
exGalleryBook().read<ExGalleryMetadata>(it)
|
exGalleryBook().read<ExGalleryMetadata>(it)
|
||||||
}
|
}
|
||||||
@ -26,5 +52,9 @@ class MetadataHelper {
|
|||||||
return exGalleryBook().exist(it.galleryUniqueIdentifier())
|
return exGalleryBook().exist(it.galleryUniqueIdentifier())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Problem, our new metadata structures are incompatible.
|
||||||
|
//TODO We will probably just delete the old metadata structures
|
||||||
fun exGalleryBook() = Paper.book("gallery-ex")!!
|
fun exGalleryBook() = Paper.book("gallery-ex")!!
|
||||||
|
|
||||||
|
fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!!
|
||||||
}
|
}
|
@ -3,7 +3,10 @@ package exh.metadata
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import exh.metadata.models.PervEdenGalleryMetadata
|
||||||
|
import exh.metadata.models.SearchableGalleryMetadata
|
||||||
import exh.metadata.models.Tag
|
import exh.metadata.models.Tag
|
||||||
import exh.plusAssign
|
import exh.plusAssign
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -40,7 +43,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
|||||||
|
|
||||||
//No title bug?
|
//No title bug?
|
||||||
val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
|
val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
|
||||||
altTitle ?: title
|
altTitles.firstOrNull() ?: title
|
||||||
else
|
else
|
||||||
title
|
title
|
||||||
titleObj?.let { manga.title = it }
|
titleObj?.let { manga.title = it }
|
||||||
@ -70,7 +73,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
|||||||
//Build a nice looking description out of what we know
|
//Build a nice looking description out of what we know
|
||||||
val titleDesc = StringBuilder()
|
val titleDesc = StringBuilder()
|
||||||
title?.let { titleDesc += "Title: $it\n" }
|
title?.let { titleDesc += "Title: $it\n" }
|
||||||
altTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
altTitles.firstOrNull()?.let { titleDesc += "Alternate Title: $it\n" }
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
val detailsDesc = StringBuilder()
|
||||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||||
@ -90,16 +93,66 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
|||||||
detailsDesc += "\n"
|
detailsDesc += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
val tagsDesc = StringBuilder("Tags:\n")
|
|
||||||
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
|
||||||
tags.entries.forEach { namespace, tags ->
|
|
||||||
if(tags.isNotEmpty()) {
|
|
||||||
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
|
||||||
tagsDesc += "▪ $namespace: $joinedTags\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manga.description = listOf(titleDesc, detailsDesc, tagsDesc)
|
val tagsDesc = buildTagsDescription(this)
|
||||||
.filter { it.isNotBlank() }
|
|
||||||
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PervEdenGalleryMetadata.copyTo(manga: SManga) {
|
||||||
|
url?.let { manga.url = it }
|
||||||
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
|
val titleDesc = StringBuilder()
|
||||||
|
title?.let {
|
||||||
|
manga.title = it
|
||||||
|
titleDesc += "Title: $it\n"
|
||||||
|
}
|
||||||
|
if(altTitles.isNotEmpty())
|
||||||
|
titleDesc += "Alternate Titles: \n" + altTitles.map {
|
||||||
|
"▪ $it"
|
||||||
|
}.joinToString(separator = "\n", postfix = "\n")
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
artist?.let {
|
||||||
|
manga.artist = it
|
||||||
|
detailsDesc += "Artist: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
type?.let {
|
||||||
|
manga.genre = it
|
||||||
|
detailsDesc += "Type: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
status?.let {
|
||||||
|
manga.status = when(it) {
|
||||||
|
"Ongoing" -> SManga.ONGOING
|
||||||
|
"Completed", "Suspended" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
detailsDesc += "Status: $it\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
rating?.let {
|
||||||
|
detailsDesc += "Rating: %.2\n".format(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildTagsDescription(metadata: SearchableGalleryMetadata)
|
||||||
|
= StringBuilder("Tags:\n").apply {
|
||||||
|
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
||||||
|
metadata.tags.entries.forEach { namespace, tags ->
|
||||||
|
if (tags.isNotEmpty()) {
|
||||||
|
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
||||||
|
this += "▪ $namespace: $joinedTags\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,19 +7,15 @@ import java.util.*
|
|||||||
* Gallery metadata storage model
|
* Gallery metadata storage model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ExGalleryMetadata {
|
class ExGalleryMetadata : SearchableGalleryMetadata() {
|
||||||
var url: String? = null
|
var url: String? = null
|
||||||
|
|
||||||
var exh: Boolean? = null
|
var exh: Boolean? = null
|
||||||
|
|
||||||
var title: String? = null
|
|
||||||
var altTitle: String? = null
|
|
||||||
|
|
||||||
var thumbnailUrl: String? = null
|
var thumbnailUrl: String? = null
|
||||||
|
|
||||||
var genre: String? = null
|
var genre: String? = null
|
||||||
|
|
||||||
var uploader: String? = null
|
|
||||||
var datePosted: Long? = null
|
var datePosted: Long? = null
|
||||||
var parent: String? = null
|
var parent: String? = null
|
||||||
var visible: String? = null //Not a boolean
|
var visible: String? = null //Not a boolean
|
||||||
@ -31,8 +27,6 @@ class ExGalleryMetadata {
|
|||||||
var ratingCount: Int? = null
|
var ratingCount: Int? = null
|
||||||
var averageRating: Double? = null
|
var averageRating: Double? = null
|
||||||
|
|
||||||
//Being specific about which classes are used in generics to make deserialization easier
|
|
||||||
var tags: HashMap<String, ArrayList<Tag>> = HashMap()
|
|
||||||
|
|
||||||
private fun splitGalleryUrl()
|
private fun splitGalleryUrl()
|
||||||
= url?.let {
|
= url?.let {
|
||||||
@ -44,8 +38,10 @@ class ExGalleryMetadata {
|
|||||||
fun galleryToken() =
|
fun galleryToken() =
|
||||||
splitGalleryUrl()?.last()
|
splitGalleryUrl()?.last()
|
||||||
|
|
||||||
fun galleryUniqueIdentifier() = exh?.let { exh ->
|
override fun galleryUniqueIdentifier() = exh?.let { exh ->
|
||||||
url?.let {
|
url?.let {
|
||||||
|
//Fuck, this should be EXH and EH but it's too late to change it now...
|
||||||
|
//TODO Change this during migration
|
||||||
"${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
|
"${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
//TODO Add artificial artist tag
|
||||||
|
class PervEdenGalleryMetadata : SearchableGalleryMetadata() {
|
||||||
|
var url: String? = null
|
||||||
|
var thumbnailUrl: String? = null
|
||||||
|
|
||||||
|
var artist: String? = null
|
||||||
|
|
||||||
|
var type: String? = null
|
||||||
|
|
||||||
|
var rating: Float? = null
|
||||||
|
|
||||||
|
var status: String? = null
|
||||||
|
|
||||||
|
var lang: String? = null
|
||||||
|
|
||||||
|
private fun splitGalleryUrl()
|
||||||
|
= url?.let {
|
||||||
|
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun galleryUniqueIdentifier() = splitGalleryUrl()?.let {
|
||||||
|
Timber.d(
|
||||||
|
"PERVEDEN-${lang?.toUpperCase()}-${it.last()}"
|
||||||
|
)
|
||||||
|
"PERVEDEN-${lang?.toUpperCase()}-${it.last()}"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A gallery that can be searched using the EH search engine
|
||||||
|
*/
|
||||||
|
abstract class SearchableGalleryMetadata {
|
||||||
|
var uploader: String? = null
|
||||||
|
|
||||||
|
var title: String? = null
|
||||||
|
val altTitles: MutableList<String> = mutableListOf()
|
||||||
|
|
||||||
|
//Being specific about which classes are used in generics to make deserialization easier
|
||||||
|
val tags: HashMap<String, ArrayList<Tag>> = HashMap()
|
||||||
|
|
||||||
|
abstract fun galleryUniqueIdentifier(): String?
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package exh.search
|
package exh.search
|
||||||
|
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.SearchableGalleryMetadata
|
||||||
import exh.metadata.models.Tag
|
import exh.metadata.models.Tag
|
||||||
|
|
||||||
class SearchEngine {
|
class SearchEngine {
|
||||||
|
|
||||||
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
|
private val queryCache = mutableMapOf<String, List<QueryComponent>>()
|
||||||
|
|
||||||
fun matches(metadata: ExGalleryMetadata, query: List<QueryComponent>): Boolean {
|
fun matches(metadata: SearchableGalleryMetadata, query: List<QueryComponent>): Boolean {
|
||||||
|
|
||||||
fun matchTagList(tags: Sequence<Tag>,
|
fun matchTagList(tags: Sequence<Tag>,
|
||||||
component: Text): Boolean {
|
component: Text): Boolean {
|
||||||
@ -29,13 +29,13 @@ class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val cachedLowercaseTitle = metadata.title?.toLowerCase()
|
val cachedLowercaseTitle = metadata.title?.toLowerCase()
|
||||||
val cachedLowercaseAltTitle = metadata.altTitle?.toLowerCase()
|
val cachedLowercaseAltTitles = metadata.altTitles.map(String::toLowerCase)
|
||||||
|
|
||||||
for(component in query) {
|
for(component in query) {
|
||||||
if(component is Text) {
|
if(component is Text) {
|
||||||
//Match title
|
//Match title
|
||||||
if (component.asRegex().test(cachedLowercaseTitle)
|
if (component.asRegex().test(cachedLowercaseTitle)
|
||||||
|| component.asRegex().test(cachedLowercaseAltTitle)) {
|
|| cachedLowercaseAltTitles.find { component.asRegex().test(it) } != null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//Match tags
|
//Match tags
|
||||||
|
@ -63,7 +63,7 @@ class MetadataFetchDialog {
|
|||||||
source?.let {
|
source?.let {
|
||||||
it as EHentai
|
it as EHentai
|
||||||
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
|
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first())
|
||||||
metadataHelper.fetchMetadata(manga.url, it.exh)?.copyTo(manga)
|
metadataHelper.fetchEhMetadata(manga.url, it.exh)?.copyTo(manga)
|
||||||
}
|
}
|
||||||
} catch(t: Throwable) {
|
} catch(t: Throwable) {
|
||||||
Timber.e(t, "Could not migrate manga!")
|
Timber.e(t, "Could not migrate manga!")
|
||||||
|
@ -39,33 +39,33 @@ class UrlMigrator {
|
|||||||
//Sort possible dups so we can use binary search on it
|
//Sort possible dups so we can use binary search on it
|
||||||
possibleDups.sortBy { it.url }
|
possibleDups.sortBy { it.url }
|
||||||
|
|
||||||
badMangas.forEach {
|
badMangas.forEach { manga ->
|
||||||
//Build fixed URL
|
//Build fixed URL
|
||||||
val urlWithSlash = "/" + it.url
|
val urlWithSlash = "/" + manga.url
|
||||||
//Fix metadata if required
|
//Fix metadata if required
|
||||||
val metadata = metadataHelper.fetchMetadata(it.url, isExSource(it.source))
|
val metadata = metadataHelper.fetchEhMetadata(manga.url, isExSource(manga.source))
|
||||||
metadata?.url?.let {
|
metadata?.url?.let {
|
||||||
if(it.startsWith("g/")) { //Check if metadata URL has no slash
|
if(it.startsWith("g/")) { //Check if metadata URL has no slash
|
||||||
metadata.url = urlWithSlash //Fix it
|
metadata.url = urlWithSlash //Fix it
|
||||||
metadataHelper.writeGallery(metadata) //Write new metadata to disk
|
metadataHelper.writeGallery(metadata, manga.source) //Write new metadata to disk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//If we have a dup (with the fixed url), use the dup instead
|
//If we have a dup (with the fixed url), use the dup instead
|
||||||
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
|
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
|
||||||
if(possibleDup >= 0) {
|
if(possibleDup >= 0) {
|
||||||
//Make sure it is favorited if we are
|
//Make sure it is favorited if we are
|
||||||
if(it.favorite) {
|
if(manga.favorite) {
|
||||||
val dup = possibleDups[possibleDup]
|
val dup = possibleDups[possibleDup]
|
||||||
dup.favorite = true
|
dup.favorite = true
|
||||||
db.insertManga(dup).executeAsBlocking() //Update DB with changes
|
db.insertManga(dup).executeAsBlocking() //Update DB with changes
|
||||||
}
|
}
|
||||||
//Delete ourself (but the dup is still there)
|
//Delete ourself (but the dup is still there)
|
||||||
db.deleteManga(it).executeAsBlocking()
|
db.deleteManga(manga).executeAsBlocking()
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
//No dup, correct URL and reinsert ourselves
|
//No dup, correct URL and reinsert ourselves
|
||||||
it.url = urlWithSlash
|
manga.url = urlWithSlash
|
||||||
db.insertManga(it).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
app/src/main/java/exh/util/UriFilter.kt
Normal file
10
app/src/main/java/exh/util/UriFilter.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package exh.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uri filter
|
||||||
|
*/
|
||||||
|
interface UriFilter {
|
||||||
|
fun addToUri(builder: Uri.Builder)
|
||||||
|
}
|
15
app/src/main/java/exh/util/UriGroup.kt
Normal file
15
app/src/main/java/exh/util/UriGroup.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package exh.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UriGroup
|
||||||
|
*/
|
||||||
|
open class UriGroup<V>(name: String, state: List<V>) : Filter.Group<V>(name, state), UriFilter {
|
||||||
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
|
state.forEach {
|
||||||
|
if(it is UriFilter) it.addToUri(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user