mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-08 18:18:56 +01:00
Upstream merge
Internal permission change Fix url adder
This commit is contained in:
62
app/src/main/java/exh/metadata/MetadataHelper.kt
Executable file
62
app/src/main/java/exh/metadata/MetadataHelper.kt
Executable file
@@ -0,0 +1,62 @@
|
||||
package exh.metadata
|
||||
|
||||
import exh.*
|
||||
import exh.metadata.models.ExGalleryMetadata
|
||||
import exh.metadata.models.NHentaiMetadata
|
||||
import exh.metadata.models.PervEdenGalleryMetadata
|
||||
import exh.metadata.models.SearchableGalleryMetadata
|
||||
import io.paperdb.Paper
|
||||
|
||||
class MetadataHelper {
|
||||
|
||||
fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
|
||||
= (if(isExSource(source) || isEhSource(source)) exGalleryBook()
|
||||
else if(isPervEdenSource(source)) pervEdenGalleryBook()
|
||||
else if(isNhentaiSource(source)) nhentaiGalleryBook()
|
||||
else null)?.write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!!
|
||||
|
||||
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 fetchNhentaiMetadata(url: String) = NHentaiMetadata().let {
|
||||
it.url = url
|
||||
nhentaiGalleryBook().read<NHentaiMetadata>(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 if(isNhentaiSource(source)) {
|
||||
return fetchNhentaiMetadata(url)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllGalleries() = exGalleryBook().allKeys.map {
|
||||
exGalleryBook().read<ExGalleryMetadata>(it)
|
||||
}
|
||||
|
||||
fun exGalleryBook() = Paper.book("gallery-ex")!!
|
||||
|
||||
fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!!
|
||||
|
||||
fun nhentaiGalleryBook() = Paper.book("gallery-nhentai")!!
|
||||
}
|
||||
47
app/src/main/java/exh/metadata/MetadataUtil.kt
Executable file
47
app/src/main/java/exh/metadata/MetadataUtil.kt
Executable file
@@ -0,0 +1,47 @@
|
||||
package exh.metadata
|
||||
|
||||
/**
|
||||
* Metadata utils
|
||||
*/
|
||||
fun humanReadableByteCount(bytes: Long, si: Boolean): String {
|
||||
val unit = if (si) 1000 else 1024
|
||||
if (bytes < unit) return bytes.toString() + " B"
|
||||
val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
|
||||
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit.toDouble(), exp.toDouble()), pre)
|
||||
}
|
||||
|
||||
private val KB_FACTOR: Long = 1000
|
||||
private val KIB_FACTOR: Long = 1024
|
||||
private val MB_FACTOR = 1000 * KB_FACTOR
|
||||
private val MIB_FACTOR = 1024 * KIB_FACTOR
|
||||
private val GB_FACTOR = 1000 * MB_FACTOR
|
||||
private val GIB_FACTOR = 1024 * MIB_FACTOR
|
||||
|
||||
fun parseHumanReadableByteCount(arg0: String): Double? {
|
||||
val spaceNdx = arg0.indexOf(" ")
|
||||
val ret = java.lang.Double.parseDouble(arg0.substring(0, spaceNdx))
|
||||
when (arg0.substring(spaceNdx + 1)) {
|
||||
"GB" -> return ret * GB_FACTOR
|
||||
"GiB" -> return ret * GIB_FACTOR
|
||||
"MB" -> return ret * MB_FACTOR
|
||||
"MiB" -> return ret * MIB_FACTOR
|
||||
"KB" -> return ret * KB_FACTOR
|
||||
"KiB" -> return ret * KIB_FACTOR
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun String?.nullIfBlank(): String? = if(isNullOrBlank())
|
||||
null
|
||||
else
|
||||
this
|
||||
|
||||
fun <T> ignore(expr: () -> T): T? {
|
||||
return try { expr() } catch (t: Throwable) { null }
|
||||
}
|
||||
|
||||
fun <K,V> Set<Map.Entry<K,V>>.forEach(action: (K, V) -> Unit) {
|
||||
forEach { action(it.key, it.value) }
|
||||
}
|
||||
218
app/src/main/java/exh/metadata/MetdataCopier.kt
Executable file
218
app/src/main/java/exh/metadata/MetdataCopier.kt
Executable file
@@ -0,0 +1,218 @@
|
||||
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.EHentai
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
|
||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
||||
import exh.metadata.models.*
|
||||
import exh.plusAssign
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Copies gallery metadata to a manga object
|
||||
*/
|
||||
|
||||
private const val EH_ARTIST_NAMESPACE = "artist"
|
||||
private const val EH_AUTHOR_NAMESPACE = "author"
|
||||
|
||||
private const val NHENTAI_ARTIST_NAMESPACE = "artist"
|
||||
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||
|
||||
private val ONGOING_SUFFIX = arrayOf(
|
||||
"[ongoing]",
|
||||
"(ongoing)",
|
||||
"{ongoing}"
|
||||
)
|
||||
|
||||
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
||||
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
fun ExGalleryMetadata.copyTo(manga: SManga) {
|
||||
//TODO Find some way to do this with SManga
|
||||
/*exh?.let {
|
||||
manga.source = if(it)
|
||||
2
|
||||
else
|
||||
1
|
||||
}*/
|
||||
url?.let { manga.url = it }
|
||||
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||
|
||||
//No title bug?
|
||||
val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
|
||||
altTitle ?: title
|
||||
else
|
||||
title
|
||||
titleObj?.let { manga.title = it }
|
||||
|
||||
//Set artist (if we can find one)
|
||||
tags[EH_ARTIST_NAMESPACE]?.let {
|
||||
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
|
||||
}
|
||||
//Set author (if we can find one)
|
||||
tags[EH_AUTHOR_NAMESPACE]?.let {
|
||||
if(it.isNotEmpty()) manga.author = it.joinToString(transform = Tag::name)
|
||||
}
|
||||
//Set genre
|
||||
genre?.let { manga.genre = it }
|
||||
|
||||
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||
//We default to completed
|
||||
manga.status = SManga.COMPLETED
|
||||
title?.let { t ->
|
||||
ONGOING_SUFFIX.find {
|
||||
t.endsWith(it, ignoreCase = true)
|
||||
}?.let {
|
||||
manga.status = SManga.ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
//Build a nice looking description out of what we know
|
||||
val titleDesc = StringBuilder()
|
||||
title?.let { titleDesc += "Title: $it\n" }
|
||||
altTitle?.let { titleDesc += "Alternate Title: $it\n" }
|
||||
|
||||
val detailsDesc = StringBuilder()
|
||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||
datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
||||
visible?.let { detailsDesc += "Visible: $it\n" }
|
||||
language?.let {
|
||||
detailsDesc += "Language: $it"
|
||||
if(translated == true) detailsDesc += " TR"
|
||||
detailsDesc += "\n"
|
||||
}
|
||||
size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" }
|
||||
length?.let { detailsDesc += "Length: $it pages\n" }
|
||||
favorites?.let { detailsDesc += "Favorited: $it times\n" }
|
||||
averageRating?.let {
|
||||
detailsDesc += "Rating: $it"
|
||||
ratingCount?.let { detailsDesc += " ($it)" }
|
||||
detailsDesc += "\n"
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
fun NHentaiMetadata.copyTo(manga: SManga) {
|
||||
url?.let { manga.url = it }
|
||||
|
||||
//TODO next update allow this to be changed to use HD covers
|
||||
if(mediaId != null)
|
||||
NHentaiMetadata.typeToExtension(thumbnailImageType)?.let {
|
||||
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/thumb.$it"
|
||||
}
|
||||
|
||||
manga.title = englishTitle ?: japaneseTitle ?: shortTitle!!
|
||||
|
||||
//Set artist (if we can find one)
|
||||
tags[NHENTAI_ARTIST_NAMESPACE]?.let {
|
||||
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
|
||||
}
|
||||
|
||||
tags[NHENTAI_CATEGORIES_NAMESPACE]?.let {
|
||||
if(it.isNotEmpty()) manga.genre = it.joinToString(transform = Tag::name)
|
||||
}
|
||||
|
||||
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||
//We default to completed
|
||||
manga.status = SManga.COMPLETED
|
||||
englishTitle?.let { t ->
|
||||
ONGOING_SUFFIX.find {
|
||||
t.endsWith(it, ignoreCase = true)
|
||||
}?.let {
|
||||
manga.status = SManga.ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
val titleDesc = StringBuilder()
|
||||
englishTitle?.let { titleDesc += "English Title: $it\n" }
|
||||
japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
||||
shortTitle?.let { titleDesc += "Short Title: $it\n" }
|
||||
|
||||
val detailsDesc = StringBuilder()
|
||||
uploadDate?.let { detailsDesc += "Upload Date: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
||||
pageImageTypes.size.let { detailsDesc += "Length: $it pages\n" }
|
||||
favoritesCount?.let { detailsDesc += "Favorited: $it times\n" }
|
||||
scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" }
|
||||
|
||||
val tagsDesc = buildTagsDescription(this)
|
||||
|
||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||
.filter(String::isNotBlank)
|
||||
.joinToString(separator = "\n")
|
||||
}
|
||||
|
||||
fun SearchableGalleryMetadata.genericCopyTo(manga: SManga): Boolean {
|
||||
when(this) {
|
||||
is ExGalleryMetadata -> this.copyTo(manga)
|
||||
is PervEdenGalleryMetadata -> this.copyTo(manga)
|
||||
is NHentaiMetadata -> this.copyTo(manga)
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
Executable file
51
app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
Executable file
@@ -0,0 +1,51 @@
|
||||
package exh.metadata.models
|
||||
|
||||
import android.net.Uri
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Gallery metadata storage model
|
||||
*/
|
||||
|
||||
class ExGalleryMetadata : SearchableGalleryMetadata() {
|
||||
var url: String? = null
|
||||
|
||||
var exh: Boolean? = null
|
||||
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var title: String? = null
|
||||
var altTitle: String? = null
|
||||
|
||||
var genre: String? = null
|
||||
|
||||
var datePosted: Long? = null
|
||||
var parent: String? = null
|
||||
var visible: String? = null //Not a boolean
|
||||
var language: String? = null
|
||||
var translated: Boolean? = null
|
||||
var size: Long? = null
|
||||
var length: Int? = null
|
||||
var favorites: Int? = null
|
||||
var ratingCount: Int? = null
|
||||
var averageRating: Double? = null
|
||||
|
||||
override fun getTitles() = listOf(title, altTitle).filterNotNull()
|
||||
|
||||
private fun splitGalleryUrl()
|
||||
= url?.let {
|
||||
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||
}
|
||||
|
||||
fun galleryId() = splitGalleryUrl()?.let { it[it.size - 2] }
|
||||
|
||||
fun galleryToken() =
|
||||
splitGalleryUrl()?.last()
|
||||
|
||||
override fun galleryUniqueIdentifier() = exh?.let { exh ->
|
||||
url?.let {
|
||||
//Fuck, this should be EXH and EH but it's too late to change it now...
|
||||
"${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
48
app/src/main/java/exh/metadata/models/NHentaiMetadata.kt
Executable file
48
app/src/main/java/exh/metadata/models/NHentaiMetadata.kt
Executable file
@@ -0,0 +1,48 @@
|
||||
package exh.metadata.models
|
||||
|
||||
/**
|
||||
* NHentai metadata
|
||||
*/
|
||||
|
||||
class NHentaiMetadata : SearchableGalleryMetadata() {
|
||||
|
||||
var id: Long? = null
|
||||
|
||||
var url get() = id?.let { "$BASE_URL/g/$it" }
|
||||
set(a) {
|
||||
a?.let {
|
||||
id = a.split("/").last().toLong()
|
||||
}
|
||||
}
|
||||
|
||||
var uploadDate: Long? = null
|
||||
|
||||
var favoritesCount: Long? = null
|
||||
|
||||
var mediaId: String? = null
|
||||
|
||||
var japaneseTitle: String? = null
|
||||
var englishTitle: String? = null
|
||||
var shortTitle: String? = null
|
||||
|
||||
var coverImageType: String? = null
|
||||
var pageImageTypes: MutableList<String> = mutableListOf()
|
||||
var thumbnailImageType: String? = null
|
||||
|
||||
var scanlator: String? = null
|
||||
|
||||
override fun galleryUniqueIdentifier(): String? = "NHENTAI-$id"
|
||||
|
||||
override fun getTitles() = listOf(japaneseTitle, englishTitle, shortTitle).filterNotNull()
|
||||
|
||||
companion object {
|
||||
val BASE_URL = "https://nhentai.net"
|
||||
|
||||
fun typeToExtension(t: String?) =
|
||||
when(t) {
|
||||
"p" -> "png"
|
||||
"j" -> "jpg"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt
Executable file
32
app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt
Executable file
@@ -0,0 +1,32 @@
|
||||
package exh.metadata.models
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
class PervEdenGalleryMetadata : SearchableGalleryMetadata() {
|
||||
var url: String? = null
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var title: String? = null
|
||||
var altTitles: MutableList<String> = mutableListOf()
|
||||
|
||||
var artist: String? = null
|
||||
|
||||
var type: String? = null
|
||||
|
||||
var rating: Float? = null
|
||||
|
||||
var status: String? = null
|
||||
|
||||
var lang: String? = null
|
||||
|
||||
override fun getTitles() = listOf(title).plus(altTitles).filterNotNull()
|
||||
|
||||
private fun splitGalleryUrl()
|
||||
= url?.let {
|
||||
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||
}
|
||||
|
||||
override fun galleryUniqueIdentifier() = splitGalleryUrl()?.let {
|
||||
"PERVEDEN-${lang?.toUpperCase()}-${it.last()}"
|
||||
}
|
||||
}
|
||||
18
app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt
Executable file
18
app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt
Executable file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
|
||||
//Being specific about which classes are used in generics to make deserialization easier
|
||||
val tags: HashMap<String, ArrayList<Tag>> = HashMap()
|
||||
|
||||
abstract fun galleryUniqueIdentifier(): String?
|
||||
|
||||
abstract fun getTitles(): List<String>
|
||||
}
|
||||
7
app/src/main/java/exh/metadata/models/Tag.kt
Executable file
7
app/src/main/java/exh/metadata/models/Tag.kt
Executable file
@@ -0,0 +1,7 @@
|
||||
package exh.metadata.models
|
||||
|
||||
/**
|
||||
* Simple tag model
|
||||
*/
|
||||
|
||||
data class Tag(var name: String, var light: Boolean)
|
||||
Reference in New Issue
Block a user