built-in EH compatible with extension (#613)

* switch off integrated H to show E-H source & extension

* More languages for E-H/ExH, also more popular with rate 5

* Don't hide Multi source when toggle Integrated H

* Avoid crash trying to load ehentaiBrowseDisplayMode

* change source ID to matching general extension

* migration old source ID to new source ID

* Allow importing of EHentai extension backups

* limit library update & metadata source to only Multi lang

* update app version

* fix spotless

* also migrate saved search/feed & pinned EH/EXH source ID when restore from backup or update app
This commit is contained in:
Cuong-Tran 2025-01-26 02:20:06 +07:00 committed by GitHub
parent 747cd48abc
commit 2e744ed5e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 386 additions and 116 deletions

View file

@ -30,8 +30,8 @@ android {
defaultConfig { defaultConfig {
applicationId = "app.komikku" applicationId = "app.komikku"
versionCode = 71 versionCode = 72
versionName = "1.12.2" versionName = "1.13.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")

View file

@ -25,7 +25,7 @@ class SourcePreferences(
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet()) fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet()) fun pinnedSources() = preferenceStore.getStringSet(pinnedSourcesPrefKey, emptySet())
fun lastUsedSource() = preferenceStore.getLong( fun lastUsedSource() = preferenceStore.getLong(
Preference.appStateKey("last_catalogue_source"), Preference.appStateKey("last_catalogue_source"),
@ -109,3 +109,5 @@ class SourcePreferences(
fun relatedMangas() = preferenceStore.getBoolean("related_mangas", true) fun relatedMangas() = preferenceStore.getBoolean("related_mangas", true)
// KMK <-- // KMK <--
} }
const val pinnedSourcesPrefKey = "pinned_catalogues"

View file

@ -31,8 +31,8 @@ import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedSource
import exh.util.SourceTagsUtil import exh.util.SourceTagsUtil
import androidx.compose.material3.SuggestionChipDefaults as SuggestionChipDefaultsM3 import androidx.compose.material3.SuggestionChipDefaults as SuggestionChipDefaultsM3
@ -64,7 +64,7 @@ value class SearchMetadataChips(
} else { } else {
SourceTagsUtil.getWrappedTag(source.id, fullTag = it.name) SourceTagsUtil.getWrappedTag(source.id, fullTag = it.name)
} ?: it.name, } ?: it.name,
border = if (source.id == EXH_SOURCE_ID || source.id == EH_SOURCE_ID) { border = if (source.isEhBasedSource()) {
when (it.type) { when (it.type) {
EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2 EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2
EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1 EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1

View file

@ -72,9 +72,6 @@ import eu.kanade.tachiyomi.util.system.toast
import exh.debug.SettingsDebugScreen import exh.debug.SettingsDebugScreen
import exh.log.EHLogLevel import exh.log.EHLogLevel
import exh.pref.DelegateSourcePreferences import exh.pref.DelegateSourcePreferences
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.util.toAnnotatedString import exh.util.toAnnotatedString
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
@ -740,16 +737,17 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(SYMR.strings.toggle_hentai_features), title = stringResource(SYMR.strings.toggle_hentai_features),
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary), subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
onValueChanged = { onValueChanged = {
if (it) {
BlacklistedSources.HIDDEN_SOURCES += EH_SOURCE_ID
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
} else {
BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID
BlacklistedSources.HIDDEN_SOURCES -= EXH_SOURCE_ID
}
true true
}, },
), ),
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.enableSourceBlacklist(),
title = stringResource(SYMR.strings.enable_source_blacklist),
subtitle = stringResource(
SYMR.strings.enable_source_blacklist_summary,
stringResource(MR.strings.app_name),
),
),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = delegateSourcePreferences.delegateSources(), pref = delegateSourcePreferences.delegateSources(),
title = stringResource(SYMR.strings.toggle_delegated_sources), title = stringResource(SYMR.strings.toggle_delegated_sources),
@ -770,14 +768,6 @@ object SettingsAdvancedScreen : SearchableSettings {
})" })"
}.toMap().toImmutableMap(), }.toMap().toImmutableMap(),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.enableSourceBlacklist(),
title = stringResource(SYMR.strings.enable_source_blacklist),
subtitle = stringResource(
SYMR.strings.enable_source_blacklist_summary,
stringResource(MR.strings.app_name),
),
),
kotlin.run { kotlin.run {
var enableEncryptDatabase by rememberSaveable { mutableStateOf(false) } var enableEncryptDatabase by rememberSaveable { mutableStateOf(false) }

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupFeed import eu.kanade.tachiyomi.data.backup.models.BackupFeed
import exh.EXHMigrations
import exh.util.nullIfBlank import exh.util.nullIfBlank
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -20,7 +21,11 @@ class FeedRestorer(
saved_searchQueries.selectAll() saved_searchQueries.selectAll()
} }
backupFeeds.filter { backupFeed -> backupFeeds.map {
// KMK -->
EXHMigrations.migrateBackupFeed(it)
// KMK <--
}.filter { backupFeed ->
// Filter out source's global Popular/Latest feed already existed // Filter out source's global Popular/Latest feed already existed
backupFeed.savedSearch == null && backupFeed.savedSearch == null &&
currentFeeds.none { currentFeed -> currentFeeds.none { currentFeed ->

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers package eu.kanade.tachiyomi.data.backup.restore.restorers
import android.content.Context import android.content.Context
import eu.kanade.domain.source.service.pinnedSourcesPrefKey
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
@ -12,6 +13,7 @@ import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.source.sourcePreferences import eu.kanade.tachiyomi.source.sourcePreferences
import exh.EXHMigrations
import tachiyomi.core.common.preference.AndroidPreferenceStore import tachiyomi.core.common.preference.AndroidPreferenceStore
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -70,7 +72,15 @@ class PreferenceRestorer(
} }
is StringSetPreferenceValue -> { is StringSetPreferenceValue -> {
if (prefs[key] is Set<*>?) { if (prefs[key] is Set<*>?) {
preferenceStore.getStringSet(key).set(value.value) preferenceStore.getStringSet(key).set(
// KMK -->
if (key == pinnedSourcesPrefKey) {
EXHMigrations.migratePinnedSources(value.value)
} else {
// KMK <--
value.value
},
)
} }
} }
} }

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import exh.EXHMigrations
import exh.util.nullIfBlank import exh.util.nullIfBlank
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -22,7 +23,11 @@ class SavedSearchRestorer(
// KMK <-- // KMK <--
} }
backupSavedSearches.filter { backupSavedSearch -> backupSavedSearches.map {
// KMK -->
EXHMigrations.migrateBackupSavedSearch(it)
// KMK <--
}.filter { backupSavedSearch ->
currentSavedSearches.none { currentSavedSearch -> currentSavedSearches.none { currentSavedSearch ->
currentSavedSearch.source == backupSavedSearch.source && currentSavedSearch.source == backupSavedSearch.source &&
currentSavedSearch.name == backupSavedSearch.name && currentSavedSearch.name == backupSavedSearch.name &&

View file

@ -17,8 +17,8 @@ import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.log.xLogD import exh.log.xLogD
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EHENTAI_EXT_SOURCES
import exh.source.EXH_SOURCE_ID import exh.source.EXHENTAI_EXT_SOURCES
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -116,8 +117,10 @@ class ExtensionManager(
// SY --> // SY -->
return when (sourceId) { return when (sourceId) {
EH_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_ehentai_source) // KMK -->
EXH_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_exhentai_source) in EHENTAI_EXT_SOURCES -> ContextCompat.getDrawable(context, R.mipmap.ic_ehentai_source)
in EXHENTAI_EXT_SOURCES -> ContextCompat.getDrawable(context, R.mipmap.ic_exhentai_source)
// KMK <--
MERGED_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_merged_source) MERGED_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_merged_source)
else -> null else -> null
} }
@ -166,8 +169,17 @@ class ExtensionManager(
} }
} }
private fun Extension.isBlacklisted(blacklistEnabled: Boolean = preferences.enableSourceBlacklist().get()): Boolean { private fun Extension.isBlacklisted(
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled blacklistEnabled: Boolean = preferences.enableSourceBlacklist().get(),
// KMK -->
isHentaiEnabled: Boolean = Injekt.get<UnsortedPreferences>().isHentaiEnabled().get(),
// KMK <--
): Boolean {
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS &&
blacklistEnabled &&
// KMK -->
isHentaiEnabled
// KMK <--
} }
// EXH <-- // EXH <--

View file

@ -23,6 +23,9 @@ import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.time.Instant import java.time.Instant
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
@ -179,8 +182,15 @@ internal class ExtensionApi {
// SY --> // SY -->
private fun Extension.isBlacklisted( private fun Extension.isBlacklisted(
blacklistEnabled: Boolean = sourcePreferences.enableSourceBlacklist().get(), blacklistEnabled: Boolean = sourcePreferences.enableSourceBlacklist().get(),
// KMK -->
isHentaiEnabled: Boolean = Injekt.get<UnsortedPreferences>().isHentaiEnabled().get(),
// KMK <--
): Boolean { ): Boolean {
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS &&
blacklistEnabled &&
// KMK -->
isHentaiEnabled
// KMK <--
} }
// SY <-- // SY <--
} }

View file

@ -15,9 +15,9 @@ import eu.kanade.tachiyomi.source.online.english.Tsumino
import exh.log.xLogD import exh.log.xLogD
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.source.EH_SOURCE_ID import exh.source.EHENTAI_EXT_SOURCES
import exh.source.EIGHTMUSES_SOURCE_ID import exh.source.EIGHTMUSES_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXHENTAI_EXT_SOURCES
import exh.source.EnhancedHttpSource import exh.source.EnhancedHttpSource
import exh.source.HBROWSE_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
@ -79,8 +79,13 @@ class AndroidSourceManager(
.combine(preferences.enableExhentai().changes()) { extensions, enableExhentai -> .combine(preferences.enableExhentai().changes()) { extensions, enableExhentai ->
extensions to enableExhentai extensions to enableExhentai
} }
// KMK -->
.combine(
preferences.isHentaiEnabled().changes(),
) { (a, b), c -> Triple(a, b, c) }
// KMK <--
// SY <-- // SY <--
.collectLatest { (extensions, enableExhentai) -> .collectLatest { (extensions, enableExhentai/* KMK --> */, isHentaiEnabled/* KMK <-- */) ->
val mutableMap = ConcurrentHashMap<Long, Source>( val mutableMap = ConcurrentHashMap<Long, Source>(
mapOf( mapOf(
LocalSource.ID to LocalSource( LocalSource.ID to LocalSource(
@ -93,16 +98,24 @@ class AndroidSourceManager(
), ),
), ),
).apply { ).apply {
// SY --> // KMK -->
put(EH_SOURCE_ID, EHentai(EH_SOURCE_ID, false, context)) if (isHentaiEnabled) {
if (enableExhentai) { EHENTAI_EXT_SOURCES.forEach { (id, lang) ->
put(EXH_SOURCE_ID, EHentai(EXH_SOURCE_ID, true, context)) put(id, EHentai(id, false, context, lang))
}
if (enableExhentai) {
EXHENTAI_EXT_SOURCES.forEach { (id, lang) ->
put(id, EHentai(id, true, context, lang))
}
}
// SY -->
put(MERGED_SOURCE_ID, MergedSource())
// SY <--
} }
put(MERGED_SOURCE_ID, MergedSource()) // KMK <--
// SY <--
} }
extensions.forEach { extension -> extensions.forEach { extension ->
extension.sources.mapNotNull { it.toInternalSource() }.forEach { extension.sources.mapNotNull { it.toInternalSource(/* KMK --> */isHentaiEnabled/* KMK <-- */) }.forEach {
mutableMap[it.id] = it mutableMap[it.id] = it
registerStubSource(StubSource.from(it)) registerStubSource(StubSource.from(it))
} }
@ -123,7 +136,11 @@ class AndroidSourceManager(
} }
} }
private fun Source.toInternalSource(): Source? { private fun Source.toInternalSource(
// KMK -->
isHentaiEnabled: Boolean,
// KMK <--
): Source? {
// EXH --> // EXH -->
val sourceQName = this::class.qualifiedName val sourceQName = this::class.qualifiedName
val factories = DELEGATED_SOURCES.entries val factories = DELEGATED_SOURCES.entries
@ -158,7 +175,12 @@ class AndroidSourceManager(
this this
} }
return if (id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) { return if (
// KMK -->
isHentaiEnabled &&
// KMK <--
id in BlacklistedSources.BLACKLISTED_EXT_SOURCES
) {
xLogD( xLogD(
"Removing blacklisted source: (id: %s, name: %s, lang: %s)!", "Removing blacklisted source: (id: %s, name: %s, lang: %s)!",
id, id,

View file

@ -97,7 +97,13 @@ class EHentai(
override val id: Long, override val id: Long,
val exh: Boolean, val exh: Boolean,
val context: Context, val context: Context,
// KMK -->
override val lang: String = "all",
// KMK <--
) : HttpSource(), ) : HttpSource(),
// KMK -->
EhBasedSource,
// KMK <--
MetadataSource<EHentaiSearchMetadata, Document>, MetadataSource<EHentaiSearchMetadata, Document>,
UrlImportableSource, UrlImportableSource,
NamespaceSource, NamespaceSource,
@ -114,9 +120,19 @@ class EHentai(
override val baseUrl: String override val baseUrl: String
get() = "https://$domain" get() = "https://$domain"
override val lang = "all"
override val supportsLatest = true override val supportsLatest = true
// KMK -->
private val ehLang = languageMapping[lang]
// true if lang is a "natural human language"
private fun isLangNatural(): Boolean = lang !in listOf("none", "other", "all")
private fun languageTag(): String {
return "language:$ehLang"
}
// KMK <--
private val preferences: UnsortedPreferences by injectLazy() private val preferences: UnsortedPreferences by injectLazy()
private val updateHelper: EHentaiUpdateHelper by injectLazy() private val updateHelper: EHentaiUpdateHelper by injectLazy()
@ -273,7 +289,6 @@ class EHentai(
private fun getDateTag(element: Element?): Long? { private fun getDateTag(element: Element?): Long? {
val text = element?.text()?.nullIfBlank() val text = element?.text()?.nullIfBlank()
return if (text != null) { return if (text != null) {
println(text)
val date = ZonedDateTime.parse(text, MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC)) val date = ZonedDateTime.parse(text, MetadataUtil.EX_DATE_FORMAT.withZone(ZoneOffset.UTC))
date?.toInstant()?.toEpochMilli() date?.toInstant()?.toEpochMilli()
} else { } else {
@ -467,9 +482,18 @@ class EHentai(
return if (it.text() == ">") it.attr("href") else null return if (it.text() == ">") it.attr("href") else null
} }
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int) =
return exGet("$baseUrl/popular") // KMK -->
} if (isLangNatural()) {
exGet("$baseUrl/?f_search=${languageTag()}&f_srdd=5&f_sr=on", page)
} else {
if (page > 1) {
exGet("$baseUrl/?f_srdd=5&f_sr=on", page - 1)
} else {
// KMK <--
exGet("$baseUrl/popular")
}
}
private fun <T : MangasPage> Observable<T>.checkValid(): Observable<MangasPage> = map { private fun <T : MangasPage> Observable<T>.checkValid(): Observable<MangasPage> = map {
it.checkValid() it.checkValid()
@ -565,7 +589,14 @@ class EHentai(
) )
} }
override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page) override fun latestUpdatesRequest(page: Int) =
// KMK -->
if (isLangNatural()) {
exGet("$baseUrl/?f_search=${languageTag()}", page)
} else {
// KMK <--
exGet(baseUrl, page)
}
override fun popularMangaParse(response: Response) = genericMangaParse(response) override fun popularMangaParse(response: Response) = genericMangaParse(response)
override fun searchMangaParse(response: Response) = genericMangaParse(response) override fun searchMangaParse(response: Response) = genericMangaParse(response)
@ -911,7 +942,7 @@ class EHentai(
// Session-less extended display mode (for users without ExHentai) // Session-less extended display mode (for users without ExHentai)
cookies["sl"] = "dm_2" cookies["sl"] = "dm_2"
// Ignore all content warnings // Ignore all content warnings ("Offensive For Everyone")
cookies["nw"] = "1" cookies["nw"] = "1"
return cookies return cookies
@ -1382,5 +1413,27 @@ class EHentai(
fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") { fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") {
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
} }
// KMK -->
val languageMapping = mapOf(
"ja" to "japanese",
"en" to "english",
"zh" to "chinese",
"nl" to "dutch",
"fr" to "french",
"de" to "german",
"hu" to "hungarian",
"it" to "italian",
"ko" to "korean",
"pl" to "polish",
"pt-BR" to "portuguese",
"ru" to "russian",
"es" to "spanish",
"th" to "thai",
"vi" to "vietnamese",
"none" to "n/a",
"other" to "other",
)
// KMK <--
} }
} }

View file

@ -21,7 +21,6 @@ import exh.metadata.metadata.NHentaiSearchMetadata
import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.source.NHENTAI_SOURCE_ID
import exh.util.trimOrNull import exh.util.trimOrNull
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend import exh.util.urlImportFetchSearchMangaSuspend
@ -239,8 +238,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
} }
companion object { companion object {
const val otherId = NHENTAI_SOURCE_ID
private val jsonParser = Json { private val jsonParser = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }

View file

@ -998,31 +998,21 @@ class LibraryScreenModel(
unfiltered.asFlow().cancellable().filter { item -> unfiltered.asFlow().cancellable().filter { item ->
val mangaId = item.libraryManga.manga.id val mangaId = item.libraryManga.manga.id
val sourceId = item.libraryManga.manga.source val sourceId = item.libraryManga.manga.source
if (isMetadataSource(sourceId)) { if (isMetadataSource(sourceId) && mangaWithMetaIds.binarySearch(mangaId) >= 0) {
if (mangaWithMetaIds.binarySearch(mangaId) < 0) { val tags = getSearchTags.await(mangaId)
// No meta? Filter using title val titles = getSearchTitles.await(mangaId)
filterManga( filterManga(
queries = parsedQuery, queries = parsedQuery,
libraryManga = item.libraryManga, libraryManga = item.libraryManga,
tracks = tracks[mangaId], tracks = tracks[mangaId],
source = sources[sourceId], source = sources[sourceId],
loggedInTrackServices = loggedInTrackServices, checkGenre = false,
) searchTags = tags,
} else { searchTitles = titles,
val tags = getSearchTags.await(mangaId) loggedInTrackServices = loggedInTrackServices,
val titles = getSearchTitles.await(mangaId) )
filterManga(
queries = parsedQuery,
libraryManga = item.libraryManga,
tracks = tracks[mangaId],
source = sources[sourceId],
checkGenre = false,
searchTags = tags,
searchTitles = titles,
loggedInTrackServices = loggedInTrackServices,
)
}
} else { } else {
// No meta? Filter using title
filterManga( filterManga(
queries = parsedQuery, queries = parsedQuery,
libraryManga = item.libraryManga, libraryManga = item.libraryManga,

View file

@ -94,9 +94,6 @@ import eu.kanade.tachiyomi.util.view.setComposeContent
import exh.debug.DebugToggles import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.log.DebugModeOverlay import exh.log.DebugModeOverlay
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -400,13 +397,6 @@ class MainActivity : BaseActivity() {
chapterCache.clear() chapterCache.clear()
} }
} }
// SY -->
if (!unsortedPreferences.isHentaiEnabled().get()) {
BlacklistedSources.HIDDEN_SOURCES += EH_SOURCE_ID
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
}
// SY -->
} }
// KMK --> // KMK -->

View file

@ -1,7 +1,11 @@
package exh package exh
import exh.source.BlacklistedSources import eu.kanade.tachiyomi.data.backup.models.BackupFeed
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import exh.source.EH_OLD_ID
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_OLD_ID
import exh.source.EXH_SOURCE_ID
import exh.source.HBROWSE_OLD_ID import exh.source.HBROWSE_OLD_ID
import exh.source.HBROWSE_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID
import exh.source.NHENTAI_OLD_ID import exh.source.NHENTAI_OLD_ID
@ -42,16 +46,101 @@ object EXHMigrations {
) )
} }
// KMK -->
// Allow importing of EHentai extension backups // Allow importing of EHentai extension backups
if (newManga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) { if (newManga.source == EH_OLD_ID) {
newManga = newManga.copy( newManga = newManga.copy(
source = EH_SOURCE_ID, source = EH_SOURCE_ID,
) )
} }
if (newManga.source == EXH_OLD_ID) {
newManga = newManga.copy(
source = EXH_SOURCE_ID,
)
}
// KMK <--
return newManga return newManga
} }
// KMK -->
/**
* Migrate old source ID of delegated sources in old backup
*/
fun migrateBackupSavedSearch(savedSearch: BackupSavedSearch): BackupSavedSearch {
return when (savedSearch.source) {
NHENTAI_OLD_ID -> savedSearch.copy(
source = NHENTAI_SOURCE_ID,
)
TSUMINO_OLD_ID -> savedSearch.copy(
source = TSUMINO_SOURCE_ID,
)
HBROWSE_OLD_ID -> savedSearch.copy(
source = HBROWSE_SOURCE_ID,
)
EH_OLD_ID -> savedSearch.copy(
source = EH_SOURCE_ID,
)
EXH_OLD_ID -> savedSearch.copy(
source = EXH_SOURCE_ID,
)
else -> savedSearch
}
}
/**
* Migrate old source ID of delegated sources in old backup
*/
fun migrateBackupFeed(feed: BackupFeed): BackupFeed {
return when (feed.source) {
NHENTAI_OLD_ID -> feed.copy(
source = NHENTAI_SOURCE_ID,
)
TSUMINO_OLD_ID -> feed.copy(
source = TSUMINO_SOURCE_ID,
)
HBROWSE_OLD_ID -> feed.copy(
source = HBROWSE_SOURCE_ID,
)
EH_OLD_ID -> feed.copy(
source = EH_SOURCE_ID,
)
EXH_OLD_ID -> feed.copy(
source = EXH_SOURCE_ID,
)
else -> feed
}
}
/**
* Migrate old source ID of delegated sources in old backup
*/
fun migratePinnedSources(pinnedSources: Set<String>): Set<String> {
var pinned = pinnedSources
if (NHENTAI_OLD_ID.toString() in pinned) {
pinned = pinned.minus(NHENTAI_OLD_ID.toString())
.plus(NHENTAI_SOURCE_ID.toString())
}
if (TSUMINO_OLD_ID.toString() in pinned) {
pinned = pinned.minus(TSUMINO_OLD_ID.toString())
.plus(TSUMINO_SOURCE_ID.toString())
}
if (HBROWSE_OLD_ID.toString() in pinned) {
pinned = pinned.minus(HBROWSE_OLD_ID.toString())
.plus(HBROWSE_SOURCE_ID.toString())
}
if (EH_OLD_ID.toString() in pinned) {
pinned = pinned.minus(EH_OLD_ID.toString())
.plus(EH_SOURCE_ID.toString())
}
if (EXH_OLD_ID.toString() in pinned) {
pinned = pinned.minus(EXH_OLD_ID.toString())
.plus(EXH_SOURCE_ID.toString())
}
return pinned
}
// KMK -->
private fun getUrlWithoutDomain(orig: String): String { private fun getUrlWithoutDomain(orig: String): String {
return try { return try {
val uri = URI(orig) val uri = URI(orig)

View file

@ -1,27 +1,7 @@
package exh.source package exh.source
object BlacklistedSources { object BlacklistedSources {
val EHENTAI_EXT_SOURCES = longArrayOf( val BLACKLISTED_EXT_SOURCES = EHENTAI_EXT_SOURCES.keys
8100626124886895451,
57122881048805941,
4678440076103929247,
1876021963378735852,
3955189842350477641,
4348288691341764259,
773611868725221145,
5759417018342755550,
825187715438990384,
6116711405602166104,
7151438547982231541,
2171445159732592630,
3032959619549451093,
5980349886941016589,
6073266008352078708,
5499077866612745456,
6140480779421365791,
)
val BLACKLISTED_EXT_SOURCES = EHENTAI_EXT_SOURCES
val BLACKLISTED_EXTENSIONS = arrayOf( val BLACKLISTED_EXTENSIONS = arrayOf(
"eu.kanade.tachiyomi.extension.all.ehentai", "eu.kanade.tachiyomi.extension.all.ehentai",

View file

@ -1,7 +1,9 @@
package mihon.core.migration package mihon.core.migration
import eu.kanade.domain.source.service.SourcePreferences
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
object MigrateUtils { object MigrateUtils {
@ -9,7 +11,23 @@ object MigrateUtils {
val handler = migrationContext.get<DatabaseHandler>() ?: return val handler = migrationContext.get<DatabaseHandler>() ?: return
runBlocking { runBlocking {
handler.await { ehQueries.migrateSource(newId, oldId) } handler.await { ehQueries.migrateSource(newId, oldId) }
// KMK -->
// Migrate saved searches & feeds
handler.await { ehQueries.migrateSourceSavedSearch(newId, oldId) }
handler.await { ehQueries.migrateSourceFeed(newId, oldId) }
} }
// Also update pin
val preferences = migrationContext.get<SourcePreferences>() ?: return
val isPinned = oldId.toString() in preferences.pinnedSources().get()
if (isPinned) {
preferences.pinnedSources().getAndSet { pinned ->
pinned.minus(oldId.toString())
.plus(newId.toString())
}
}
// KMK <--
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View file

@ -0,0 +1,21 @@
package mihon.core.migration.migrations
import exh.source.EH_OLD_ID
import exh.source.EH_SOURCE_ID
import exh.source.EXH_OLD_ID
import exh.source.EXH_SOURCE_ID
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class EHentaiMigration : Migration {
override val version: Float = 72f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
MigrateUtils.updateSourceId(migrationContext, EH_SOURCE_ID, EH_OLD_ID)
MigrateUtils.updateSourceId(migrationContext, EXH_SOURCE_ID, EXH_OLD_ID)
return@withIOContext true
}
}

View file

@ -49,5 +49,6 @@ val migrations: List<Migration>
OfficialExtensionRepositoryMigration(), OfficialExtensionRepositoryMigration(),
IntegratedHentaiMigration(), IntegratedHentaiMigration(),
SetupAppUpdateMigration(), SetupAppUpdateMigration(),
EHentaiMigration(),
// KMK <-- // KMK <--
) )

View file

@ -6,6 +6,18 @@ UPDATE mangas
SET source = :newId SET source = :newId
WHERE source = :oldId; WHERE source = :oldId;
-- KMK -->
migrateSourceSavedSearch:
UPDATE saved_search
SET source = :newId
WHERE source = :oldId;
migrateSourceFeed:
UPDATE feed_saved_search
SET source = :newId
WHERE source = :oldId;
-- KMK <--
getChaptersByMangaIds: getChaptersByMangaIds:
SELECT * FROM chapters WHERE manga_id IN :mangaIds; SELECT * FROM chapters WHERE manga_id IN :mangaIds;

View file

@ -1,6 +1,7 @@
package exh.source package exh.source
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.all.EhBasedSource
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
// Used to speed up isLewdSource // Used to speed up isLewdSource
@ -18,13 +19,21 @@ var LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf(
// This method MUST be fast! // This method MUST be fast!
fun isMetadataSource(source: Long) = source in 6900..6999 || fun isMetadataSource(source: Long) = source in 6900..6999 ||
// KMK -->
source == EH_SOURCE_ID ||
source == EXH_SOURCE_ID ||
// KMK <--
metadataDelegatedSourceIds.binarySearch(source) >= 0 metadataDelegatedSourceIds.binarySearch(source) >= 0
fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID // KMK -->
fun Source.isEhBasedSource() = this is EhBasedSource && id in EHENTAI_EXT_SOURCES || id in EXHENTAI_EXT_SOURCES
// KMK <--
fun Source.isMdBasedSource() = id in mangaDexSourceIds fun Source.isMdBasedSource() = id in mangaDexSourceIds
fun Manga.isEhBasedManga() = source == EH_SOURCE_ID || source == EXH_SOURCE_ID // KMK -->
fun Manga.isEhBasedManga() = source in EHENTAI_EXT_SOURCES || source in EXHENTAI_EXT_SOURCES
// KMK <--
fun Source.getMainSource(): Source = if (this is EnhancedHttpSource) { fun Source.getMainSource(): Source = if (this is EnhancedHttpSource) {
this.source() this.source()

View file

@ -0,0 +1,3 @@
package eu.kanade.tachiyomi.source.online.all
interface EhBasedSource

View file

@ -2,8 +2,12 @@ package exh.source
// Lewd source IDs // Lewd source IDs
const val LEWD_SOURCE_SERIES = 6900L const val LEWD_SOURCE_SERIES = 6900L
const val EH_SOURCE_ID = LEWD_SOURCE_SERIES + 1
const val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2 // KMK -->
const val EH_SOURCE_ID = 1713178126840476467 // LEWD_SOURCE_SERIES + 1
const val EXH_SOURCE_ID = 6225928719850211219 // LEWD_SOURCE_SERIES + 2
// KMK <--
const val NHENTAI_SOURCE_ID = 7309872737163460316L const val NHENTAI_SOURCE_ID = 7309872737163460316L
const val PURURIN_SOURCE_ID = 2221515250486218861L const val PURURIN_SOURCE_ID = 2221515250486218861L
const val TSUMINO_SOURCE_ID = 6707338697138388238L const val TSUMINO_SOURCE_ID = 6707338697138388238L
@ -11,11 +15,58 @@ const val EIGHTMUSES_SOURCE_ID = 1802675169972965535L
const val HBROWSE_SOURCE_ID = 1401584337232758222L const val HBROWSE_SOURCE_ID = 1401584337232758222L
const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69 const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
// KMK -->
const val EH_OLD_ID = 6901L
const val EXH_OLD_ID = 6902L
// KMK <--
const val NHENTAI_OLD_ID = 6907L const val NHENTAI_OLD_ID = 6907L
const val TSUMINO_OLD_ID = 6909L const val TSUMINO_OLD_ID = 6909L
const val HBROWSE_OLD_ID = 6912L const val HBROWSE_OLD_ID = 6912L
// KMK --> // KMK -->
val EHENTAI_EXT_SOURCES = mapOf(
8100626124886895451 to "ja", // E-Hentai (Ja)
57122881048805941 to "en", // E-Hentai (En)
4678440076103929247 to "zh", // E-Hentai (Zh)
1876021963378735852 to "nl", // E-Hentai (Nl)
3955189842350477641 to "fr", // E-Hentai (Fr)
4348288691341764259 to "de", // E-Hentai (De)
773611868725221145 to "hu", // E-Hentai (Hu)
5759417018342755550 to "it", // E-Hentai (It)
825187715438990384 to "ko", // E-Hentai (Ko)
6116711405602166104 to "pl", // E-Hentai (Pl)
7151438547982231541 to "pt-BR", // E-Hentai (PtBr)
2171445159732592630 to "ru", // E-Hentai (Ru)
3032959619549451093 to "es", // E-Hentai (Es)
5980349886941016589 to "th", // E-Hentai (Th)
6073266008352078708 to "vi", // E-Hentai (Vi)
5499077866612745456 to "none", // E-Hentai (None)
6140480779421365791 to "other", // E-Hentai (Other)
EH_SOURCE_ID to "all", // E-Hentai (Multi)
)
val EXHENTAI_EXT_SOURCES = mapOf(
4069364610143267166 to "ja", // E-Hentai (Ja)
6024400692103629868 to "en", // E-Hentai (En)
1394807835077780591 to "zh", // E-Hentai (Zh)
3403092744483051659 to "nl", // E-Hentai (Nl)
2063958147920418330 to "fr", // E-Hentai (Fr)
4277540540471304114 to "de", // E-Hentai (De)
5580677059017993793 to "hu", // E-Hentai (Hu)
6348816521182710144 to "it", // E-Hentai (It)
933801322201782118 to "ko", // E-Hentai (Ko)
250678340923599076 to "pl", // E-Hentai (Pl)
7151438547982231541 to "pt-BR", // E-Hentai (PtBr)
2298110591802103872 to "ru", // E-Hentai (Ru)
1471697890032830855 to "es", // E-Hentai (Es)
5297549186919793998 to "th", // E-Hentai (Th)
1904260310237764859 to "vi", // E-Hentai (Vi)
2351087900641384311 to "none", // E-Hentai (None)
609703168489499116 to "other", // E-Hentai (Other)
EXH_SOURCE_ID to "all", // E-Hentai (Multi)
)
val COMICK_IDS = setOf( val COMICK_IDS = setOf(
982606170401027267, // all 982606170401027267, // all
2971557565147974499, // en 2971557565147974499, // en