See CHANGELOG.md for this commit
This commit is contained in:
parent
dec4471871
commit
263cc1d97c
@ -3,6 +3,7 @@
|
|||||||
- Upstream merge
|
- Upstream merge
|
||||||
- Fix PervEden search
|
- Fix PervEden search
|
||||||
- Add ability to use high-quality thumbnails on nhentai
|
- Add ability to use high-quality thumbnails on nhentai
|
||||||
- Enable PervEden link importing
|
- Enable link importing for all NSFW sources
|
||||||
- Fix back button in library search
|
- Fix back button in library search
|
||||||
- Add HentaiCafe source
|
- Add HentaiCafe source
|
||||||
|
- Add Tsumino source
|
@ -144,9 +144,23 @@
|
|||||||
android:pathPrefix="/g/"
|
android:pathPrefix="/g/"
|
||||||
android:scheme="https"/>
|
android:scheme="https"/>
|
||||||
<data
|
<data
|
||||||
android:host="nhentai.net"
|
android:host="www.perveden.com"
|
||||||
android:pathPrefix="/g/"
|
|
||||||
android:scheme="http"/>
|
android:scheme="http"/>
|
||||||
|
<data
|
||||||
|
android:host="www.perveden.com"
|
||||||
|
android:scheme="https"/>
|
||||||
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:scheme="http"/>
|
||||||
|
<data
|
||||||
|
android:host="hentai.cafe"
|
||||||
|
android:scheme="https"/>
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:scheme="http"/>
|
||||||
|
<data
|
||||||
|
android:host="www.tsumino.com"
|
||||||
|
android:scheme="https"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ import dalvik.system.PathClassLoader
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
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.online.all.EHentai
|
|
||||||
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.EHentai
|
||||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
import eu.kanade.tachiyomi.source.online.all.NHentai
|
||||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||||
import eu.kanade.tachiyomi.source.online.english.*
|
import eu.kanade.tachiyomi.source.online.english.*
|
||||||
@ -20,7 +20,10 @@ 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.*
|
import exh.EH_SOURCE_ID
|
||||||
|
import exh.EXH_SOURCE_ID
|
||||||
|
import exh.PERV_EDEN_EN_SOURCE_ID
|
||||||
|
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||||
import exh.metadata.models.PervEdenLang
|
import exh.metadata.models.PervEdenLang
|
||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -98,6 +101,7 @@ open class SourceManager(private val context: Context) {
|
|||||||
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it)
|
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it)
|
||||||
exSrcs += NHentai(context)
|
exSrcs += NHentai(context)
|
||||||
exSrcs += HentaiCafe()
|
exSrcs += HentaiCafe()
|
||||||
|
exSrcs += Tsumino()
|
||||||
return exSrcs
|
return exSrcs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import exh.HENTAI_CAFE_SOURCE_ID
|
|||||||
import exh.metadata.models.HentaiCafeMetadata
|
import exh.metadata.models.HentaiCafeMetadata
|
||||||
import exh.metadata.models.HentaiCafeMetadata.Companion.BASE_URL
|
import exh.metadata.models.HentaiCafeMetadata.Companion.BASE_URL
|
||||||
import exh.metadata.models.Tag
|
import exh.metadata.models.Tag
|
||||||
|
import exh.util.urlImportFetchSearchManga
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
@ -35,7 +36,12 @@ class HentaiCafe : ParsedHttpSource(), LewdSource<HentaiCafeMetadata, Document>
|
|||||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Unused method called!")
|
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Unused method called!")
|
||||||
override fun fetchPopularManga(page: Int) = fetchLatestUpdates(page)
|
override fun fetchPopularManga(page: Int) = fetchLatestUpdates(page)
|
||||||
|
|
||||||
override fun searchMangaSelector() = "article.post"
|
//Support direct URL importing
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
|
urlImportFetchSearchManga(query, {
|
||||||
|
super.fetchSearchManga(page, query, filters)
|
||||||
|
})
|
||||||
|
override fun searchMangaSelector() = "article.post:not(#post-0)"
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
override fun searchMangaFromElement(element: Element): SManga {
|
||||||
val thumb = element.select(".entry-thumb > img")
|
val thumb = element.select(".entry-thumb > img")
|
||||||
val title = element.select(".entry-title > a")
|
val title = element.select(".entry-title > a")
|
||||||
@ -112,6 +118,8 @@ class HentaiCafe : ParsedHttpSource(), LewdSource<HentaiCafeMetadata, Document>
|
|||||||
url = Uri.decode(it.location())
|
url = Uri.decode(it.location())
|
||||||
title = eTitle.text()
|
title = eTitle.text()
|
||||||
|
|
||||||
|
thumbnailUrl = content.select("img").attr("src")
|
||||||
|
|
||||||
tags.clear()
|
tags.clear()
|
||||||
val eDetails = content.select("p > a[rel=tag]")
|
val eDetails = content.select("p > a[rel=tag]")
|
||||||
eDetails.forEach {
|
eDetails.forEach {
|
||||||
|
@ -0,0 +1,304 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.english
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.salomonbrys.kotson.*
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
|
import eu.kanade.tachiyomi.source.online.LewdSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import exh.TSUMINO_SOURCE_ID
|
||||||
|
import exh.metadata.models.Tag
|
||||||
|
import exh.metadata.models.TsuminoMetadata
|
||||||
|
import exh.metadata.models.TsuminoMetadata.Companion.BASE_URL
|
||||||
|
import exh.util.urlImportFetchSearchManga
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Tsumino: ParsedHttpSource(), LewdSource<TsuminoMetadata, Document> {
|
||||||
|
override val id = TSUMINO_SOURCE_ID
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
override val supportsLatest = true
|
||||||
|
override val name = "Tsumino"
|
||||||
|
|
||||||
|
override fun queryAll() = TsuminoMetadata.EmptyQuery()
|
||||||
|
|
||||||
|
override fun queryFromUrl(url: String) = TsuminoMetadata.UrlQuery(url)
|
||||||
|
|
||||||
|
override val baseUrl = BASE_URL
|
||||||
|
|
||||||
|
override val metaParser: TsuminoMetadata.(Document) -> Unit = {
|
||||||
|
url = it.location()
|
||||||
|
tags.clear()
|
||||||
|
|
||||||
|
it.getElementById("Title")?.text()?.let {
|
||||||
|
title = it.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Artist")?.children()?.first()?.text()?.trim()?.let {
|
||||||
|
tags.add(Tag("artist", it, false))
|
||||||
|
artist = it
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Uploader")?.children()?.first()?.text()?.trim()?.let {
|
||||||
|
tags.add(Tag("uploader", it, false))
|
||||||
|
uploader = it
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Uploaded")?.text()?.let {
|
||||||
|
uploadDate = TM_DATE_FORMAT.parse(it.trim()).time
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Pages")?.text()?.let {
|
||||||
|
length = it.trim().toIntOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Rating")?.text()?.let {
|
||||||
|
ratingString = it.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Category")?.children()?.first()?.text()?.let {
|
||||||
|
category = it.trim()
|
||||||
|
tags.add(Tag("genre", it, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Collection")?.children()?.first()?.text()?.let {
|
||||||
|
collection = it.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Group")?.children()?.first()?.text()?.let {
|
||||||
|
group = it.trim()
|
||||||
|
tags.add(Tag("group", it, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
parody.clear()
|
||||||
|
it.getElementById("Parody")?.children()?.forEach {
|
||||||
|
val entry = it.text().trim()
|
||||||
|
parody.add(entry)
|
||||||
|
tags.add(Tag("parody", entry, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
character.clear()
|
||||||
|
it.getElementById("Character")?.children()?.forEach {
|
||||||
|
val entry = it.text().trim()
|
||||||
|
character.add(entry)
|
||||||
|
tags.add(Tag("character", entry, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
it.getElementById("Tag")?.children()?.let {
|
||||||
|
tags.addAll(it.map {
|
||||||
|
Tag("tag", it.text().trim(), false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun genericMangaParse(response: Response): MangasPage {
|
||||||
|
val json = jsonParser.parse(response.body()!!.string()!!).asJsonObject
|
||||||
|
val hasNextPage = json["PageNumber"].int < json["PageCount"].int
|
||||||
|
|
||||||
|
val manga = json["Data"].array.map {
|
||||||
|
val obj = it.obj["Entry"].obj
|
||||||
|
|
||||||
|
SManga.create().apply {
|
||||||
|
val id = obj["Id"].long
|
||||||
|
setUrlWithoutDomain(TsuminoMetadata.mangaUrlFromId(id.toString()))
|
||||||
|
thumbnail_url = TsuminoMetadata.thumbUrlFromId(id.toString())
|
||||||
|
|
||||||
|
title = obj["Title"].string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(manga, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun genericMangaRequest(page: Int,
|
||||||
|
query: String,
|
||||||
|
sort: SortType,
|
||||||
|
length: LengthType,
|
||||||
|
minRating: Int,
|
||||||
|
excludeParodies: Boolean = false,
|
||||||
|
advSearch: List<AdvSearchEntry> = emptyList())
|
||||||
|
= POST("$BASE_URL/Books/Operate", body = FormBody.Builder()
|
||||||
|
.add("PageNumber", (page + 1).toString())
|
||||||
|
.add("Text", query)
|
||||||
|
.add("Sort", sort.name)
|
||||||
|
.add("List", "0")
|
||||||
|
.add("Length", length.id.toString())
|
||||||
|
.add("MinimumRating", minRating.toString())
|
||||||
|
.apply {
|
||||||
|
advSearch.forEachIndexed { index, entry ->
|
||||||
|
add("Tags[$index][Type]", entry.type.toString())
|
||||||
|
add("Tags[$index][Text]", entry.text)
|
||||||
|
add("Tags[$index][Exclude]", entry.exclude.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if(excludeParodies)
|
||||||
|
add("Exclude[]", "6")
|
||||||
|
}
|
||||||
|
.build())
|
||||||
|
|
||||||
|
enum class SortType {
|
||||||
|
Newest,
|
||||||
|
Oldest,
|
||||||
|
Alphabetical,
|
||||||
|
Rating,
|
||||||
|
Pages,
|
||||||
|
Views,
|
||||||
|
Random,
|
||||||
|
Comments,
|
||||||
|
Popularity
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class LengthType(val id: Int) {
|
||||||
|
Any(0),
|
||||||
|
Short(1),
|
||||||
|
Medium(2),
|
||||||
|
Long(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun popularMangaRequest(page: Int) = genericMangaRequest(page,
|
||||||
|
"",
|
||||||
|
SortType.Random,
|
||||||
|
LengthType.Any,
|
||||||
|
0)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response) = genericMangaParse(response)
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun latestUpdatesRequest(page: Int) = genericMangaRequest(page,
|
||||||
|
"",
|
||||||
|
SortType.Newest,
|
||||||
|
LengthType.Any,
|
||||||
|
0)
|
||||||
|
override fun latestUpdatesParse(response: Response) = genericMangaParse(response)
|
||||||
|
|
||||||
|
//Support direct URL importing
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
|
urlImportFetchSearchManga(query, {
|
||||||
|
super.fetchSearchManga(page, query, filters)
|
||||||
|
})
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
// Append filters again, to provide fallback in case a filter is not provided
|
||||||
|
// Since we only work with the first filter when building the result, if the filter is provided,
|
||||||
|
// the original filter is ignored
|
||||||
|
val f = filters + getFilterList()
|
||||||
|
|
||||||
|
return genericMangaRequest(
|
||||||
|
page,
|
||||||
|
query,
|
||||||
|
SortType.values()[f.filterIsInstance<SortFilter>().first().state],
|
||||||
|
LengthType.values()[f.filterIsInstance<LengthFilter>().first().state],
|
||||||
|
f.filterIsInstance<MinimumRatingFilter>().first().state,
|
||||||
|
f.filterIsInstance<ExcludeParodiesFilter>().first().state,
|
||||||
|
f.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
|
||||||
|
val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
|
||||||
|
|
||||||
|
splitState.map {
|
||||||
|
AdvSearchEntry(filter.type, it.removePrefix("-"), it.startsWith("-"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document)
|
||||||
|
= parseToManga(queryFromUrl(document.location()), document)
|
||||||
|
|
||||||
|
override fun chapterListSelector() = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun fetchChapterList(manga: SManga) = lazyLoadMeta(queryFromUrl(manga.url),
|
||||||
|
client.newCall(mangaDetailsRequest(manga)).asObservableSuccess().map { it.asJsoup() }
|
||||||
|
).map {
|
||||||
|
listOf(
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = "/Read/View/${it.tmId}"
|
||||||
|
name = "Chapter"
|
||||||
|
|
||||||
|
it.uploadDate?.let { date_upload = it }
|
||||||
|
|
||||||
|
chapter_number = 1f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
|
val id = chapter.url.substringAfterLast('/')
|
||||||
|
val call = POST("$BASE_URL/Read/Load", body = FormBody.Builder().add("q", id).build())
|
||||||
|
return client.newCall(call).asObservableSuccess().map {
|
||||||
|
val parsed = jsonParser.parse(it.body()!!.string()).obj
|
||||||
|
val pageUrls = parsed["reader_page_urls"].array
|
||||||
|
|
||||||
|
val imageUrl = Uri.parse("$BASE_URL/Image/Object")
|
||||||
|
pageUrls.mapIndexed { index, obj ->
|
||||||
|
val newImageUrl = imageUrl.buildUpon().appendQueryParameter("name", obj.string)
|
||||||
|
Page(index, chapter.url + "#${index + 1}", newImageUrl.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(document: Document) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Unused method called!")
|
||||||
|
|
||||||
|
data class AdvSearchEntry(val type: Int, val text: String, val exclude: Boolean)
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
Filter.Header("Separate tags with commas"),
|
||||||
|
Filter.Header("Prepend with dash to exclude"),
|
||||||
|
TagFilter(),
|
||||||
|
CategoryFilter(),
|
||||||
|
CollectionFilter(),
|
||||||
|
GroupFilter(),
|
||||||
|
ArtistFilter(),
|
||||||
|
ParodyFilter(),
|
||||||
|
CharactersFilter(),
|
||||||
|
UploaderFilter(),
|
||||||
|
|
||||||
|
Filter.Separator(),
|
||||||
|
|
||||||
|
SortFilter(),
|
||||||
|
LengthFilter(),
|
||||||
|
MinimumRatingFilter(),
|
||||||
|
ExcludeParodiesFilter()
|
||||||
|
)
|
||||||
|
|
||||||
|
class TagFilter : AdvSearchEntryFilter("Tags", 1)
|
||||||
|
class CategoryFilter : AdvSearchEntryFilter("Categories", 2)
|
||||||
|
class CollectionFilter : AdvSearchEntryFilter("Collections", 3)
|
||||||
|
class GroupFilter : AdvSearchEntryFilter("Groups", 4)
|
||||||
|
class ArtistFilter : AdvSearchEntryFilter("Artists", 5)
|
||||||
|
class ParodyFilter : AdvSearchEntryFilter("Parodies", 6)
|
||||||
|
class CharactersFilter : AdvSearchEntryFilter("Characters", 7)
|
||||||
|
class UploaderFilter : AdvSearchEntryFilter("Uploaders", 8)
|
||||||
|
open class AdvSearchEntryFilter(name: String, val type: Int) : Filter.Text(name)
|
||||||
|
|
||||||
|
class SortFilter : Filter.Select<SortType>("Sort by", SortType.values())
|
||||||
|
class LengthFilter : Filter.Select<LengthType>("Length", LengthType.values())
|
||||||
|
class MinimumRatingFilter : Filter.Select<String>("Minimum rating", (0 .. 5).map { "$it stars" }.toTypedArray())
|
||||||
|
class ExcludeParodiesFilter : Filter.CheckBox("Exclude parodies")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val jsonParser by lazy {
|
||||||
|
JsonParser()
|
||||||
|
}
|
||||||
|
|
||||||
|
val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US)
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,8 @@ val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
|
|||||||
|
|
||||||
val HENTAI_CAFE_SOURCE_ID = LEWD_SOURCE_SERIES + 8
|
val HENTAI_CAFE_SOURCE_ID = LEWD_SOURCE_SERIES + 8
|
||||||
|
|
||||||
|
val TSUMINO_SOURCE_ID = LEWD_SOURCE_SERIES + 9
|
||||||
|
|
||||||
fun isLewdSource(source: Long) = source in 6900..6999
|
fun isLewdSource(source: Long) = source in 6900..6999
|
||||||
|
|
||||||
fun isEhSource(source: Long) = source == EH_SOURCE_ID
|
fun isEhSource(source: Long) = source == EH_SOURCE_ID
|
||||||
|
@ -10,10 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
||||||
import exh.metadata.models.ExGalleryMetadata
|
import exh.metadata.models.*
|
||||||
import exh.metadata.models.NHentaiMetadata
|
|
||||||
import exh.metadata.models.PervEdenGalleryMetadata
|
|
||||||
import exh.metadata.models.PervEdenLang
|
|
||||||
import exh.util.defRealm
|
import exh.util.defRealm
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -68,7 +65,7 @@ class GalleryAdder {
|
|||||||
try {
|
try {
|
||||||
val urlObj = Uri.parse(url)
|
val urlObj = Uri.parse(url)
|
||||||
val lowercasePs = urlObj.pathSegments.map(String::toLowerCase)
|
val lowercasePs = urlObj.pathSegments.map(String::toLowerCase)
|
||||||
val firstPathSegment = lowercasePs[0]
|
val lcFirstPathSegment = lowercasePs[0]
|
||||||
val source = when (urlObj.host.toLowerCase()) {
|
val source = when (urlObj.host.toLowerCase()) {
|
||||||
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
|
"g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
|
||||||
"exhentai.org" -> EXH_SOURCE_ID
|
"exhentai.org" -> EXH_SOURCE_ID
|
||||||
@ -80,6 +77,8 @@ class GalleryAdder {
|
|||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"hentai.cafe" -> HENTAI_CAFE_SOURCE_ID
|
||||||
|
"www.tsumino.com" -> TSUMINO_SOURCE_ID
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +87,7 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val realUrl = when(source) {
|
val realUrl = when(source) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID -> when (firstPathSegment) {
|
EH_SOURCE_ID, EXH_SOURCE_ID -> when (lcFirstPathSegment) {
|
||||||
"g" -> {
|
"g" -> {
|
||||||
//Is already gallery page, do nothing
|
//Is already gallery page, do nothing
|
||||||
url
|
url
|
||||||
@ -100,7 +99,7 @@ class GalleryAdder {
|
|||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
NHENTAI_SOURCE_ID -> {
|
NHENTAI_SOURCE_ID -> {
|
||||||
if(firstPathSegment != "g")
|
if(lcFirstPathSegment != "g")
|
||||||
return GalleryAddEvent.Fail.UnknownType(url)
|
return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
|
|
||||||
"https://nhentai.net/g/${urlObj.pathSegments[1]}/"
|
"https://nhentai.net/g/${urlObj.pathSegments[1]}/"
|
||||||
@ -113,6 +112,18 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
uri.toString()
|
uri.toString()
|
||||||
}
|
}
|
||||||
|
HENTAI_CAFE_SOURCE_ID -> {
|
||||||
|
if(lcFirstPathSegment == "manga")
|
||||||
|
"https://hentai.cafe/${urlObj.pathSegments[2]}"
|
||||||
|
|
||||||
|
"https://hentai.cafe/$lcFirstPathSegment"
|
||||||
|
}
|
||||||
|
TSUMINO_SOURCE_ID -> {
|
||||||
|
if(lcFirstPathSegment != "read" && lcFirstPathSegment != "book")
|
||||||
|
return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
|
|
||||||
|
"https://tsumino.com/Book/Info/${urlObj.pathSegments[2]}"
|
||||||
|
}
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +135,8 @@ class GalleryAdder {
|
|||||||
NHENTAI_SOURCE_ID -> realUrl //nhentai uses URLs directly (oops, my bad when implementing this source)
|
NHENTAI_SOURCE_ID -> realUrl //nhentai uses URLs directly (oops, my bad when implementing this source)
|
||||||
PERV_EDEN_EN_SOURCE_ID,
|
PERV_EDEN_EN_SOURCE_ID,
|
||||||
PERV_EDEN_IT_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
PERV_EDEN_IT_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
||||||
|
HENTAI_CAFE_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
||||||
|
TSUMINO_SOURCE_ID -> getUrlWithoutDomain(realUrl)
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,23 +155,14 @@ class GalleryAdder {
|
|||||||
//Apply metadata
|
//Apply metadata
|
||||||
defRealm { realm ->
|
defRealm { realm ->
|
||||||
when (source) {
|
when (source) {
|
||||||
EH_SOURCE_ID, EXH_SOURCE_ID ->
|
EH_SOURCE_ID, EXH_SOURCE_ID -> ExGalleryMetadata.UrlQuery(realUrl, isExSource(source))
|
||||||
ExGalleryMetadata.UrlQuery(realUrl, isExSource(source))
|
NHENTAI_SOURCE_ID -> NHentaiMetadata.UrlQuery(realUrl)
|
||||||
.query(realm)
|
|
||||||
.findFirst()?.copyTo(manga)
|
|
||||||
NHENTAI_SOURCE_ID ->
|
|
||||||
NHentaiMetadata.UrlQuery(realUrl)
|
|
||||||
.query(realm)
|
|
||||||
.findFirst()
|
|
||||||
?.copyTo(manga)
|
|
||||||
PERV_EDEN_EN_SOURCE_ID,
|
PERV_EDEN_EN_SOURCE_ID,
|
||||||
PERV_EDEN_IT_SOURCE_ID ->
|
PERV_EDEN_IT_SOURCE_ID -> PervEdenGalleryMetadata.UrlQuery(realUrl, PervEdenLang.source(source))
|
||||||
PervEdenGalleryMetadata.UrlQuery(realUrl, PervEdenLang.source(source))
|
HENTAI_CAFE_SOURCE_ID -> HentaiCafeMetadata.UrlQuery(realUrl)
|
||||||
.query(realm)
|
TSUMINO_SOURCE_ID -> TsuminoMetadata.UrlQuery(realUrl)
|
||||||
.findFirst()
|
|
||||||
?.copyTo(manga)
|
|
||||||
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
else -> return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}.query(realm).findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fav) manga.favorite = true
|
if (fav) manga.favorite = true
|
||||||
|
@ -26,15 +26,17 @@ open class HentaiCafeMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var thumbnailUrl: String? = null
|
||||||
|
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
|
|
||||||
var artist: String? = null
|
var artist: String? = null
|
||||||
|
|
||||||
override var uploader: String? = null
|
override var uploader: String? = null //Always will be null as this is unknown
|
||||||
|
|
||||||
override var tags: RealmList<Tag> = RealmList()
|
override var tags: RealmList<Tag> = RealmList()
|
||||||
|
|
||||||
override fun getTitles() = listOf(title).filterNotNull()
|
override fun getTitles() = listOfNotNull(title)
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
override val titleFields = listOf(
|
override val titleFields = listOf(
|
||||||
@ -45,6 +47,8 @@ open class HentaiCafeMetadata : RealmObject(), SearchableGalleryMetadata {
|
|||||||
override var mangaId: Long? = null
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
override fun copyTo(manga: SManga) {
|
override fun copyTo(manga: SManga) {
|
||||||
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
manga.title = title!!
|
manga.title = title!!
|
||||||
manga.artist = artist
|
manga.artist = artist
|
||||||
manga.author = artist
|
manga.author = artist
|
||||||
|
130
app/src/main/java/exh/metadata/models/TsuminoMetadata.kt
Normal file
130
app/src/main/java/exh/metadata/models/TsuminoMetadata.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.metadata.EX_DATE_FORMAT
|
||||||
|
import exh.metadata.buildTagsDescription
|
||||||
|
import exh.plusAssign
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Ignore
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import io.realm.annotations.RealmClass
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@RealmClass
|
||||||
|
open class TsuminoMetadata : RealmObject(), SearchableGalleryMetadata {
|
||||||
|
@PrimaryKey
|
||||||
|
override var uuid: String = UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
@Index
|
||||||
|
var tmId: String? = null
|
||||||
|
|
||||||
|
var url get() = tmId?.let { mangaUrlFromId(it) }
|
||||||
|
set(a) {
|
||||||
|
a?.let {
|
||||||
|
tmId = tmIdFromUrl(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String? = null
|
||||||
|
|
||||||
|
var artist: String? = null
|
||||||
|
|
||||||
|
override var uploader: String? = null
|
||||||
|
|
||||||
|
var uploadDate: Long? = null
|
||||||
|
|
||||||
|
var length: Int? = null
|
||||||
|
|
||||||
|
var ratingString: String? = null
|
||||||
|
|
||||||
|
var category: String? = null
|
||||||
|
|
||||||
|
var collection: String? = null
|
||||||
|
|
||||||
|
var group: String? = null
|
||||||
|
|
||||||
|
var parody: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
var character: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
override var tags: RealmList<Tag> = RealmList()
|
||||||
|
|
||||||
|
override fun getTitles() = listOfNotNull(title)
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
override val titleFields = listOf(
|
||||||
|
TsuminoMetadata::title.name
|
||||||
|
)
|
||||||
|
|
||||||
|
@Index
|
||||||
|
override var mangaId: Long? = null
|
||||||
|
|
||||||
|
class EmptyQuery : GalleryQuery<TsuminoMetadata>(TsuminoMetadata::class)
|
||||||
|
|
||||||
|
class UrlQuery(
|
||||||
|
val url: String
|
||||||
|
) : GalleryQuery<TsuminoMetadata>(TsuminoMetadata::class) {
|
||||||
|
override fun transform() = Query(
|
||||||
|
tmIdFromUrl(url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Query(
|
||||||
|
val tmId: String
|
||||||
|
) : GalleryQuery<TsuminoMetadata>(TsuminoMetadata::class) {
|
||||||
|
override fun map() = mapOf(
|
||||||
|
TsuminoMetadata::tmId to Query::tmId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyTo(manga: SManga) {
|
||||||
|
title?.let { manga.title = it }
|
||||||
|
manga.thumbnail_url = thumbUrlFromId(tmId.toString())
|
||||||
|
|
||||||
|
artist?.let { manga.artist = it }
|
||||||
|
|
||||||
|
manga.status = SManga.UNKNOWN
|
||||||
|
|
||||||
|
val titleDesc = "Title: $title\n"
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||||
|
uploadDate?.let { detailsDesc += "Uploaded: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
||||||
|
length?.let { detailsDesc += "Length: $it pages\n" }
|
||||||
|
ratingString?.let { detailsDesc += "Rating: $it\n" }
|
||||||
|
category?.let {
|
||||||
|
manga.genre = it
|
||||||
|
detailsDesc += "Category: $it\n"
|
||||||
|
}
|
||||||
|
collection?.let { detailsDesc += "Collection: $it\n" }
|
||||||
|
group?.let { detailsDesc += "Group: $it\n" }
|
||||||
|
val parodiesString = parody.joinToString()
|
||||||
|
if(parodiesString.isNotEmpty()) {
|
||||||
|
detailsDesc += "Parody: $parodiesString\n"
|
||||||
|
}
|
||||||
|
val charactersString = character.joinToString()
|
||||||
|
if(charactersString.isNotEmpty()) {
|
||||||
|
detailsDesc += "Character: $charactersString\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsDesc = buildTagsDescription(this)
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val BASE_URL = "https://www.tsumino.com"
|
||||||
|
|
||||||
|
fun tmIdFromUrl(url: String)
|
||||||
|
= Uri.parse(url).pathSegments[2]
|
||||||
|
|
||||||
|
fun mangaUrlFromId(id: String) = "$BASE_URL/Book/Info/$id"
|
||||||
|
|
||||||
|
fun thumbUrlFromId(id: String) = "$BASE_URL/Image/Thumb/$id"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user