Extract source api from app module (#8014)
* Extract source api from app module
* Extract source online api from app module
(cherry picked from commit 86fe850794)
# Conflicts:
# app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
# core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
# source-api/src/main/java/eu/kanade/tachiyomi/source/Source.kt
# source-api/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
package exh.log
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.preference.PreferenceManager
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||
|
||||
enum class EHLogLevel(@StringRes val nameRes: Int, @StringRes val description: Int) {
|
||||
MINIMAL(R.string.log_minimal, R.string.log_minimal_desc),
|
||||
EXTRA(R.string.log_extra, R.string.log_extra_desc),
|
||||
EXTREME(R.string.log_extreme, R.string.log_extreme_desc),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private var curLogLevel: Int? = null
|
||||
|
||||
val currentLogLevel get() = values()[curLogLevel!!]
|
||||
|
||||
fun init(context: Context) {
|
||||
curLogLevel = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getInt(PreferenceKeys.eh_logLevel, 0)
|
||||
}
|
||||
|
||||
fun shouldLog(requiredLogLevel: EHLogLevel): Boolean {
|
||||
return curLogLevel!! >= requiredLogLevel.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package exh.log
|
||||
|
||||
import com.elvishew.xlog.XLog
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
||||
fun OkHttpClient.Builder.maybeInjectEHLogger(): OkHttpClient.Builder {
|
||||
if (EHLogLevel.shouldLog(EHLogLevel.EXTREME)) {
|
||||
val logger: HttpLoggingInterceptor.Logger = HttpLoggingInterceptor.Logger { message ->
|
||||
try {
|
||||
Json.decodeFromString<Any>(message)
|
||||
XLog.tag("||EH-NETWORK-JSON").json(message)
|
||||
} catch (ex: Exception) {
|
||||
XLog.tag("||EH-NETWORK").disableBorder().d(message)
|
||||
}
|
||||
}
|
||||
return addInterceptor(HttpLoggingInterceptor(logger).apply { level = HttpLoggingInterceptor.Level.BODY })
|
||||
}
|
||||
return this
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package exh.log
|
||||
|
||||
import android.util.Log
|
||||
import com.elvishew.xlog.Logger
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.elvishew.xlog.LogLevel as XLogLevel
|
||||
|
||||
fun Any.xLog(): Logger = XLog.tag(this::class.java.simpleName).build()
|
||||
|
||||
fun Any.xLogStack(): Logger = XLog.tag(this::class.java.simpleName).enableStackTrace(0).build()
|
||||
|
||||
fun Any.xLogE(log: String) = xLog().e(log)
|
||||
fun Any.xLogW(log: String) = xLog().w(log)
|
||||
fun Any.xLogD(log: String) = xLog().d(log)
|
||||
fun Any.xLogI(log: String) = xLog().i(log)
|
||||
fun Any.xLog(logLevel: LogLevel, log: String) = xLog().log(logLevel.int, log)
|
||||
fun Any.xLogJson(log: String) = xLog().json(log)
|
||||
fun Any.xLogXML(log: String) = xLog().xml(log)
|
||||
|
||||
fun Any.xLogE(log: String, e: Throwable) = xLogStack().e(log, e)
|
||||
fun Any.xLogW(log: String, e: Throwable) = xLogStack().w(log, e)
|
||||
fun Any.xLogD(log: String, e: Throwable) = xLogStack().d(log, e)
|
||||
fun Any.xLogI(log: String, e: Throwable) = xLogStack().i(log, e)
|
||||
fun Any.xLog(logLevel: LogLevel, log: String, e: Throwable) = xLogStack().log(logLevel.int, log, e)
|
||||
|
||||
fun Any.xLogE(log: Any?) = xLog().let { if (log == null) it.e("null") else it.e(log) }
|
||||
fun Any.xLogW(log: Any?) = xLog().let { if (log == null) it.w("null") else it.w(log) }
|
||||
fun Any.xLogD(log: Any?) = xLog().let { if (log == null) it.d("null") else it.d(log) }
|
||||
fun Any.xLogI(log: Any?) = xLog().let { if (log == null) it.i("null") else it.i(log) }
|
||||
fun Any.xLog(logLevel: LogLevel, log: Any?) = xLog().let { if (log == null) it.log(logLevel.int, "null") else it.log(logLevel.int, log) }
|
||||
|
||||
/*fun Any.xLogE(vararg logs: Any) = xLog().e(logs)
|
||||
fun Any.xLogW(vararg logs: Any) = xLog().w(logs)
|
||||
fun Any.xLogD(vararg logs: Any) = xLog().d(logs)
|
||||
fun Any.xLogI(vararg logs: Any) = xLog().i(logs)
|
||||
fun Any.xLog(logLevel: LogLevel, vararg logs: Any) = xLog().log(logLevel.int, logs)*/
|
||||
|
||||
fun Any.xLogE(format: String, vararg args: Any?) = xLog().e(format, *args)
|
||||
fun Any.xLogW(format: String, vararg args: Any?) = xLog().w(format, *args)
|
||||
fun Any.xLogD(format: String, vararg args: Any?) = xLog().d(format, *args)
|
||||
fun Any.xLogI(format: String, vararg args: Any?) = xLog().i(format, *args)
|
||||
fun Any.xLog(logLevel: LogLevel, format: String, vararg args: Any) = xLog().log(logLevel.int, format, *args)
|
||||
|
||||
sealed class LogLevel(val int: Int, val androidLevel: Int) {
|
||||
object None : LogLevel(XLogLevel.NONE, Log.ASSERT)
|
||||
object Error : LogLevel(XLogLevel.ERROR, Log.ERROR)
|
||||
object Warn : LogLevel(XLogLevel.WARN, Log.WARN)
|
||||
object Info : LogLevel(XLogLevel.INFO, Log.INFO)
|
||||
object Debug : LogLevel(XLogLevel.DEBUG, Log.DEBUG)
|
||||
object Verbose : LogLevel(XLogLevel.VERBOSE, Log.VERBOSE)
|
||||
object All : LogLevel(XLogLevel.ALL, Log.VERBOSE)
|
||||
|
||||
val name get() = getLevelName(this)
|
||||
val shortName get() = getLevelShortName(this)
|
||||
|
||||
companion object {
|
||||
fun getLevelName(logLevel: LogLevel): String = XLogLevel.getLevelName(logLevel.int)
|
||||
fun getLevelShortName(logLevel: LogLevel): String = XLogLevel.getShortLevelName(logLevel.int)
|
||||
|
||||
fun values() = listOf(
|
||||
None,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Verbose,
|
||||
All,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use proper throwable function", ReplaceWith("""xLogE("", log)"""))
|
||||
fun Any.xLogE(log: Throwable) = xLogStack().e(log)
|
||||
|
||||
@Deprecated("Use proper throwable function", ReplaceWith("""xLogW("", log)"""))
|
||||
fun Any.xLogW(log: Throwable) = xLogStack().w(log)
|
||||
|
||||
@Deprecated("Use proper throwable function", ReplaceWith("""xLogD("", log)"""))
|
||||
fun Any.xLogD(log: Throwable) = xLogStack().d(log)
|
||||
|
||||
@Deprecated("Use proper throwable function", ReplaceWith("""xLogI("", log)"""))
|
||||
fun Any.xLogI(log: Throwable) = xLogStack().i(log)
|
||||
|
||||
@Deprecated("Use proper throwable function", ReplaceWith("""xLog(logLevel, "", log)"""))
|
||||
fun Any.xLog(logLevel: LogLevel, log: Throwable) = xLogStack().log(logLevel.int, log)
|
||||
@@ -1,29 +0,0 @@
|
||||
package exh.md.utils
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
enum class MangaDexRelation(@StringRes val resId: Int, val mdString: String?) {
|
||||
SIMILAR(R.string.relation_similar, null),
|
||||
MONOCHROME(R.string.relation_monochrome, "monochrome"),
|
||||
MAIN_STORY(R.string.relation_main_story, "main_story"),
|
||||
ADAPTED_FROM(R.string.relation_adapted_from, "adapted_from"),
|
||||
BASED_ON(R.string.relation_based_on, "based_on"),
|
||||
PREQUEL(R.string.relation_prequel, "prequel"),
|
||||
SIDE_STORY(R.string.relation_side_story, "side_story"),
|
||||
DOUJINSHI(R.string.relation_doujinshi, "doujinshi"),
|
||||
SAME_FRANCHISE(R.string.relation_same_franchise, "same_franchise"),
|
||||
SHARED_UNIVERSE(R.string.relation_shared_universe, "shared_universe"),
|
||||
SEQUEL(R.string.relation_sequel, "sequel"),
|
||||
SPIN_OFF(R.string.relation_spin_off, "spin_off"),
|
||||
ALTERNATE_STORY(R.string.relation_alternate_story, "alternate_story"),
|
||||
PRESERIALIZATION(R.string.relation_preserialization, "preserialization"),
|
||||
COLORED(R.string.relation_colored, "colored"),
|
||||
SERIALIZATION(R.string.relation_serialization, "serialization"),
|
||||
ALTERNATE_VERSION(R.string.relation_alternate_version, "alternate_version"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromDex(mdString: String) = values().find { it.mdString == mdString }
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.serialization.Serializable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
|
||||
@Serializable
|
||||
class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||
var gId: String?
|
||||
get() = indexedExtra
|
||||
set(value) { indexedExtra = value }
|
||||
|
||||
var gToken: String? = null
|
||||
var exh: Boolean? = null
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_TITLE)
|
||||
var altTitle by titleDelegate(TITLE_TYPE_ALT_TITLE)
|
||||
|
||||
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
|
||||
|
||||
var aged: Boolean = false
|
||||
var lastUpdateCheck: Long = 0
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = gId?.let { gId ->
|
||||
gToken?.let { gToken ->
|
||||
idAndTokenToUrl(gId, gToken)
|
||||
}
|
||||
}
|
||||
val cover = thumbnailUrl
|
||||
|
||||
// No title bug?
|
||||
val title = altTitle
|
||||
?.takeIf { Injekt.get<PreferencesHelper>().useJapaneseTitle().get() }
|
||||
?: title
|
||||
|
||||
// Set artist (if we can find one)
|
||||
val artist = tags.ofNamespace(EH_ARTIST_NAMESPACE)
|
||||
.ifEmpty { null }
|
||||
?.joinToString { it.name }
|
||||
|
||||
// Copy tags -> genres
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
// Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||
// We default to completed
|
||||
var status = SManga.COMPLETED
|
||||
title?.let { t ->
|
||||
MetadataUtil.ONGOING_SUFFIX.find {
|
||||
t.endsWith(it, ignoreCase = true)
|
||||
}?.let {
|
||||
status = SManga.ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
title = title ?: manga.title,
|
||||
artist = artist ?: manga.artist,
|
||||
description = description,
|
||||
genre = genres,
|
||||
status = status,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(gId) { getString(R.string.id) },
|
||||
getItem(gToken) { getString(R.string.token) },
|
||||
getItem(exh) { getString(R.string.is_exhentai_gallery) },
|
||||
getItem(thumbnailUrl) { getString(R.string.thumbnail_url) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(altTitle) { getString(R.string.alt_title) },
|
||||
getItem(genre) { getString(R.string.genre) },
|
||||
getItem(datePosted, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { getString(R.string.date_posted) },
|
||||
getItem(parent) { getString(R.string.parent) },
|
||||
getItem(visible) { getString(R.string.visible) },
|
||||
getItem(language) { getString(R.string.language) },
|
||||
getItem(translated) { getString(R.string.translated) },
|
||||
getItem(size, { MetadataUtil.humanReadableByteCount(it, true) }) { getString(R.string.gallery_size) },
|
||||
getItem(length) { getString(R.string.page_count) },
|
||||
getItem(favorites) { getString(R.string.total_favorites) },
|
||||
getItem(ratingCount) { getString(R.string.total_ratings) },
|
||||
getItem(averageRating) { getString(R.string.average_rating) },
|
||||
getItem(aged) { getString(R.string.aged) },
|
||||
getItem(lastUpdateCheck, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { getString(R.string.last_update_check) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_TITLE = 0
|
||||
private const val TITLE_TYPE_ALT_TITLE = 1
|
||||
|
||||
const val TAG_TYPE_NORMAL = 0
|
||||
const val TAG_TYPE_LIGHT = 1
|
||||
const val TAG_TYPE_WEAK = 2
|
||||
|
||||
const val EH_GENRE_NAMESPACE = "genre"
|
||||
private const val EH_ARTIST_NAMESPACE = "artist"
|
||||
const val EH_LANGUAGE_NAMESPACE = "language"
|
||||
const val EH_META_NAMESPACE = "meta"
|
||||
const val EH_UPLOADER_NAMESPACE = "uploader"
|
||||
const val EH_VISIBILITY_NAMESPACE = "visibility"
|
||||
|
||||
private fun splitGalleryUrl(url: String) =
|
||||
url.let {
|
||||
// Only parse URL if is full URL
|
||||
val pathSegments = if (it.startsWith("http")) {
|
||||
it.toUri().pathSegments
|
||||
} else {
|
||||
it.split('/')
|
||||
}
|
||||
pathSegments.filterNot(String::isNullOrBlank)
|
||||
}
|
||||
|
||||
fun galleryId(url: String): String = splitGalleryUrl(url)[1]
|
||||
|
||||
fun galleryToken(url: String): String =
|
||||
splitGalleryUrl(url)[2]
|
||||
|
||||
fun normalizeUrl(url: String) =
|
||||
idAndTokenToUrl(galleryId(url), galleryToken(url))
|
||||
|
||||
fun idAndTokenToUrl(id: String, token: String) =
|
||||
"/g/$id/$token/?nw=always"
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.util.nullIfEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class EightMusesSearchMetadata : RaisedSearchMetadata() {
|
||||
var path: List<String> = emptyList()
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_MAIN)
|
||||
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = path.joinToString("/", prefix = "/")
|
||||
|
||||
val title = title
|
||||
|
||||
val cover = thumbnailUrl
|
||||
|
||||
val artist = tags.ofNamespace(ARTIST_NAMESPACE).joinToString { it.name }
|
||||
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key,
|
||||
title = title ?: manga.title,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
artist = artist,
|
||||
genre = genres,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(path.nullIfEmpty(), { it.joinToString("/", prefix = "/") }) { getString(R.string.path) },
|
||||
getItem(thumbnailUrl) { getString(R.string.thumbnail_url) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
const val TAGS_NAMESPACE = "tags"
|
||||
const val ARTIST_NAMESPACE = "artist"
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class HBrowseSearchMetadata : RaisedSearchMetadata() {
|
||||
var hbId: Long? = null
|
||||
|
||||
var hbUrl: String? = null
|
||||
|
||||
var thumbnail: String? = null
|
||||
|
||||
var title: String? by titleDelegate(TITLE_TYPE_MAIN)
|
||||
|
||||
// Length in pages
|
||||
var length: Int? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = hbUrl
|
||||
|
||||
val title = title
|
||||
|
||||
// Guess thumbnail URL if manga does not have thumbnail URL
|
||||
val cover = if (manga.thumbnail_url.isNullOrBlank()) {
|
||||
guessThumbnailUrl(hbId.toString())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val artist = tags.ofNamespace(ARTIST_NAMESPACE).joinToString { it.name }
|
||||
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
title = title ?: manga.title,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
artist = artist,
|
||||
genre = genres,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(hbId) { getString(R.string.id) },
|
||||
getItem(hbUrl) { getString(R.string.url) },
|
||||
getItem(thumbnail) { getString(R.string.thumbnail_url) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(length) { getString(R.string.page_count) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://www.hbrowse.com"
|
||||
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
const val ARTIST_NAMESPACE = "artist"
|
||||
|
||||
fun guessThumbnailUrl(hbid: String): String {
|
||||
return "$BASE_URL/thumbnails/${hbid}_1.jpg#guessed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.util.nullIfEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.Date
|
||||
|
||||
@Serializable
|
||||
class HitomiSearchMetadata : RaisedSearchMetadata() {
|
||||
var url get() = hlId?.let { urlFromHlId(it) }
|
||||
set(a) {
|
||||
a?.let {
|
||||
hlId = hlIdFromUrl(a)
|
||||
}
|
||||
}
|
||||
|
||||
var hlId: String? = null
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_MAIN)
|
||||
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var artists: List<String> = emptyList()
|
||||
|
||||
var genre: String? = null
|
||||
|
||||
var language: String? = null
|
||||
|
||||
var uploadDate: Long? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val cover = thumbnailUrl
|
||||
|
||||
val title = title
|
||||
|
||||
// Copy tags -> genres
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val artist = artists.joinToString()
|
||||
|
||||
val status = SManga.UNKNOWN
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
title = title ?: manga.title,
|
||||
genre = genres,
|
||||
artist = artist,
|
||||
status = status,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(hlId) { getString(R.string.id) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(thumbnailUrl) { getString(R.string.thumbnail_url) },
|
||||
getItem(artists.nullIfEmpty(), { it.joinToString() }) { getString(R.string.artist) },
|
||||
getItem(genre) { getString(R.string.genre) },
|
||||
getItem(language) { getString(R.string.language) },
|
||||
getItem(uploadDate, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { getString(R.string.date_posted) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
const val BASE_URL = "https://hitomi.la"
|
||||
|
||||
fun hlIdFromUrl(url: String) =
|
||||
url.split('/').last().split('-').last().substringBeforeLast('.')
|
||||
|
||||
fun urlFromHlId(id: String) =
|
||||
"$BASE_URL/galleries/$id.html"
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.md.utils.MangaDexRelation
|
||||
import exh.md.utils.MdUtil
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class MangaDexSearchMetadata : RaisedSearchMetadata() {
|
||||
var mdUuid: String? = null
|
||||
|
||||
// var mdUrl: String? = null
|
||||
|
||||
var cover: String? = null
|
||||
|
||||
var title: String? by titleDelegate(TITLE_TYPE_MAIN)
|
||||
var altTitles: List<String>? = null
|
||||
|
||||
var description: String? = null
|
||||
|
||||
var authors: List<String>? = null
|
||||
var artists: List<String>? = null
|
||||
|
||||
var langFlag: String? = null
|
||||
|
||||
var lastChapterNumber: Int? = null
|
||||
var rating: Float? = null
|
||||
// var users: String? = null
|
||||
|
||||
var anilistId: String? = null
|
||||
var kitsuId: String? = null
|
||||
var myAnimeListId: String? = null
|
||||
var mangaUpdatesId: String? = null
|
||||
var animePlanetId: String? = null
|
||||
|
||||
var status: Int? = null
|
||||
|
||||
// var missing_chapters: String? = null
|
||||
|
||||
var followStatus: Int? = null
|
||||
var relation: MangaDexRelation? = null
|
||||
|
||||
// var maxChapterNumber: Int? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = mdUuid?.let { MdUtil.buildMangaUrl(it) }
|
||||
|
||||
val title = title
|
||||
|
||||
val cover = cover
|
||||
|
||||
val author = authors?.joinToString()?.let { MdUtil.cleanString(it) }
|
||||
|
||||
val artist = artists?.joinToString()?.let { MdUtil.cleanString(it) }
|
||||
|
||||
val status = status
|
||||
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = description
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
title = title ?: manga.title,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
author = author ?: manga.author,
|
||||
artist = artist ?: manga.artist,
|
||||
status = status ?: manga.status,
|
||||
genre = genres,
|
||||
description = description ?: manga.description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(mdUuid) { getString(R.string.id) },
|
||||
// getItem(mdUrl) { getString(R.string.url) },
|
||||
getItem(cover) { getString(R.string.thumbnail_url) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(authors, { it.joinToString() }) { getString(R.string.author) },
|
||||
getItem(artists, { it.joinToString() }) { getString(R.string.artist) },
|
||||
getItem(langFlag) { getString(R.string.language) },
|
||||
getItem(lastChapterNumber) { getString(R.string.last_chapter_number) },
|
||||
getItem(rating) { getString(R.string.average_rating) },
|
||||
// getItem(users) { getString(R.string.total_ratings) },
|
||||
getItem(status) { getString(R.string.status) },
|
||||
// getItem(missing_chapters) { getString(R.string.missing_chapters) },
|
||||
getItem(followStatus) { getString(R.string.follow_status) },
|
||||
getItem(anilistId) { getString(R.string.anilist_id) },
|
||||
getItem(kitsuId) { getString(R.string.kitsu_id) },
|
||||
getItem(myAnimeListId) { getString(R.string.mal_id) },
|
||||
getItem(mangaUpdatesId) { getString(R.string.manga_updates_id) },
|
||||
getItem(animePlanetId) { getString(R.string.anime_planet_id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.Date
|
||||
|
||||
@Serializable
|
||||
class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||
var url get() = nhId?.let { BASE_URL + nhIdToPath(it) }
|
||||
set(a) {
|
||||
a?.let {
|
||||
nhId = nhUrlToId(a)
|
||||
}
|
||||
}
|
||||
|
||||
var nhId: Long? = null
|
||||
|
||||
var uploadDate: Long? = null
|
||||
|
||||
var favoritesCount: Long? = null
|
||||
|
||||
var mediaId: String? = null
|
||||
|
||||
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
||||
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
||||
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
||||
|
||||
var coverImageType: String? = null
|
||||
var pageImageTypes: List<String> = emptyList()
|
||||
var thumbnailImageType: String? = null
|
||||
|
||||
var scanlator: String? = null
|
||||
|
||||
var preferredTitle: Int? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = nhId?.let { nhIdToPath(it) }
|
||||
|
||||
val cover = if (mediaId != null) {
|
||||
typeToExtension(coverImageType)?.let {
|
||||
"https://t.nhentai.net/galleries/$mediaId/cover.$it"
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val title = when (preferredTitle) {
|
||||
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
||||
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
||||
else -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
||||
}
|
||||
|
||||
// Set artist (if we can find one)
|
||||
val artist = tags.ofNamespace(NHENTAI_ARTIST_NAMESPACE).let { tags ->
|
||||
if (tags.isNotEmpty()) tags.joinToString(transform = { it.name }) else null
|
||||
}
|
||||
|
||||
// Copy tags -> genres
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
// Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||
// We default to completed
|
||||
var status = SManga.COMPLETED
|
||||
englishTitle?.let { t ->
|
||||
MetadataUtil.ONGOING_SUFFIX.find {
|
||||
t.endsWith(it, ignoreCase = true)
|
||||
}?.let {
|
||||
status = SManga.ONGOING
|
||||
}
|
||||
}
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
title = title,
|
||||
artist = artist ?: manga.artist,
|
||||
genre = genres,
|
||||
status = status,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(nhId) { getString(R.string.id) },
|
||||
getItem(uploadDate, { MetadataUtil.EX_DATE_FORMAT.format(Date(it * 1000)) }) { getString(R.string.date_posted) },
|
||||
getItem(favoritesCount) { getString(R.string.total_favorites) },
|
||||
getItem(mediaId) { getString(R.string.media_id) },
|
||||
getItem(japaneseTitle) { getString(R.string.japanese_title) },
|
||||
getItem(englishTitle) { getString(R.string.english_title) },
|
||||
getItem(shortTitle) { getString(R.string.short_title) },
|
||||
getItem(coverImageType) { getString(R.string.cover_image_file_type) },
|
||||
getItem(pageImageTypes.size) { getString(R.string.page_count) },
|
||||
getItem(thumbnailImageType) { getString(R.string.thumbnail_image_file_type) },
|
||||
getItem(scanlator) { getString(R.string.scanlator) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_JAPANESE = 0
|
||||
const val TITLE_TYPE_ENGLISH = 1
|
||||
const val TITLE_TYPE_SHORT = 2
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
const val BASE_URL = "https://nhentai.net"
|
||||
|
||||
private const val NHENTAI_ARTIST_NAMESPACE = "artist"
|
||||
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||
|
||||
fun typeToExtension(t: String?) =
|
||||
when (t) {
|
||||
"p" -> "png"
|
||||
"j" -> "jpg"
|
||||
"g" -> "gif"
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun nhUrlToId(url: String) =
|
||||
url.split("/").last { it.isNotBlank() }.toLong()
|
||||
|
||||
fun nhIdToPath(id: Long) = "/g/$id/"
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.RaisedTitle
|
||||
import exh.util.nullIfEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
||||
var pvId: String? = null
|
||||
|
||||
var url: String? = null
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_MAIN)
|
||||
var altTitles
|
||||
get() = titles.filter { it.type == TITLE_TYPE_ALT }.map { it.title }
|
||||
set(value) {
|
||||
titles.removeAll { it.type == TITLE_TYPE_ALT }
|
||||
titles += value.map { RaisedTitle(it, TITLE_TYPE_ALT) }
|
||||
}
|
||||
|
||||
var artist: String? = null
|
||||
|
||||
var genre: String? = null
|
||||
|
||||
var rating: Float? = null
|
||||
|
||||
var status: String? = null
|
||||
|
||||
var lang: String? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = url
|
||||
val cover = thumbnailUrl
|
||||
|
||||
val title = title
|
||||
|
||||
val artist = artist
|
||||
|
||||
val status = when (status) {
|
||||
"Ongoing" -> SManga.ONGOING
|
||||
"Completed", "Suspended" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
// Copy tags -> genres
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
title = title ?: manga.title,
|
||||
artist = artist ?: manga.artist,
|
||||
status = status,
|
||||
genre = genres,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(pvId) { getString(R.string.id) },
|
||||
getItem(url) { getString(R.string.url) },
|
||||
getItem(thumbnailUrl) { getString(R.string.thumbnail_url) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(altTitles.nullIfEmpty(), { it.joinToString() }) { getString(R.string.alt_titles) },
|
||||
getItem(artist) { getString(R.string.artist) },
|
||||
getItem(genre) { getString(R.string.genre) },
|
||||
getItem(rating) { getString(R.string.average_rating) },
|
||||
getItem(status) { getString(R.string.status) },
|
||||
getItem(lang) { getString(R.string.language) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
private const val TITLE_TYPE_ALT = 1
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
private fun splitGalleryUrl(url: String) =
|
||||
url.toUri().pathSegments.filterNot(String::isNullOrBlank)
|
||||
|
||||
fun pvIdFromUrl(url: String): String = splitGalleryUrl(url).last()
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class PururinSearchMetadata : RaisedSearchMetadata() {
|
||||
var prId: Int? = null
|
||||
|
||||
var prShortLink: String? = null
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_TITLE)
|
||||
var altTitle by titleDelegate(TITLE_TYPE_ALT_TITLE)
|
||||
|
||||
var thumbnailUrl: String? = null
|
||||
|
||||
var uploaderDisp: String? = null
|
||||
|
||||
var pages: Int? = null
|
||||
|
||||
var fileSize: String? = null
|
||||
|
||||
var ratingCount: Int? = null
|
||||
var averageRating: Double? = null
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = prId?.let { prId ->
|
||||
prShortLink?.let { prShortLink ->
|
||||
"/gallery/$prId/$prShortLink"
|
||||
}
|
||||
}
|
||||
|
||||
val title = title ?: altTitle
|
||||
|
||||
val cover = thumbnailUrl
|
||||
|
||||
val artist = tags.ofNamespace(TAG_NAMESPACE_ARTIST).joinToString { it.name }
|
||||
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
title = title ?: manga.title,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
artist = artist,
|
||||
genre = genres,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(prId) { getString(R.string.id) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(altTitle) { getString(R.string.alt_title) },
|
||||
getItem(thumbnailUrl) { getString(R.string.thumbnail_url) },
|
||||
getItem(uploaderDisp) { getString(R.string.uploader_capital) },
|
||||
getItem(uploader) { getString(R.string.uploader) },
|
||||
getItem(pages) { getString(R.string.page_count) },
|
||||
getItem(fileSize) { getString(R.string.gallery_size) },
|
||||
getItem(ratingCount) { getString(R.string.total_ratings) },
|
||||
getItem(averageRating) { getString(R.string.average_rating) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_TITLE = 0
|
||||
private const val TITLE_TYPE_ALT_TITLE = 1
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
private const val TAG_NAMESPACE_ARTIST = "artist"
|
||||
const val TAG_NAMESPACE_CATEGORY = "category"
|
||||
|
||||
const val BASE_URL = "https://pururin.io"
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package exh.metadata.metadata
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.util.nullIfEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Serializable
|
||||
class TsuminoSearchMetadata : RaisedSearchMetadata() {
|
||||
var tmId: Int? = null
|
||||
|
||||
var title by titleDelegate(TITLE_TYPE_MAIN)
|
||||
|
||||
var artist: String? = null
|
||||
|
||||
var uploadDate: Long? = null
|
||||
|
||||
var length: Int? = null
|
||||
|
||||
var ratingString: String? = null
|
||||
|
||||
var averageRating: Float? = null
|
||||
|
||||
var userRatings: Long? = null
|
||||
|
||||
var favorites: Long? = null
|
||||
|
||||
var category: String? = null
|
||||
|
||||
var collection: String? = null
|
||||
|
||||
var group: String? = null
|
||||
|
||||
var parody: List<String> = emptyList()
|
||||
|
||||
var character: List<String> = emptyList()
|
||||
|
||||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val title = title
|
||||
val cover = tmId?.let { BASE_URL.replace("www", "content") + thumbUrlFromId(it.toString()) }
|
||||
|
||||
val artist = artist
|
||||
|
||||
val status = SManga.UNKNOWN
|
||||
|
||||
// Copy tags -> genres
|
||||
val genres = tagsToGenreString()
|
||||
|
||||
val description = "meta"
|
||||
|
||||
return manga.copy(
|
||||
title = title ?: manga.title,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
artist = artist ?: manga.artist,
|
||||
status = status,
|
||||
genre = genres,
|
||||
description = description,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||
return with(context) {
|
||||
listOfNotNull(
|
||||
getItem(tmId) { getString(R.string.id) },
|
||||
getItem(title) { getString(R.string.title) },
|
||||
getItem(uploader) { getString(R.string.uploader) },
|
||||
getItem(uploadDate, { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) }) { getString(R.string.date_posted) },
|
||||
getItem(length) { getString(R.string.page_count) },
|
||||
getItem(ratingString) { getString(R.string.rating_string) },
|
||||
getItem(averageRating) { getString(R.string.average_rating) },
|
||||
getItem(userRatings) { getString(R.string.total_ratings) },
|
||||
getItem(favorites) { getString(R.string.total_favorites) },
|
||||
getItem(category) { getString(R.string.genre) },
|
||||
getItem(collection) { getString(R.string.collection) },
|
||||
getItem(group) { getString(R.string.group) },
|
||||
getItem(parody.nullIfEmpty(), { it.joinToString() }) { getString(R.string.parodies) },
|
||||
getItem(character.nullIfEmpty(), { it.joinToString() }) { getString(R.string.characters) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TITLE_TYPE_MAIN = 0
|
||||
|
||||
const val TAG_TYPE_DEFAULT = 0
|
||||
|
||||
val BASE_URL = "https://www.tsumino.com"
|
||||
|
||||
val TSUMINO_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
|
||||
fun tmIdFromUrl(url: String) = url.toUri().lastPathSegment
|
||||
|
||||
fun thumbUrlFromId(id: String) = "/thumbs/$id/1"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package exh.metadata.metadata.base
|
||||
|
||||
import exh.metadata.sql.models.SearchMetadata
|
||||
import exh.metadata.sql.models.SearchTag
|
||||
import exh.metadata.sql.models.SearchTitle
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.serializer
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Serializable
|
||||
data class FlatMetadata(
|
||||
val metadata: SearchMetadata,
|
||||
val tags: List<SearchTag>,
|
||||
val titles: List<SearchTitle>,
|
||||
) {
|
||||
inline fun <reified T : RaisedSearchMetadata> raise(): T = raise(T::class)
|
||||
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>): T =
|
||||
RaisedSearchMetadata.raiseFlattenJson
|
||||
.decodeFromString(clazz.serializer(), metadata.extra).apply {
|
||||
fillBaseFields(this@FlatMetadata)
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
package exh.metadata.metadata.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.metadata.metadata.EightMusesSearchMetadata
|
||||
import exh.metadata.metadata.HBrowseSearchMetadata
|
||||
import exh.metadata.metadata.HitomiSearchMetadata
|
||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||
import exh.metadata.metadata.NHentaiSearchMetadata
|
||||
import exh.metadata.metadata.PervEdenSearchMetadata
|
||||
import exh.metadata.metadata.PururinSearchMetadata
|
||||
import exh.metadata.metadata.TsuminoSearchMetadata
|
||||
import exh.metadata.sql.models.SearchMetadata
|
||||
import exh.metadata.sql.models.SearchTag
|
||||
import exh.metadata.sql.models.SearchTitle
|
||||
import exh.util.plusAssign
|
||||
import kotlinx.serialization.Polymorphic
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import kotlinx.serialization.modules.subclass
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@Polymorphic
|
||||
@Serializable
|
||||
abstract class RaisedSearchMetadata {
|
||||
@Transient
|
||||
var mangaId: Long = -1
|
||||
|
||||
@Transient
|
||||
var uploader: String? = null
|
||||
|
||||
@Transient
|
||||
protected open var indexedExtra: String? = null
|
||||
|
||||
@Transient
|
||||
val tags = mutableListOf<RaisedTag>()
|
||||
|
||||
@Transient
|
||||
val titles = mutableListOf<RaisedTitle>()
|
||||
|
||||
fun getTitleOfType(type: Int): String? = titles.find { it.type == type }?.title
|
||||
|
||||
fun replaceTitleOfType(type: Int, newTitle: String?) {
|
||||
titles.removeAll { it.type == type }
|
||||
if (newTitle != null) titles += RaisedTitle(newTitle, type)
|
||||
}
|
||||
|
||||
fun <T : Any> getItem(
|
||||
item: T?,
|
||||
toString: (T) -> String = Any::toString,
|
||||
block: (T) -> String,
|
||||
): Pair<String, String>? {
|
||||
item ?: return null
|
||||
return block(item) to toString(item)
|
||||
}
|
||||
|
||||
open fun copyTo(manga: SManga) {
|
||||
val infoManga = createMangaInfo(manga.copy())
|
||||
manga.copyFrom(infoManga)
|
||||
}
|
||||
|
||||
abstract fun createMangaInfo(manga: SManga): SManga
|
||||
|
||||
fun tagsToGenreString() = tags.toGenreString()
|
||||
|
||||
fun tagsToGenreList() = tags.toGenreList()
|
||||
|
||||
fun tagsToDescription() =
|
||||
StringBuilder("Tags:\n").apply {
|
||||
// BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
||||
val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy {
|
||||
it.namespace
|
||||
}.entries
|
||||
|
||||
groupedTags.forEach { (namespace, tags) ->
|
||||
if (tags.isNotEmpty()) {
|
||||
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
||||
if (namespace != null) {
|
||||
this += "▪ "
|
||||
this += namespace
|
||||
this += ": "
|
||||
}
|
||||
this += joinedTags
|
||||
this += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<RaisedTag>.ofNamespace(ns: String): List<RaisedTag> {
|
||||
return filter { it.namespace == ns }
|
||||
}
|
||||
|
||||
fun flatten(): FlatMetadata {
|
||||
require(mangaId != -1L)
|
||||
|
||||
val extra = raiseFlattenJson.encodeToString(this)
|
||||
return FlatMetadata(
|
||||
SearchMetadata(
|
||||
mangaId,
|
||||
uploader,
|
||||
extra,
|
||||
indexedExtra,
|
||||
0,
|
||||
),
|
||||
tags.map {
|
||||
SearchTag(
|
||||
null,
|
||||
mangaId,
|
||||
it.namespace,
|
||||
it.name,
|
||||
it.type,
|
||||
)
|
||||
},
|
||||
titles.map {
|
||||
SearchTitle(
|
||||
null,
|
||||
mangaId,
|
||||
it.title,
|
||||
it.type,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun fillBaseFields(metadata: FlatMetadata) {
|
||||
mangaId = metadata.metadata.mangaId
|
||||
uploader = metadata.metadata.uploader
|
||||
indexedExtra = metadata.metadata.indexedExtra
|
||||
|
||||
this.tags.clear()
|
||||
this.tags += metadata.tags.map {
|
||||
RaisedTag(it.namespace, it.name, it.type)
|
||||
}
|
||||
|
||||
this.titles.clear()
|
||||
this.titles += metadata.titles.map {
|
||||
RaisedTitle(it.title, it.type)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getExtraInfoPairs(context: Context): List<Pair<String, String>>
|
||||
|
||||
companion object {
|
||||
// Virtual tags allow searching of otherwise unindexed fields
|
||||
const val TAG_TYPE_VIRTUAL = -2
|
||||
|
||||
fun MutableList<RaisedTag>.toGenreString() =
|
||||
this.filter { it.type != TAG_TYPE_VIRTUAL }
|
||||
.joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
|
||||
|
||||
fun MutableList<RaisedTag>.toGenreList() =
|
||||
this.filter { it.type != TAG_TYPE_VIRTUAL }
|
||||
.map { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
|
||||
|
||||
private val module = SerializersModule {
|
||||
polymorphic(RaisedSearchMetadata::class) {
|
||||
subclass(EHentaiSearchMetadata::class)
|
||||
subclass(EightMusesSearchMetadata::class)
|
||||
subclass(HBrowseSearchMetadata::class)
|
||||
subclass(HitomiSearchMetadata::class)
|
||||
subclass(MangaDexSearchMetadata::class)
|
||||
subclass(NHentaiSearchMetadata::class)
|
||||
subclass(PervEdenSearchMetadata::class)
|
||||
subclass(PururinSearchMetadata::class)
|
||||
subclass(TsuminoSearchMetadata::class)
|
||||
}
|
||||
}
|
||||
|
||||
val raiseFlattenJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
serializersModule = module
|
||||
}
|
||||
|
||||
fun titleDelegate(type: Int) = object : ReadWriteProperty<RaisedSearchMetadata, String?> {
|
||||
/**
|
||||
* Returns the value of the property for the given object.
|
||||
* @param thisRef the object for which the value is requested.
|
||||
* @param property the metadata for the property.
|
||||
* @return the property value.
|
||||
*/
|
||||
override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) =
|
||||
thisRef.getTitleOfType(type)
|
||||
|
||||
/**
|
||||
* Sets the value of the property for the given object.
|
||||
* @param thisRef the object for which the value is requested.
|
||||
* @param property the metadata for the property.
|
||||
* @param value the value to set.
|
||||
*/
|
||||
override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) =
|
||||
thisRef.replaceTitleOfType(type, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package exh.metadata.metadata.base
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RaisedTag(
|
||||
val namespace: String?,
|
||||
val name: String,
|
||||
val type: Int,
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package exh.metadata.metadata.base
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RaisedTitle(
|
||||
val title: String,
|
||||
val type: Int = 0,
|
||||
)
|
||||
@@ -1,26 +0,0 @@
|
||||
package exh.metadata.sql.models
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SearchMetadata(
|
||||
// Manga ID this gallery is linked to
|
||||
val mangaId: Long,
|
||||
|
||||
// Gallery uploader
|
||||
val uploader: String?,
|
||||
|
||||
// Extra data attached to this metadata, in JSON format
|
||||
val extra: String,
|
||||
|
||||
// Indexed extra data attached to this metadata
|
||||
val indexedExtra: String?,
|
||||
|
||||
// The version of this metadata's extra. Used to track changes to the 'extra' field's schema
|
||||
val extraVersion: Int,
|
||||
) {
|
||||
// Transient information attached to this piece of metadata, useful for caching
|
||||
|
||||
var transientCache: Map<String, @Contextual Any>? = null
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package exh.metadata.sql.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SearchTag(
|
||||
// Tag identifier, unique
|
||||
val id: Long?,
|
||||
|
||||
// Metadata this tag is attached to
|
||||
val mangaId: Long,
|
||||
|
||||
// Tag namespace
|
||||
val namespace: String?,
|
||||
|
||||
// Tag name
|
||||
val name: String,
|
||||
|
||||
// Tag type
|
||||
val type: Int,
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package exh.metadata.sql.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SearchTitle(
|
||||
// Title identifier, unique
|
||||
val id: Long?,
|
||||
|
||||
// Metadata this title is attached to
|
||||
val mangaId: Long,
|
||||
|
||||
// Title
|
||||
val title: String,
|
||||
|
||||
// Title type, useful for distinguishing between main/alt titles
|
||||
val type: Int,
|
||||
)
|
||||
@@ -1,297 +0,0 @@
|
||||
package exh.source
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
|
||||
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
|
||||
abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
||||
/**
|
||||
* Returns the request for the popular manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun popularMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for the search manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun searchMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for latest manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun latestUpdatesParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the details of a manga.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun mangaDetailsParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of chapters.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun chapterListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of pages.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun pageListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the absolute url to the source image.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun imageUrlParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||
*/
|
||||
override val baseUrl get() = delegate.baseUrl
|
||||
|
||||
/**
|
||||
* Headers used for requests.
|
||||
*/
|
||||
override val headers get() = delegate.headers
|
||||
|
||||
/**
|
||||
* Whether the source has support for latest updates.
|
||||
*/
|
||||
override val supportsLatest get() = delegate.supportsLatest
|
||||
|
||||
/**
|
||||
* Name of the source.
|
||||
*/
|
||||
final override val name get() = delegate.name
|
||||
|
||||
// ===> OPTIONAL FIELDS
|
||||
|
||||
/**
|
||||
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
||||
* of the MD5 of the string: sourcename/language/versionId
|
||||
* Note the generated id sets the sign bit to 0.
|
||||
*/
|
||||
override val id get() = delegate.id
|
||||
|
||||
/**
|
||||
* Default network client for doing requests.
|
||||
*/
|
||||
final override val client get() = delegate.client
|
||||
|
||||
/**
|
||||
* You must NEVER call super.client if you override this!
|
||||
*/
|
||||
open val baseHttpClient: OkHttpClient? = null
|
||||
open val networkHttpClient: OkHttpClient get() = network.client
|
||||
open val networkCloudflareClient: OkHttpClient get() = network.cloudflareClient
|
||||
|
||||
/**
|
||||
* Visible name of the source.
|
||||
*/
|
||||
override fun toString() = delegate.toString()
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchPopularManga(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of latest manga updates.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchLatestUpdates(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchMangaDetails(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get the updated details for a manga.
|
||||
*/
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getMangaDetails(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request for the details of a manga. Override only if it's needed to change the
|
||||
* url, send different headers or request method like POST.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to
|
||||
* override this method. If a manga is licensed an empty chapter list observable is returned
|
||||
*
|
||||
* @param manga the manga to look for chapters.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchChapterList(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get all the available chapters for a manga.
|
||||
*/
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getChapterList(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the page list for a chapter.
|
||||
*
|
||||
* @param chapter the chapter whose page list has to be fetched.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchPageList(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get the list of pages a chapter has.
|
||||
*/
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getPageList(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the page containing the source url of the image. If there's any
|
||||
* error, it will return null instead of throwing an exception.
|
||||
*
|
||||
* @param page the page whose source image has to be fetched.
|
||||
*/
|
||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchImageUrl(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the response of the source image.
|
||||
*
|
||||
* @param page the page whose source image has to be downloaded.
|
||||
*/
|
||||
override fun fetchImage(page: Page): Observable<Response> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchImage(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before inserting a new chapter into database. Use it if you need to override chapter
|
||||
* fields, like the title or the chapter number. Do not change anything to [manga].
|
||||
*
|
||||
* @param chapter the chapter to be added.
|
||||
* @param manga the manga of the chapter.
|
||||
*/
|
||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.prepareNewChapter(chapter, manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of filters for the source.
|
||||
*/
|
||||
override fun getFilterList() = delegate.getFilterList()
|
||||
|
||||
protected open fun ensureDelegateCompatible() {
|
||||
if (versionId != delegate.versionId || lang != delegate.lang) {
|
||||
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
||||
}
|
||||
}
|
||||
|
||||
class IncompatibleDelegateException(message: String) : RuntimeException(message)
|
||||
|
||||
init {
|
||||
delegate.bindDelegate(this)
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package exh.source
|
||||
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
|
||||
class EnhancedHttpSource(
|
||||
val originalSource: HttpSource,
|
||||
val enhancedSource: HttpSource,
|
||||
) : HttpSource() {
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* Returns the request for the popular manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun popularMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for the search manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun searchMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for latest manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun latestUpdatesParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the details of a manga.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun mangaDetailsParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of chapters.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun chapterListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of pages.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun pageListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the absolute url to the source image.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun imageUrlParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||
*/
|
||||
override val baseUrl get() = source().baseUrl
|
||||
|
||||
/**
|
||||
* Headers used for requests.
|
||||
*/
|
||||
override val headers get() = source().headers
|
||||
|
||||
/**
|
||||
* Whether the source has support for latest updates.
|
||||
*/
|
||||
override val supportsLatest get() = source().supportsLatest
|
||||
|
||||
/**
|
||||
* Name of the source.
|
||||
*/
|
||||
override val name get() = source().name
|
||||
|
||||
/**
|
||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||
*/
|
||||
override val lang get() = source().lang
|
||||
|
||||
// ===> OPTIONAL FIELDS
|
||||
|
||||
/**
|
||||
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
||||
* of the MD5 of the string: sourcename/language/versionId
|
||||
* Note the generated id sets the sign bit to 0.
|
||||
*/
|
||||
override val id get() = source().id
|
||||
|
||||
/**
|
||||
* Default network client for doing requests.
|
||||
*/
|
||||
override val client get() = originalSource.client // source().client
|
||||
|
||||
/**
|
||||
* Visible name of the source.
|
||||
*/
|
||||
override fun toString() = source().toString()
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun fetchPopularManga(page: Int) = source().fetchPopularManga(page)
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
source().fetchSearchManga(page, query, filters)
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of latest manga updates.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun fetchLatestUpdates(page: Int) = source().fetchLatestUpdates(page)
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
|
||||
override fun fetchMangaDetails(manga: SManga) = source().fetchMangaDetails(manga)
|
||||
|
||||
/**
|
||||
* [1.x API] Get the updated details for a manga.
|
||||
*/
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga = source().getMangaDetails(manga)
|
||||
|
||||
/**
|
||||
* Returns the request for the details of a manga. Override only if it's needed to change the
|
||||
* url, send different headers or request method like POST.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
override fun mangaDetailsRequest(manga: SManga) = source().mangaDetailsRequest(manga)
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to
|
||||
* override this method. If a manga is licensed an empty chapter list observable is returned
|
||||
*
|
||||
* @param manga the manga to look for chapters.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
|
||||
override fun fetchChapterList(manga: SManga) = source().fetchChapterList(manga)
|
||||
|
||||
/**
|
||||
* [1.x API] Get all the available chapters for a manga.
|
||||
*/
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> = source().getChapterList(manga)
|
||||
|
||||
/**
|
||||
* Returns an observable with the page list for a chapter.
|
||||
*
|
||||
* @param chapter the chapter whose page list has to be fetched.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
|
||||
override fun fetchPageList(chapter: SChapter) = source().fetchPageList(chapter)
|
||||
|
||||
/**
|
||||
* [1.x API] Get the list of pages a chapter has.
|
||||
*/
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> = source().getPageList(chapter)
|
||||
|
||||
/**
|
||||
* Returns an observable with the page containing the source url of the image. If there's any
|
||||
* error, it will return null instead of throwing an exception.
|
||||
*
|
||||
* @param page the page whose source image has to be fetched.
|
||||
*/
|
||||
override fun fetchImageUrl(page: Page) = source().fetchImageUrl(page)
|
||||
|
||||
/**
|
||||
* Returns an observable with the response of the source image.
|
||||
*
|
||||
* @param page the page whose source image has to be downloaded.
|
||||
*/
|
||||
override fun fetchImage(page: Page) = source().fetchImage(page)
|
||||
|
||||
/**
|
||||
* Called before inserting a new chapter into database. Use it if you need to override chapter
|
||||
* fields, like the title or the chapter number. Do not change anything to [manga].
|
||||
*
|
||||
* @param chapter the chapter to be added.
|
||||
* @param manga the manga of the chapter.
|
||||
*/
|
||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) =
|
||||
source().prepareNewChapter(chapter, manga)
|
||||
|
||||
/**
|
||||
* Returns the list of filters for the source.
|
||||
*/
|
||||
override fun getFilterList() = source().getFilterList()
|
||||
|
||||
fun source(): HttpSource {
|
||||
return if (prefs.delegateSources().get()) {
|
||||
enhancedSource
|
||||
} else {
|
||||
originalSource
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterEhBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
|
||||
@Composable
|
||||
fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
|
||||
@@ -29,7 +29,7 @@ fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
val binding = DescriptionAdapterEhBinding.bind(it)
|
||||
|
||||
binding.genre.text =
|
||||
meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }
|
||||
meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }
|
||||
?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
@@ -61,7 +61,7 @@ fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
val ratingFloat = meta.averageRating?.toFloat()
|
||||
binding.ratingBar.rating = ratingFloat ?: 0F
|
||||
@SuppressLint("SetTextI18n")
|
||||
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUtil.getRatingString(context, ratingFloat?.times(2))
|
||||
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
||||
|
||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapter8mBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.EightMusesSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
|
||||
@Composable
|
||||
fun EightMusesDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHbBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.HBrowseSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
|
||||
@Composable
|
||||
fun HBrowseDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
|
||||
|
||||
@@ -11,8 +11,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterHiBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.HitomiSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
@@ -28,7 +28,7 @@ fun HitomiDescription(state: MangaScreenState.Success, openMetadataViewer: () ->
|
||||
if (meta == null || meta !is HitomiSearchMetadata) return@AndroidView
|
||||
val binding = DescriptionAdapterHiBinding.bind(it)
|
||||
|
||||
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.text = meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
} ?: meta.genre ?: context.getString(R.string.unknown)
|
||||
|
||||
@@ -12,9 +12,9 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapterMdBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil.getRatingString
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.getRatingString
|
||||
import kotlin.math.round
|
||||
|
||||
@Composable
|
||||
|
||||
Executable → Regular
+9
-64
@@ -1,72 +1,17 @@
|
||||
package exh.metadata
|
||||
package exh.ui.metadata.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.R
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import exh.util.SourceTagsUtil
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlin.math.ln
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Metadata utils
|
||||
*/
|
||||
object MetadataUtil {
|
||||
fun humanReadableByteCount(bytes: Long, si: Boolean): String {
|
||||
val unit = if (si) 1000 else 1024
|
||||
if (bytes < unit) return "$bytes B"
|
||||
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
|
||||
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
|
||||
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
|
||||
}
|
||||
|
||||
private const val KB_FACTOR: Long = 1000
|
||||
private const val KIB_FACTOR: Long = 1024
|
||||
private const val MB_FACTOR = 1000 * KB_FACTOR
|
||||
private const val MIB_FACTOR = 1024 * KIB_FACTOR
|
||||
private const val GB_FACTOR = 1000 * MB_FACTOR
|
||||
private const val GIB_FACTOR = 1024 * MIB_FACTOR
|
||||
|
||||
fun parseHumanReadableByteCount(bytes: String): Double? {
|
||||
val ret = bytes.substringBefore(' ').toDouble()
|
||||
return when (bytes.substringAfter(' ')) {
|
||||
"GB" -> ret * GB_FACTOR
|
||||
"GiB" -> ret * GIB_FACTOR
|
||||
"MB" -> ret * MB_FACTOR
|
||||
"MiB" -> ret * MIB_FACTOR
|
||||
"KB" -> ret * KB_FACTOR
|
||||
"KiB" -> ret * KIB_FACTOR
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val ONGOING_SUFFIX = arrayOf(
|
||||
"[ongoing]",
|
||||
"(ongoing)",
|
||||
"{ongoing}",
|
||||
"<ongoing>",
|
||||
"ongoing",
|
||||
"[incomplete]",
|
||||
"(incomplete)",
|
||||
"{incomplete}",
|
||||
"<incomplete>",
|
||||
"incomplete",
|
||||
"[wip]",
|
||||
"(wip)",
|
||||
"{wip}",
|
||||
"<wip>",
|
||||
"wip",
|
||||
)
|
||||
|
||||
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
||||
|
||||
object MetadataUIUtil {
|
||||
fun getRatingString(context: Context, @FloatRange(from = 0.0, to = 10.0) rating: Float? = null) = when (rating?.roundToInt()) {
|
||||
0 -> R.string.rating0
|
||||
1 -> R.string.rating1
|
||||
@@ -103,12 +48,12 @@ object MetadataUtil {
|
||||
}?.let { (genreColor, stringId) ->
|
||||
genreColor.color to context.getString(stringId)
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.bindDrawable(context: Context, @DrawableRes drawable: Int) {
|
||||
ContextCompat.getDrawable(context, drawable)?.apply {
|
||||
setTint(context.getResourceColor(R.attr.colorAccent))
|
||||
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
|
||||
setCompoundDrawables(this, null, null, null)
|
||||
fun TextView.bindDrawable(context: Context, @DrawableRes drawable: Int) {
|
||||
ContextCompat.getDrawable(context, drawable)?.apply {
|
||||
setTint(context.getResourceColor(R.attr.colorAccent))
|
||||
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
|
||||
setCompoundDrawables(this, null, null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterNhBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.NHentaiSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
@@ -32,7 +32,7 @@ fun NHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
binding.genre.text = meta.tags.filter { it.namespace == NHentaiSearchMetadata.NHENTAI_CATEGORIES_NAMESPACE }.let { tags ->
|
||||
if (tags.isNotEmpty()) tags.joinToString(transform = { it.name }) else null
|
||||
}.let { categoriesString ->
|
||||
categoriesString?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
|
||||
categoriesString?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
} ?: categoriesString ?: context.getString(R.string.unknown)
|
||||
|
||||
@@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPeBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.PervEdenSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import java.util.Locale
|
||||
import kotlin.math.round
|
||||
|
||||
@@ -30,7 +29,7 @@ fun PervEdenDescription(state: MangaScreenState.Success, openMetadataViewer: ()
|
||||
if (meta == null || meta !is PervEdenSearchMetadata) return@AndroidView
|
||||
val binding = DescriptionAdapterPeBinding.bind(it)
|
||||
|
||||
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.text = meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
} ?: meta.genre ?: context.getString(R.string.unknown)
|
||||
@@ -45,7 +44,7 @@ fun PervEdenDescription(state: MangaScreenState.Success, openMetadataViewer: ()
|
||||
|
||||
binding.ratingBar.rating = meta.rating ?: 0F
|
||||
@SuppressLint("SetTextI18n")
|
||||
binding.rating.text = (round((meta.rating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, meta.rating?.times(2))
|
||||
binding.rating.text = (round((meta.rating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.rating?.times(2))
|
||||
|
||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPuBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.PururinSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import kotlin.math.round
|
||||
|
||||
@Composable
|
||||
@@ -30,7 +29,7 @@ fun PururinDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
val binding = DescriptionAdapterPuBinding.bind(it)
|
||||
|
||||
binding.genre.text = meta.tags.find { it.namespace == PururinSearchMetadata.TAG_NAMESPACE_CATEGORY }.let { genre ->
|
||||
genre?.let { MetadataUtil.getGenreAndColour(context, it.name) }?.let {
|
||||
genre?.let { MetadataUIUtil.getGenreAndColour(context, it.name) }?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
} ?: genre?.name ?: context.getString(R.string.unknown)
|
||||
@@ -47,7 +46,7 @@ fun PururinDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
val ratingFloat = meta.averageRating?.toFloat()
|
||||
binding.ratingBar.rating = ratingFloat ?: 0F
|
||||
@SuppressLint("SetTextI18n")
|
||||
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, ratingFloat?.times(2))
|
||||
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
||||
|
||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.DescriptionAdapterTsBinding
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.metadata.bindDrawable
|
||||
import exh.metadata.metadata.TsuminoSearchMetadata
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
|
||||
import java.util.Date
|
||||
import kotlin.math.round
|
||||
|
||||
@@ -30,7 +29,7 @@ fun TsuminoDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
if (meta == null || meta !is TsuminoSearchMetadata) return@AndroidView
|
||||
val binding = DescriptionAdapterTsBinding.bind(it)
|
||||
|
||||
binding.genre.text = meta.category?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.text = meta.category?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
|
||||
binding.genre.setBackgroundColor(it.first)
|
||||
it.second
|
||||
} ?: meta.category ?: context.getString(R.string.unknown)
|
||||
@@ -47,7 +46,7 @@ fun TsuminoDescription(state: MangaScreenState.Success, openMetadataViewer: () -
|
||||
|
||||
binding.ratingBar.rating = meta.averageRating ?: 0F
|
||||
@SuppressLint("SetTextI18n")
|
||||
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, meta.averageRating?.times(2))
|
||||
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.averageRating?.times(2))
|
||||
|
||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package exh.util
|
||||
|
||||
fun <C : Collection<R>, R> C.nullIfEmpty() = ifEmpty { null }
|
||||
@@ -34,12 +34,12 @@ object SourceTagsUtil {
|
||||
}
|
||||
if (parsed?.namespace != null) {
|
||||
when (sourceId) {
|
||||
in hitomiSourceIds -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
in nHentaiSourceIds -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
in hitomiSourceIds -> wrapTagHitomi(parsed.namespace!!, parsed.name.substringBefore('|').trim())
|
||||
in nHentaiSourceIds -> wrapTagNHentai(parsed.namespace!!, parsed.name.substringBefore('|').trim())
|
||||
in mangaDexSourceIds -> parsed.name
|
||||
PURURIN_SOURCE_ID -> parsed.name.substringBefore('|').trim()
|
||||
TSUMINO_SOURCE_ID -> wrapTagTsumino(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
else -> wrapTag(parsed.namespace, parsed.name.substringBefore('|').trim())
|
||||
TSUMINO_SOURCE_ID -> wrapTagTsumino(parsed.namespace!!, parsed.name.substringBefore('|').trim())
|
||||
else -> wrapTag(parsed.namespace!!, parsed.name.substringBefore('|').trim())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package exh.util
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
fun String.capitalize(locale: Locale = Locale.getDefault()) =
|
||||
replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
|
||||
@@ -1,3 +0,0 @@
|
||||
package exh.util
|
||||
|
||||
operator fun StringBuilder.plusAssign(other: String) { append(other) }
|
||||
@@ -1,15 +0,0 @@
|
||||
package exh.util
|
||||
|
||||
fun Collection<String>.trimAll() = map { it.trim() }
|
||||
fun Collection<String>.dropBlank() = filter { it.isNotBlank() }
|
||||
fun Collection<String>.dropEmpty() = filter { it.isNotEmpty() }
|
||||
|
||||
private val articleRegex by lazy { "^(an|a|the) ".toRegex(RegexOption.IGNORE_CASE) }
|
||||
|
||||
fun String.removeArticles(): String {
|
||||
return replace(articleRegex, "")
|
||||
}
|
||||
|
||||
fun String.trimOrNull() = trim().nullIfBlank()
|
||||
|
||||
fun String.nullIfBlank(): String? = ifBlank { null }
|
||||
Reference in New Issue
Block a user