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.HttpSource
|
||||
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.german.WieManga
|
||||
import eu.kanade.tachiyomi.source.online.russian.Mangachan
|
||||
import eu.kanade.tachiyomi.source.online.russian.Mintmanga
|
||||
import eu.kanade.tachiyomi.source.online.russian.Readmanga
|
||||
import eu.kanade.tachiyomi.util.hasPermission
|
||||
import exh.EH_METADATA_SOURCE_ID
|
||||
import exh.EH_SOURCE_ID
|
||||
import exh.EXH_METADATA_SOURCE_ID
|
||||
import exh.EXH_SOURCE_ID
|
||||
import exh.*
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
import rx.functions.Action1
|
||||
import timber.log.Timber
|
||||
@ -99,6 +97,8 @@ open class SourceManager(private val context: Context) {
|
||||
exSrcs += EHentai(EXH_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
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
import exh.ui.login.LoginActivity
|
||||
import exh.util.UriFilter
|
||||
import exh.util.UriGroup
|
||||
import okhttp3.Request
|
||||
|
||||
class EHentai(override val id: Long,
|
||||
@ -158,11 +160,14 @@ class EHentai(override val id: Long,
|
||||
override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
|
||||
val metdata = ExGalleryMetadata()
|
||||
with(metdata) {
|
||||
val manga = SManga.create()
|
||||
url = response.request().url().toString()
|
||||
exh = this@EHentai.exh
|
||||
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()
|
||||
|
||||
@ -227,12 +232,13 @@ class EHentai(override val id: Long,
|
||||
}
|
||||
|
||||
//Save metadata
|
||||
metadataHelper.writeGallery(this)
|
||||
metadataHelper.writeGallery(this, id)
|
||||
|
||||
//Copy metadata to manga
|
||||
copyTo(manga)
|
||||
|
||||
manga
|
||||
SManga.create().let {
|
||||
copyTo(it)
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,9 +339,6 @@ class EHentai(override val id: Long,
|
||||
GenreGroup(),
|
||||
AdvancedGroup()
|
||||
)
|
||||
private interface UriFilter {
|
||||
fun addToUri(builder: Uri.Builder)
|
||||
}
|
||||
|
||||
class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
@ -386,14 +389,6 @@ class EHentai(override val id: Long,
|
||||
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)
|
||||
"ExHentai"
|
||||
else
|
||||
|
@ -111,7 +111,7 @@ class EHentaiMetadata(override val id: Long,
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable {
|
||||
//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
|
||||
}!!
|
||||
|
||||
|
@ -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)
|
||||
} else {
|
||||
//Use gallery search engine for EH manga
|
||||
val source = sourceManager.get(manga.source)
|
||||
source?.let {
|
||||
val exh: Boolean
|
||||
if(source is EHentai)
|
||||
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
|
||||
val metadata = metadataHelper.fetchMetadata(manga.url, manga.source)
|
||||
metadata?.let {
|
||||
searchEngine.matches(it, searchEngine.parseQuery(query))
|
||||
} ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,16 @@ val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2
|
||||
val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3
|
||||
val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4
|
||||
|
||||
fun isLewdSource(source: Long) = source >= 6900
|
||||
&& source <= 6999
|
||||
val PERV_EDEN_EN_SOURCE_ID = LEWD_SOURCE_SERIES + 5
|
||||
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
|
||||
|| 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())
|
||||
|
||||
//Apply metadata
|
||||
metadataHelper.fetchMetadata(url, isExSource(source))?.copyTo(manga)
|
||||
metadataHelper.fetchEhMetadata(url, isExSource(source))?.copyTo(manga)
|
||||
|
||||
if(fav) manga.favorite = true
|
||||
|
||||
|
@ -1,20 +1,46 @@
|
||||
package exh.metadata
|
||||
|
||||
import exh.*
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import io.paperdb.Paper
|
||||
|
||||
class MetadataHelper {
|
||||
|
||||
fun writeGallery(galleryMetadata: ExGalleryMetadata)
|
||||
= exGalleryBook().write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
|
||||
fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
|
||||
= (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 {
|
||||
it.url = url
|
||||
it.exh = exh
|
||||
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 {
|
||||
exGalleryBook().read<ExGalleryMetadata>(it)
|
||||
}
|
||||
@ -26,5 +52,9 @@ class MetadataHelper {
|
||||
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 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.getOrDefault
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import exh.metadata.models.Tag
|
||||
import exh.plusAssign
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
@ -40,7 +43,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
||||
|
||||
//No title bug?
|
||||
val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
|
||||
altTitle ?: title
|
||||
altTitles.firstOrNull() ?: title
|
||||
else
|
||||
title
|
||||
titleObj?.let { manga.title = it }
|
||||
@ -70,7 +73,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
||||
//Build a nice looking description out of what we know
|
||||
val titleDesc = StringBuilder()
|
||||
title?.let { titleDesc += "Title: $it\n" }
|
||||
altTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
||||
altTitles.firstOrNull()?.let { titleDesc += "Alternate Title: $it\n" }
|
||||
|
||||
val detailsDesc = StringBuilder()
|
||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||
@ -90,16 +93,66 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
|
||||
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)
|
||||
.filter { it.isNotBlank() }
|
||||
val tagsDesc = buildTagsDescription(this)
|
||||
|
||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||
.filter(String::isNotBlank)
|
||||
.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
|
||||
*/
|
||||
|
||||
class ExGalleryMetadata {
|
||||
class ExGalleryMetadata : SearchableGalleryMetadata() {
|
||||
var url: String? = null
|
||||
|
||||
var exh: Boolean? = null
|
||||
|
||||
var title: String? = null
|
||||
var altTitle: String? = null
|
||||
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var genre: String? = null
|
||||
|
||||
var uploader: String? = null
|
||||
var datePosted: Long? = null
|
||||
var parent: String? = null
|
||||
var visible: String? = null //Not a boolean
|
||||
@ -31,8 +27,6 @@ class ExGalleryMetadata {
|
||||
var ratingCount: Int? = 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()
|
||||
= url?.let {
|
||||
@ -44,8 +38,10 @@ class ExGalleryMetadata {
|
||||
fun galleryToken() =
|
||||
splitGalleryUrl()?.last()
|
||||
|
||||
fun galleryUniqueIdentifier() = exh?.let { exh ->
|
||||
override fun galleryUniqueIdentifier() = exh?.let { exh ->
|
||||
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()}"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import exh.metadata.models.Tag
|
||||
|
||||
class SearchEngine {
|
||||
|
||||
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>,
|
||||
component: Text): Boolean {
|
||||
@ -29,13 +29,13 @@ class SearchEngine {
|
||||
}
|
||||
|
||||
val cachedLowercaseTitle = metadata.title?.toLowerCase()
|
||||
val cachedLowercaseAltTitle = metadata.altTitle?.toLowerCase()
|
||||
val cachedLowercaseAltTitles = metadata.altTitles.map(String::toLowerCase)
|
||||
|
||||
for(component in query) {
|
||||
if(component is Text) {
|
||||
//Match title
|
||||
if (component.asRegex().test(cachedLowercaseTitle)
|
||||
|| component.asRegex().test(cachedLowercaseAltTitle)) {
|
||||
|| cachedLowercaseAltTitles.find { component.asRegex().test(it) } != null) {
|
||||
continue
|
||||
}
|
||||
//Match tags
|
||||
|
@ -63,7 +63,7 @@ class MetadataFetchDialog {
|
||||
source?.let {
|
||||
it as EHentai
|
||||
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) {
|
||||
Timber.e(t, "Could not migrate manga!")
|
||||
|
@ -39,33 +39,33 @@ class UrlMigrator {
|
||||
//Sort possible dups so we can use binary search on it
|
||||
possibleDups.sortBy { it.url }
|
||||
|
||||
badMangas.forEach {
|
||||
badMangas.forEach { manga ->
|
||||
//Build fixed URL
|
||||
val urlWithSlash = "/" + it.url
|
||||
val urlWithSlash = "/" + manga.url
|
||||
//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 {
|
||||
if(it.startsWith("g/")) { //Check if metadata URL has no slash
|
||||
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
|
||||
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
|
||||
if(possibleDup >= 0) {
|
||||
//Make sure it is favorited if we are
|
||||
if(it.favorite) {
|
||||
if(manga.favorite) {
|
||||
val dup = possibleDups[possibleDup]
|
||||
dup.favorite = true
|
||||
db.insertManga(dup).executeAsBlocking() //Update DB with changes
|
||||
}
|
||||
//Delete ourself (but the dup is still there)
|
||||
db.deleteManga(it).executeAsBlocking()
|
||||
db.deleteManga(manga).executeAsBlocking()
|
||||
return@forEach
|
||||
}
|
||||
//No dup, correct URL and reinsert ourselves
|
||||
it.url = urlWithSlash
|
||||
db.insertManga(it).executeAsBlocking()
|
||||
manga.url = urlWithSlash
|
||||
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