Compare commits

...

34 Commits

Author SHA1 Message Date
arkon b7779ba14f v1.8.5 2022-08-14 15:49:00 -04:00
arkon 9a291e6da4 Fix sources not loading
(cherry picked from commit 1f79444a53)

# Conflicts:
#	app/proguard-rules.pro
2022-08-14 15:41:41 -04:00
Jobobby04 e7423e3715 Add missing mangadex languages, remove language prettyPrint since its not used
(cherry picked from commit 240d821a58)
2022-08-13 15:49:13 -04:00
Jobobby04 0ed26dbc49 More fixes 2022-08-13 15:48:49 -04:00
Jobobby04 a08e4e616d Update EHTags list
(cherry picked from commit 05f2f79e0d)
2022-08-13 15:47:53 -04:00
Jobobby04 655126eaa2 Fixes 2022-08-13 15:46:27 -04:00
CVIUS af070a3f0a Detect identical mangas when long pressing to add to library (#7095)
* Detect identical mangas when long pressing to add to library

* Use extracted duplicate manga dialog to avoid duplication

* Partially revert previous commit

* Review changes

* Review changes part 2

(cherry picked from commit f1afeac0bc)
(cherry picked from commit 431c04e54f)
2022-08-13 15:46:05 -04:00
Jobobby04 2b9d564841 Minor improvements for delegated source id lists
(cherry picked from commit 1d593de654)
2022-08-13 15:40:55 -04:00
arkon d09de07a3f Cleanup 2022-08-13 15:40:28 -04:00
arkon 048587468d Don't allow swiping away app update install notification
Also show the new version number in the notifications.

(cherry picked from commit 4aa5c6107c)
2022-08-13 15:32:29 -04:00
CVIUS 5dcdd3454b Detect identical mangas when long pressing to add to library (#7095)
* Detect identical mangas when long pressing to add to library

* Use extracted duplicate manga dialog to avoid duplication

* Partially revert previous commit

* Review changes

* Review changes part 2

(cherry picked from commit f1afeac0bc)
(cherry picked from commit afd1c3b491)
2022-08-13 15:32:18 -04:00
nicki 5d5678861d Fix Links to Changelog/Readme/Commits for multisrc (#7252)
* Fix Links to Changelog/Readme/Commits for `multisrc`

working basic fix. Needs to be refactored into `createUrl()`

* Refactor back into `createUrl`

hopefully the logic is understandable
there's three cases:
 - when multisrc, if `path` isn't mentioned, then we're trying to open
   commmit history
 - when multisrc, if `path` is mentioned, then its either a changelog or
   a readme to a multisrc extension, the files are stored in the
   `overrides` subfolder
 - when not multisrc, we're looking at a single source where the links
   are constructed in the same way regardless of it being
   changelog/readme/commit history

(cherry picked from commit e7695aef78)
(cherry picked from commit 25e0075041)
2022-08-13 15:31:46 -04:00
arkon 85bd12e731 Actually compare chapter numbers as numbers when sorting (fixes #7247)
(cherry picked from commit da8669c826)
(cherry picked from commit 4b7b710b7c)

# Conflicts:
#	app/src/test/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognitionTest.kt
2022-08-13 15:31:37 -04:00
arkon f322a7e660 Add auto split tall images setting
Also includes some fixes for bad merges in earlier commits

Co-authored-by: Saud-97 <Saud-97@users.noreply.github.com>
Co-authored-by: AntsyLich <AntsyLich@users.noreply.github.com>
(cherry picked from commit 6db2becd30)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
2022-08-13 15:30:47 -04:00
Andreas 5f7b7c652c Log extension loading errors directly (#7716)
(cherry picked from commit 7892cc1519)
(cherry picked from commit 0b7d0f7f67)
2022-08-13 15:29:38 -04:00
Alessandro Jean 214cbed3f0 Add missing Authorization header on MAL refresh token request (#7686)
* Add missing Authorization header on MAL refresh token request.

* Make sure to also close the response when it have failed.

(cherry picked from commit 5315467908)
(cherry picked from commit af1ee662ed)
2022-08-13 15:29:28 -04:00
stevenyomi 71db4eebea Filter out empty genres before saving manga to database (#7655)
(cherry picked from commit 4efb736e56)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
(cherry picked from commit 702fdb054a)
2022-08-13 15:29:15 -04:00
Andreas 9a577e1c69 Remove deprecated LibrarySort (#7659)
* Remove deprecated LibrarySort

* Apply suggestions from code review

(cherry picked from commit 58acf0a8aa)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
(cherry picked from commit 4b87831bdd)

# Conflicts:
#	app/src/main/java/exh/EXHMigrations.kt
2022-08-13 15:29:03 -04:00
MatchaSoba 9a5ea9b507 Fix logic for searchWithGenre (#7559)
(cherry picked from commit b563e85c3b)
(cherry picked from commit b729b7f0aa)
2022-08-13 15:28:04 -04:00
arkon 474eea1c84 Avoid catastrophic failure when cover can't be created in local source (fixes #7577)
(cherry picked from commit d6977e5676)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
(cherry picked from commit cfe78ff907)
2022-08-13 15:27:52 -04:00
arkon 43010e92ac Show better error when trying to open RARv5 file
(cherry picked from commit a843054388)
(cherry picked from commit 53a381ce28)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
2022-08-13 15:27:44 -04:00
nzoba 38b7240728 Add downloaded icon in TransitionView when chapter is downloaded (#7575)
* Add downloaded icon in TransitionView

* Change icon

(cherry picked from commit e8b7743826)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt
(cherry picked from commit ea37a5a7a1)
2022-08-13 15:26:46 -04:00
AntsyLich d52511d5ce Fix logic of app unlock (#7569)
(cherry picked from commit 8ea05e852e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
(cherry picked from commit 09e5bcaec1)
2022-08-13 15:26:36 -04:00
stevenyomi 06f0817bec Fix image MIME issues that cause download errors (#7562)
* Downloader: ignore non-image MIME to prevent .bin extensions

* ProgressResponseBody: allow null content type

Co-authored-by: anenasa <84259093+anenasa@users.noreply.github.com>

Co-authored-by: anenasa <84259093+anenasa@users.noreply.github.com>
(cherry picked from commit 3547d0142f)
(cherry picked from commit d734993349)
2022-08-13 15:26:28 -04:00
f1998f1998 2ee6d2d902 fix concurrent download (#7552)
* Fix concurrent download

* lower Concurrency

* artist Update app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
(cherry picked from commit b635f02d93)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
(cherry picked from commit c69f53a8f4)
2022-08-13 15:26:11 -04:00
Jobobby04 8df8622dfa Handle new default user agent where SY uses it
(cherry picked from commit f3ffd3b930)
2022-08-13 15:25:34 -04:00
arkon 58ef239959 Make default user agent string configurable
(cherry picked from commit 4ee1d72b6f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
(cherry picked from commit bcf9398987)
2022-08-13 15:21:47 -04:00
arkon a126180ca3 Replace deprecated ACTION_MEDIA_SCANNER_SCAN_FILE intent
(cherry picked from commit 0b4f3f5532)
(cherry picked from commit c7e44aa22f)
2022-08-13 15:21:36 -04:00
arkon ae7a4744bd Configure SQLite
- Turn on `foreign_keys` to cascade on delete properly
- Turn on `journal_mode` and set `synchronous` to NORMAL which may help performance for larger libraries

Based on d977b89af1

Co-authored-by: ghostbear <andreas.everos@gmail.com>
(cherry picked from commit ac4f98e152)
2022-08-13 15:21:24 -04:00
arkon 63cd8f8c07 Use Material3 switches in XML layouts
(cherry picked from commit da7a64b40d)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
#	app/src/main/res/layout/reader_pager_settings.xml
#	app/src/main/res/layout/reader_webtoon_settings.xml
(cherry picked from commit 72aba18dab)
2022-08-13 15:21:04 -04:00
arkon 2ecd2bce51 Bump dependencies + compile SDK to 33 + linting
(cherry picked from commit 3966a917ee)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2022-08-13 15:20:53 -04:00
arkon c7ecb58c61 Update .editorconfig
(cherry picked from commit be33a57d43)
2022-08-13 15:19:06 -04:00
arkon 422721bb64 Update chapter recognition and related tests
Includes 3e07100dc2

Co-authored-by: Saud-97 <Saud-97@users.noreply.github.com>
(cherry picked from commit 4a71022a60)

# Conflicts:
#	.github/workflows/build_pull_request.yml
#	.github/workflows/build_push.yml
#	app/src/test/java/eu/kanade/tachiyomi/CustomRobolectricGradleTestRunner.kt
2022-08-13 15:18:53 -04:00
arkon 92bc9d8801 Update AGP/Gradle
(cherry picked from commit 34ac39e7e5)

# Conflicts:
#	.github/workflows/build_pull_request.yml
#	.github/workflows/build_push.yml
2022-08-13 15:18:20 -04:00
129 changed files with 2847 additions and 2445 deletions
+3 -1
View File
@@ -2,4 +2,6 @@
indent_size=4 indent_size=4
insert_final_newline=true insert_final_newline=true
ij_kotlin_allow_trailing_comma=true ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
+1 -1
View File
@@ -3,7 +3,7 @@
I acknowledge that: I acknowledge that:
- I have updated: - I have updated:
- To the latest version of the app (stable is v1.8.4) - To the latest version of the app (stable is v1.8.5)
- All extensions - All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ - I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
+2 -2
View File
@@ -53,7 +53,7 @@ body:
label: Tachiyomi version label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**. description: You can find your Tachiyomi version in **More → About**.
placeholder: | placeholder: |
Example: "1.8.4" Example: "1.8.5"
validations: validations:
required: true required: true
@@ -98,7 +98,7 @@ body:
required: true required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/). - label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true required: true
- label: I have updated the app to version **[1.8.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**. - label: I have updated the app to version **[1.8.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true required: true
- label: I have updated all installed extensions. - label: I have updated all installed extensions.
required: true required: true
+1 -1
View File
@@ -33,7 +33,7 @@ body:
required: true required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose). - label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true required: true
- label: I have updated the app to version **[1.8.4](https://github.com/jobobby04/tachiyomisy/releases/latest)**. - label: I have updated the app to version **[1.8.5](https://github.com/jobobby04/tachiyomisy/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true
@@ -1,5 +0,0 @@
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx5120m
org.gradle.workers.max=2
kotlin.incremental=false
@@ -37,11 +37,6 @@ jobs:
java-version: 11 java-version: 11
distribution: adopt distribution: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Write google-services.json - name: Write google-services.json
uses: DamianReeves/write-file-action@v1.0 uses: DamianReeves/write-file-action@v1.0
with: with:
-5
View File
@@ -33,11 +33,6 @@ jobs:
java-version: 11 java-version: 11
distribution: adopt distribution: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app - name: Build app
uses: gradle/gradle-command-action@v2 uses: gradle/gradle-command-action@v2
with: with:
+22 -16
View File
@@ -1,3 +1,4 @@
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
@@ -18,6 +19,7 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
shortcutHelper.setFilePath("./shortcuts.xml") shortcutHelper.setFilePath("./shortcuts.xml")
android { android {
namespace = "eu.kanade.tachiyomi"
compileSdk = AndroidConfig.compileSdk compileSdk = AndroidConfig.compileSdk
ndkVersion = AndroidConfig.ndk ndkVersion = AndroidConfig.ndk
@@ -25,8 +27,8 @@ android {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
minSdk = AndroidConfig.minSdk minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk targetSdk = AndroidConfig.targetSdk
versionCode = 35 versionCode = 36
versionName = "1.8.4" versionName = "1.8.5"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -225,13 +227,10 @@ dependencies {
// Tests // Tests
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.assertj.core)
testImplementation(libs.mockito.core)
testImplementation(libs.bundles.robolectric)
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android) // debugImplementation(libs.leakcanary.android)
implementation(libs.leakcanary.plumber)
// SY --> // SY -->
// Changelog // Changelog
@@ -258,23 +257,30 @@ dependencies {
} }
tasks { tasks {
withType<Test> {
useJUnitPlatform()
testLogging {
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
}
}
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> { withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf( kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.Experimental", "-opt-in=kotlin.Experimental",
"-Xopt-in=kotlin.RequiresOptIn", "-opt-in=kotlin.RequiresOptIn",
"-Xopt-in=kotlin.ExperimentalStdlibApi", "-opt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xopt-in=coil.annotation.ExperimentalCoilApi", "-opt-in=coil.annotation.ExperimentalCoilApi",
"-Xopt-in=kotlin.time.ExperimentalTime", "-opt-in=kotlin.time.ExperimentalTime",
) )
} }
// Duplicating Hebrew string assets due to some locale code issues on different devices // Duplicating Hebrew string assets due to some locale code issues on different devices
val copyHebrewStrings = task("copyHebrewStrings", type = Copy::class) { val copyHebrewStrings by registering(Copy::class) {
from("./src/main/res/values-he") from("./src/main/res/values-he")
into("./src/main/res/values-iw") into("./src/main/res/values-iw")
include("**/*") include("**/*")
+1 -2
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="eu.kanade.tachiyomi">
<!-- Internet --> <!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -72,6 +72,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.security.Security import java.security.Security
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
@@ -182,6 +183,7 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
} }
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
preferences.lastAppClosed().set(Date().time)
if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) { if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) {
SecureActivityDelegate.locked = true SecureActivityDelegate.locked = true
} }
@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.updater.AppUpdateJob import eu.kanade.tachiyomi.data.updater.AppUpdateJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
@@ -106,10 +105,9 @@ object Migrations {
// Reset sorting preference if using removed sort by source // Reset sorting preference if using removed sort by source
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0) val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
@Suppress("DEPRECATION") if (oldSortingMode == 5 /* SOURCE */) {
if (oldSortingMode == LibrarySort.SOURCE) {
prefs.edit { prefs.edit {
putInt(PreferenceKeys.librarySortingMode, LibrarySort.ALPHA) putInt(PreferenceKeys.librarySortingMode, 0 /* ALPHABETICAL */)
} }
} }
} }
@@ -202,16 +200,15 @@ object Migrations {
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0) val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true) val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
@Suppress("DEPRECATION")
val newSortingMode = when (oldSortingMode) { val newSortingMode = when (oldSortingMode) {
LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL 0 -> SortModeSetting.ALPHABETICAL
LibrarySort.LAST_READ -> SortModeSetting.LAST_READ 1 -> SortModeSetting.LAST_READ
LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED 2 -> SortModeSetting.LAST_CHECKED
LibrarySort.UNREAD -> SortModeSetting.UNREAD 3 -> SortModeSetting.UNREAD
LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS 4 -> SortModeSetting.TOTAL_CHAPTERS
LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER 6 -> SortModeSetting.LATEST_CHAPTER
LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED 8 -> SortModeSetting.DATE_FETCHED
LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED 7 -> SortModeSetting.DATE_ADDED
else -> SortModeSetting.ALPHABETICAL else -> SortModeSetting.ALPHABETICAL
} }
@@ -115,5 +115,14 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {
db.setForeignKeyConstraintsEnabled(true) db.setForeignKeyConstraintsEnabled(true)
setPragma(db, "foreign_keys = ON")
setPragma(db, "journal_mode = WAL")
setPragma(db, "synchronous = NORMAL")
}
private fun setPragma(db: SupportSQLiteDatabase, pragma: String) {
val cursor = db.query("PRAGMA $pragma")
cursor.moveToFirst()
cursor.close()
} }
} }
@@ -34,11 +34,6 @@ interface Manga : SManga {
return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC
} }
fun getGenres(): List<String>? {
if (genre.isNullOrBlank()) return null
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
}
// SY --> // SY -->
fun getOriginalGenres(): List<String>? { fun getOriginalGenres(): List<String>? {
return originalGenre?.split(", ")?.map { it.trim() } return originalGenre?.split(", ")?.map { it.trim() }
@@ -275,7 +275,7 @@ class Downloader(
// Start downloader if needed // Start downloader if needed
if (autoStart && wasEmpty) { if (autoStart && wasEmpty) {
val queuedDownloads = queue.filter { it.source !is UnmeteredSource }.count() val queuedDownloads = queue.count { it.source !is UnmeteredSource }
val maxDownloadsFromSource = queue val maxDownloadsFromSource = queue
.groupBy { it.source } .groupBy { it.source }
.filterKeys { it !is UnmeteredSource } .filterKeys { it !is UnmeteredSource }
@@ -347,8 +347,8 @@ class Downloader(
// Get all the URLs to the source images, fetch pages if necessary // Get all the URLs to the source images, fetch pages if necessary
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) } .flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
// Start downloading images, consider we can have downloaded images already // Start downloading images, consider we can have downloaded images already
// Concurrently do 5 pages at a time // Concurrently do 2 pages at a time
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir, dataSaver) }, 5) .flatMap({ page -> getOrDownloadImage(page, download, tmpDir, dataSaver).subscribeOn(Schedulers.io()) }, 2)
.onBackpressureLatest() .onBackpressureLatest()
// Do when page is downloaded. // Do when page is downloaded.
.doOnNext { notifier.onProgressChange(download) } .doOnNext { notifier.onProgressChange(download) }
@@ -358,6 +358,7 @@ class Downloader(
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) } .doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
// If the page list threw, it will resume here // If the page list threw, it will resume here
.onErrorReturn { error -> .onErrorReturn { error ->
logcat(LogPriority.ERROR, error)
download.status = Download.State.ERROR download.status = Download.State.ERROR
notifier.onError(error.message, download.chapter.name, download.manga.title) notifier.onError(error.message, download.chapter.name, download.manga.title)
download download
@@ -385,7 +386,7 @@ class Downloader(
tmpFile?.delete() tmpFile?.delete()
// Try to find the image file. // Try to find the image file.
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") } val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") || it.name!!.contains("${filename}__001") }
// If the image is already downloaded, do nothing. Otherwise download from network // If the image is already downloaded, do nothing. Otherwise download from network
val pageObservable = when { val pageObservable = when {
@@ -395,8 +396,12 @@ class Downloader(
} }
return pageObservable return pageObservable
// When the image is ready, set image path, progress (just in case) and status // When the page is ready, set page path, progress (just in case) and status
.doOnNext { file -> .doOnNext { file ->
val success = splitTallImageIfNeeded(page, tmpDir)
if (success.not()) {
notifier.onError(context.getString(R.string.download_notifier_split_failed), download.chapter.name, download.manga.title)
}
page.uri = file.uri page.uri = file.uri
page.progress = 100 page.progress = 100
download.downloadedImages++ download.downloadedImages++
@@ -407,6 +412,7 @@ class Downloader(
.onErrorReturn { .onErrorReturn {
page.progress = 0 page.progress = 0
page.status = Page.ERROR page.status = Page.ERROR
notifier.onError(it.message, download.chapter.name, download.manga.title)
page page
} }
} }
@@ -471,7 +477,7 @@ class Downloader(
*/ */
private fun getImageExtension(response: Response, file: UniFile): String { private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available. // Read content type if available.
val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } val mime = response.body?.contentType()?.run { if (type == "image") "image/$subtype" else null }
// Else guess from the uri. // Else guess from the uri.
?: context.contentResolver.getType(file.uri) ?: context.contentResolver.getType(file.uri)
// Else read magic numbers. // Else read magic numbers.
@@ -480,6 +486,26 @@ class Downloader(
return ImageUtil.getExtensionFromMimeType(mime) return ImageUtil.getExtensionFromMimeType(mime)
} }
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile): Boolean {
if (!preferences.splitTallImages().get()) return true
val filename = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith(filename) }
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
val imageFilePath = imageFile.filePath
?: throw Error(context.getString(R.string.download_notifier_split_page_path_not_found, page.number))
// check if the original page was previously splitted before then skip.
if (imageFile.name!!.contains("__")) return true
return try {
ImageUtil.splitTallImage(imageFile, imageFilePath)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
false
}
}
/** /**
* Checks if the download was successful. * Checks if the download was successful.
* *
@@ -495,16 +521,10 @@ class Downloader(
dirname: String, dirname: String,
) { ) {
// Ensure that the chapter folder has all the images. // Ensure that the chapter folder has all the images.
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") } val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") || (it.name!!.contains("__") && !it.name!!.contains("__001.jpg")) }
download.status = if (downloadedImages.size == download.pages!!.size) { download.status = if (downloadedImages.size == download.pages!!.size) {
Download.State.DOWNLOADED // Only rename the directory if it's downloaded.
} else {
Download.State.ERROR
}
// Only rename the directory if it's downloaded.
if (download.status == Download.State.DOWNLOADED) {
if (preferences.saveChaptersAsCBZ().get()) { if (preferences.saveChaptersAsCBZ().get()) {
archiveChapter(mangaDir, dirname, tmpDir) archiveChapter(mangaDir, dirname, tmpDir)
} else { } else {
@@ -513,6 +533,10 @@ class Downloader(
cache.addChapter(dirname, mangaDir, download.manga) cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context) DiskUtil.createNoMediaFile(tmpDir, context)
Download.State.DOWNLOADED
} else {
Download.State.ERROR
} }
} }
@@ -7,6 +7,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -193,7 +194,7 @@ class NotificationReceiver : BroadcastReceiver() {
val file = File(path) val file = File(path)
file.delete() file.delete()
DiskUtil.scanMedia(context, file) DiskUtil.scanMedia(context, file.toUri())
} }
/** /**
@@ -65,6 +65,8 @@ object PreferenceKeys {
const val dohProvider = "doh_provider" const val dohProvider = "doh_provider"
const val defaultUserAgent = "default_user_agent"
const val defaultChapterFilterByRead = "default_chapter_filter_by_read" const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded" const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
@@ -59,7 +59,7 @@ class PreferencesHelper(val context: Context) {
fun lockAppAfter() = flowPrefs.getInt("lock_app_after", 0) fun lockAppAfter() = flowPrefs.getInt("lock_app_after", 0)
fun lastAppUnlock() = flowPrefs.getLong("last_app_unlock", 0) fun lastAppClosed() = flowPrefs.getLong("last_app_closed", 0)
fun secureScreen() = flowPrefs.getEnum("secure_screen_v2", Values.SecureScreenMode.INCOGNITO) fun secureScreen() = flowPrefs.getEnum("secure_screen_v2", Values.SecureScreenMode.INCOGNITO)
@@ -215,6 +215,8 @@ class PreferencesHelper(val context: Context) {
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true) fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true)
fun splitTallImages() = flowPrefs.getBoolean("split_tall_images", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false) fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2) fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2)
@@ -308,6 +310,8 @@ class PreferencesHelper(val context: Context) {
fun dohProvider() = prefs.getInt(Keys.dohProvider, -1) fun dohProvider() = prefs.getInt(Keys.dohProvider, -1)
fun defaultUserAgent() = flowPrefs.getString(Keys.defaultUserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44")
fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "") fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "")
fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL) fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
@@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir import eu.kanade.tachiyomi.util.storage.cacheImageDir
@@ -82,7 +83,7 @@ class ImageSaver(
} }
} }
DiskUtil.scanMedia(context, destFile) DiskUtil.scanMedia(context, destFile.toUri())
return destFile.getUriCompat(context) return destFile.getUriCompat(context)
} }
@@ -22,6 +22,7 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
@@ -256,13 +257,21 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendPath("my_list_status") .appendPath("my_list_status")
.build() .build()
fun refreshTokenRequest(refreshToken: String): Request { fun refreshTokenRequest(oauth: OAuth): Request {
val formBody: RequestBody = FormBody.Builder() val formBody: RequestBody = FormBody.Builder()
.add("client_id", clientId) .add("client_id", clientId)
.add("refresh_token", refreshToken) .add("refresh_token", oauth.refresh_token)
.add("grant_type", "refresh_token") .add("grant_type", "refresh_token")
.build() .build()
return POST("$baseOAuthUrl/token", body = formBody)
// Add the Authorization header manually as this particular
// request is called by the interceptor itself so it doesn't reach
// the part where the token is added automatically.
val headers = Headers.Builder()
.add("Authorization", "Bearer ${oauth.access_token}")
.build()
return POST("$baseOAuthUrl/token", body = formBody, headers = headers)
} }
private fun getPkceChallengeCode(): String { private fun getPkceChallengeCode(): String {
@@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.data.track.myanimelist package eu.kanade.tachiyomi.data.track.myanimelist
import kotlinx.serialization.decodeFromString import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.closeQuietly
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
@@ -24,11 +25,22 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
} }
// Refresh access token if expired // Refresh access token if expired
if (oauth != null && oauth!!.isExpired()) { if (oauth != null && oauth!!.isExpired()) {
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use { val newOauth = runCatching {
if (it.isSuccessful) { val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
setAuth(json.decodeFromString(it.body!!.string()))
if (oauthResponse.isSuccessful) {
oauthResponse.parseAs<OAuth>()
} else {
oauthResponse.closeQuietly()
null
} }
} }
if (newOauth.getOrNull() == null) {
throw IOException("Failed to refresh the access token")
}
setAuth(newOauth.getOrNull())
} }
if (oauth == null) { if (oauth == null) {
throw IOException("No authentication token") throw IOException("No authentication token")
@@ -48,6 +48,7 @@ class AppUpdateChecker {
when (result) { when (result) {
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release) is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
is AppUpdateResult.NewUpdateFdroidInstallation -> AppUpdateNotifier(context).promptFdroidUpdate() is AppUpdateResult.NewUpdateFdroidInstallation -> AppUpdateNotifier(context).promptFdroidUpdate()
else -> {}
} }
result result
@@ -29,6 +29,7 @@ internal class AppUpdateNotifier(private val context: Context) {
fun promptUpdate(release: GithubRelease) { fun promptUpdate(release: GithubRelease) {
val intent = Intent(context, AppUpdateService::class.java).apply { val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink()) putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version)
} }
val updateIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) val updateIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
@@ -116,6 +117,7 @@ internal class AppUpdateNotifier(private val context: Context) {
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
setContentIntent(installIntent) setContentIntent(installIntent)
setOngoing(true)
clearActions() clearActions()
addAction( addAction(
@@ -147,7 +147,7 @@ class AppUpdateService : Service() {
* @param context the application context. * @param context the application context.
* @param url the url to the new update. * @param url the url to the new update.
*/ */
fun start(context: Context, url: String, title: String = context.getString(R.string.app_name)) { fun start(context: Context, url: String, title: String? = context.getString(R.string.app_name)) {
if (!isRunning(context)) { if (!isRunning(context)) {
val intent = Intent(context, AppUpdateService::class.java).apply { val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title) putExtra(EXTRA_DOWNLOAD_TITLE, title)
@@ -4,7 +4,5 @@ sealed class LoadResult {
class Success(val extension: Extension.Installed) : LoadResult() class Success(val extension: Extension.Installed) : LoadResult()
class Untrusted(val extension: Extension.Untrusted) : LoadResult() class Untrusted(val extension: Extension.Untrusted) : LoadResult()
class Error(val message: String? = null) : LoadResult() { object Error : LoadResult()
constructor(exception: Throwable) : this(exception.message)
}
} }
@@ -7,10 +7,12 @@ import android.content.IntentFilter
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import logcat.LogPriority
/** /**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only * Broadcast receiver that listens for the system's packages installed, updated or removed, and only
@@ -52,6 +54,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionInstalled(result.extension) is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
else -> {}
} }
} }
} }
@@ -60,8 +63,8 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
when (val result = getExtensionFromIntent(context, intent)) { when (val result = getExtensionFromIntent(context, intent)) {
is LoadResult.Success -> listener.onExtensionUpdated(result.extension) is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
// Not needed as a package can't be upgraded if the signature is different // Not needed as a package can't be upgraded if the signature is different
is LoadResult.Untrusted -> { is LoadResult.Untrusted -> {}
} else -> {}
} }
} }
} }
@@ -93,7 +96,10 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
*/ */
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) val pkgName = getPackageNameFromIntent(intent)
?: return LoadResult.Error("Package name not found") if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" }
return LoadResult.Error
}
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
} }
@@ -80,10 +80,12 @@ internal object ExtensionLoader {
context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS) context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
} catch (error: PackageManager.NameNotFoundException) { } catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point // Unlikely, but the package may have been uninstalled at this point
return LoadResult.Error(error) logcat(LogPriority.ERROR, error)
return LoadResult.Error
} }
if (!isPackageAnExtension(pkgInfo)) { if (!isPackageAnExtension(pkgInfo)) {
return LoadResult.Error("Tried to load a package that wasn't a extension") logcat(LogPriority.WARN) { "Tried to load a package that wasn't a extension ($pkgName)" }
return LoadResult.Error
} }
return loadExtension(context, pkgName, pkgInfo) return loadExtension(context, pkgName, pkgInfo)
} }
@@ -102,7 +104,8 @@ internal object ExtensionLoader {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) { } catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point // Unlikely, but the package may have been uninstalled at this point
return LoadResult.Error(error) logcat(LogPriority.ERROR, error)
return LoadResult.Error
} }
val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ") val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
@@ -112,7 +115,7 @@ internal object ExtensionLoader {
if (versionName.isNullOrEmpty()) { if (versionName.isNullOrEmpty()) {
val exception = Exception("Missing versionName for extension $extName") val exception = Exception("Missing versionName for extension $extName")
logcat(LogPriority.WARN, exception) logcat(LogPriority.WARN, exception)
return LoadResult.Error(exception) return LoadResult.Error
} }
// Validate lib version // Validate lib version
@@ -123,13 +126,14 @@ internal object ExtensionLoader {
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed", "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed",
) )
logcat(LogPriority.WARN, exception) logcat(LogPriority.WARN, exception)
return LoadResult.Error(exception) return LoadResult.Error
} }
val signatureHash = getSignatureHash(pkgInfo) val signatureHash = getSignatureHash(pkgInfo)
if (signatureHash == null) { if (signatureHash == null) {
return LoadResult.Error("Package $pkgName isn't signed") logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
return LoadResult.Error
} else if (signatureHash !in trustedSignatures) { } else if (signatureHash !in trustedSignatures) {
val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash) val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash)
logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" } logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
@@ -138,7 +142,8 @@ internal object ExtensionLoader {
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1 val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
if (!loadNsfwSource && isNsfw) { if (!loadNsfwSource && isNsfw) {
return LoadResult.Error("NSFW extension $pkgName not allowed") logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" }
return LoadResult.Error
} }
val hasReadme = appInfo.metaData.getInt(METADATA_HAS_README, 0) == 1 val hasReadme = appInfo.metaData.getInt(METADATA_HAS_README, 0) == 1
@@ -165,7 +170,7 @@ internal object ExtensionLoader {
} }
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" } logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" }
return LoadResult.Error(e) return LoadResult.Error
} }
} }
@@ -64,4 +64,8 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))
.build() .build()
} }
val defaultUserAgent by lazy {
preferences.defaultUserAgent().get()
}
} }
@@ -15,8 +15,8 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p
source(responseBody.source()).buffer() source(responseBody.source()).buffer()
} }
override fun contentType(): MediaType { override fun contentType(): MediaType? {
return responseBody.contentType()!! return responseBody.contentType()
} }
override fun contentLength(): Long { override fun contentLength(): Long {
@@ -9,7 +9,6 @@ import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.WebViewClientCompat
@@ -109,7 +108,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
webview.settings.userAgentString = request.header("User-Agent") webview.settings.userAgentString = request.header("User-Agent")
?: HttpSource.DEFAULT_USER_AGENT ?: networkHelper.defaultUserAgent
webview.webViewClient = object : WebViewClientCompat() { webview.webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
@@ -1,10 +1,14 @@
package eu.kanade.tachiyomi.network.interceptor package eu.kanade.tachiyomi.network.interceptor
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.network.NetworkHelper
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.injectLazy
class UserAgentInterceptor : Interceptor { class UserAgentInterceptor : Interceptor {
private val networkHelper: NetworkHelper by injectLazy()
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
@@ -12,7 +16,7 @@ class UserAgentInterceptor : Interceptor {
val newRequest = originalRequest val newRequest = originalRequest
.newBuilder() .newBuilder()
.removeHeader("User-Agent") .removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT) .addHeader("User-Agent", networkHelper.defaultUserAgent)
.build() .build()
chain.proceed(newRequest) chain.proceed(newRequest)
} else { } else {
@@ -20,11 +20,13 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.EpubFile import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream import kotlinx.serialization.json.encodeToStream
import logcat.LogPriority
import rx.Observable import rx.Observable
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
@@ -286,41 +288,46 @@ class LocalSource(
} }
private fun updateCover(chapter: SChapter, manga: SManga): File? { private fun updateCover(chapter: SChapter, manga: SManga): File? {
return when (val format = getFormat(chapter)) { return try {
is Format.Directory -> { when (val format = getFormat(chapter)) {
val entry = format.file.listFiles() is Format.Directory -> {
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } val entry = format.file.listFiles()
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) } entry?.let { updateCover(context, manga, it.inputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile(format.file).use { zip -> ZipFile(format.file).use { zip ->
val entry = zip.entries().toList() val entry = zip.entries().toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, zip.getInputStream(it)) } entry?.let { updateCover(context, manga, zip.getInputStream(it)) }
} }
} }
is Format.Rar -> { is Format.Rar -> {
Archive(format.file).use { archive -> Archive(format.file).use { archive ->
val entry = archive.fileHeaders val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it)) } entry?.let { updateCover(context, manga, archive.getInputStream(it)) }
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file).use { epub -> EpubFile(format.file).use { epub ->
val entry = epub.getImagesFromPages() val entry = epub.getImagesFromPages()
.firstOrNull() .firstOrNull()
?.let { epub.getEntry(it) } ?.let { epub.getEntry(it) }
entry?.let { updateCover(context, manga, epub.getInputStream(it)) } entry?.let { updateCover(context, manga, epub.getInputStream(it)) }
}
} }
} }
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Error updating cover for ${manga.title}" }
null
} }
.also { coverCache.clearMemoryCache() } .also { coverCache.clearMemoryCache() }
} }
@@ -398,7 +405,6 @@ class LocalSource(
} }
} }
// Create a .nomedia file
DiskUtil.createNoMediaFile(UniFile.fromFile(mangaDir), context) DiskUtil.createNoMediaFile(UniFile.fromFile(mangaDir), context)
manga.thumbnail_url = coverFile.absolutePath manga.thumbnail_url = coverFile.absolutePath
@@ -253,7 +253,7 @@ open class SourceManager(private val context: Context) {
), ),
).associateBy { it.originalSourceQualifiedClassName } ).associateBy { it.originalSourceQualifiedClassName }
val currentDelegatedSources = ListenMutableMap(mutableMapOf<Long, DelegatedSource>(), ::handleSourceLibrary) val currentDelegatedSources: MutableMap<Long, DelegatedSource> = ListenMutableMap(mutableMapOf(), ::handleSourceLibrary)
data class DelegatedSource( data class DelegatedSource(
val sourceName: String, val sourceName: String,
@@ -264,19 +264,10 @@ open class SourceManager(private val context: Context) {
) )
} }
class ListenMutableMap<K, V>(private val internalMap: MutableMap<K, V>, val listener: () -> Unit) : MutableMap<K, V> { private class ListenMutableMap<K, V>(
override val size: Int private val internalMap: MutableMap<K, V>,
get() = internalMap.size private val listener: () -> Unit,
override fun containsKey(key: K): Boolean = internalMap.containsKey(key) ) : MutableMap<K, V> by internalMap {
override fun containsValue(value: V): Boolean = internalMap.containsValue(value)
override fun get(key: K): V? = internalMap[key]
override fun isEmpty(): Boolean = internalMap.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = internalMap.entries
override val keys: MutableSet<K>
get() = internalMap.keys
override val values: MutableCollection<V>
get() = internalMap.values
override fun clear() { override fun clear() {
val clearResult = internalMap.clear() val clearResult = internalMap.clear()
listener() listener()
@@ -28,6 +28,11 @@ interface SManga : Serializable {
var initialized: Boolean var initialized: Boolean
fun getGenres(): List<String>? {
if (genre.isNullOrBlank()) return null
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
}
// SY --> // SY -->
val originalTitle: String val originalTitle: String
get() = (this as? MangaImpl)?.ogTitle ?: title get() = (this as? MangaImpl)?.ogTitle ?: title
@@ -104,7 +109,7 @@ fun SManga.toMangaInfo(): MangaInfo {
artist = this.artist ?: "", artist = this.artist ?: "",
author = this.author ?: "", author = this.author ?: "",
description = this.description ?: "", description = this.description ?: "",
genres = this.genre?.split(", ") ?: emptyList(), genres = this.getGenres() ?: emptyList(),
status = this.status, status = this.status,
cover = this.thumbnail_url ?: "", cover = this.thumbnail_url ?: "",
) )
@@ -99,7 +99,7 @@ abstract class HttpSource : CatalogueSource {
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.
*/ */
protected open fun headersBuilder() = Headers.Builder().apply { protected open fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", DEFAULT_USER_AGENT) add("User-Agent", network.defaultUserAgent)
} }
/** /**
@@ -417,8 +417,4 @@ abstract class HttpSource : CatalogueSource {
this.delegate = delegate this.delegate = delegate
} }
// EXH <-- // EXH <--
companion object {
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"
}
} }
@@ -835,8 +835,8 @@ class EHentai(
) )
}, },
AutoCompleteTags( AutoCompleteTags(
EHTags.getNamespaces0Tags().map { "$it:" } + EHTags.getAllTags(), EHTags.getNamespaces().map { "$it:" } + EHTags.getAllTags(),
EHTags.getNamespaces0Tags().map { "$it:" }, EHTags.getNamespaces().map { "$it:" },
excludePrefix, excludePrefix,
), ),
if (preferences.exhWatchedListDefaultState().get()) { if (preferences.exhWatchedListDefaultState().get()) {
@@ -121,16 +121,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
MangaPlusHandler(network.client) MangaPlusHandler(network.client)
} }
private val comikeyHandler by lazy { private val comikeyHandler by lazy {
ComikeyHandler(network.cloudflareClient) ComikeyHandler(network.cloudflareClient, network.defaultUserAgent)
} }
private val bilibiliHandler by lazy { private val bilibiliHandler by lazy {
BilibiliHandler(network.cloudflareClient) BilibiliHandler(network.cloudflareClient)
} }
private val azukHandler by lazy { private val azukHandler by lazy {
AzukiHandler(network.client) AzukiHandler(network.client, network.defaultUserAgent)
} }
private val mangaHotHandler by lazy { private val mangaHotHandler by lazy {
MangaHotHandler(network.client) MangaHotHandler(network.client, network.defaultUserAgent)
} }
private val pageHandler by lazy { private val pageHandler by lazy {
PageHandler( PageHandler(
@@ -98,7 +98,7 @@ abstract class DialogController : Controller {
/** /**
* Dismiss the dialog and pop this controller * Dismiss the dialog and pop this controller
*/ */
private fun dismissDialog() { protected fun dismissDialog() {
if (dismissed) { if (dismissed) {
return return
} }
@@ -59,16 +59,17 @@ abstract class SearchableNucleusController<VB : ViewBinding, P : BasePresenter<*
val searchAutoComplete: SearchView.SearchAutoComplete = searchView.findViewById( val searchAutoComplete: SearchView.SearchAutoComplete = searchView.findViewById(
R.id.search_src_text, R.id.search_src_text,
) )
searchAutoComplete.addTextChangedListener(object : TextWatcher { searchAutoComplete.addTextChangedListener(
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(editable: Editable) { override fun afterTextChanged(editable: Editable) {
editable.getSpans(0, editable.length, CharacterStyle::class.java) editable.getSpans(0, editable.length, CharacterStyle::class.java)
.forEach { editable.removeSpan(it) } .forEach { editable.removeSpan(it) }
} }
}, },
) )
searchView.queryTextEvents() searchView.queryTextEvents()
@@ -134,12 +135,12 @@ abstract class SearchableNucleusController<VB : ViewBinding, P : BasePresenter<*
searchItem.setOnActionExpandListener( searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener { object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
onSearchMenuItemActionExpand(item) onSearchMenuItemActionExpand(item)
return true return true
} }
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
val localSearchView = searchItem.actionView as SearchView val localSearchView = searchItem.actionView as SearchView
// if it is blank the flow event won't trigger so we would stay in a COLLAPSING state // if it is blank the flow event won't trigger so we would stay in a COLLAPSING state
@@ -119,6 +119,6 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
// SY <-- // SY <--
return preferences.lockAppAfter().get() <= 0 || return preferences.lockAppAfter().get() <= 0 ||
Date().time >= preferences.lastAppUnlock().get() + 60 * 1000 * preferences.lockAppAfter().get() Date().time >= preferences.lastAppClosed().get() + 60 * 1000 * preferences.lockAppAfter().get()
} }
} }
@@ -247,9 +247,13 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
} }
private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String { private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
return when { return if (!pkgFactory.isNullOrEmpty()) {
!pkgFactory.isNullOrEmpty() -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory$path" when (path.isEmpty()) {
else -> "$url/src/${pkgName.replace(".", "/")}$path" true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
}
} else {
url + "/src/" + pkgName.replace(".", "/") + path
} }
} }
@@ -474,8 +474,8 @@ class MigrationListController(bundle: Bundle? = null) :
} }
private fun MenuItem.setIconTint(enabled: Boolean, color: Int) { private fun MenuItem.setIconTint(enabled: Boolean, color: Int) {
icon.mutate() icon?.mutate()
icon.setTint(color) icon?.setTint(color)
isEnabled = enabled isEnabled = enabled
} }
@@ -41,6 +41,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.AddDuplicateMangaDialog
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@@ -483,19 +484,20 @@ open class BrowseSourceController(bundle: Bundle) :
* @param genreName the name of the genre * @param genreName the name of the genre
*/ */
fun searchWithGenre(genreName: String) { fun searchWithGenre(genreName: String) {
presenter.sourceFilters = presenter.source.getFilterList() val defaultFilters = presenter.source.getFilterList()
var filterList: FilterList? = null var genreExists = false
filter@ for (sourceFilter in presenter.sourceFilters) { filter@ for (sourceFilter in defaultFilters) {
if (sourceFilter is Filter.Group<*>) { if (sourceFilter is Filter.Group<*>) {
for (filter in sourceFilter.state) { for (filter in sourceFilter.state) {
if (filter is Filter<*> && filter.name.equals(genreName, true)) { if (filter is Filter<*> && filter.name.equals(genreName, true)) {
when (filter) { when (filter) {
is Filter.TriState -> filter.state = 1 is Filter.TriState -> filter.state = 1
is Filter.CheckBox -> filter.state = true is Filter.CheckBox -> filter.state = true
else -> {}
} }
filterList = presenter.sourceFilters genreExists = true
break@filter break@filter
} }
} }
@@ -505,19 +507,20 @@ open class BrowseSourceController(bundle: Bundle) :
if (index != -1) { if (index != -1) {
sourceFilter.state = index sourceFilter.state = index
filterList = presenter.sourceFilters genreExists = true
break break
} }
} }
} }
if (filterList != null) { if (genreExists) {
presenter.sourceFilters = defaultFilters
filterSheet?.setFilters(presenter.filterItems) filterSheet?.setFilters(presenter.filterItems)
showProgressBar() showProgressBar()
adapter?.clear() adapter?.clear()
presenter.restartPager("", filterList) presenter.restartPager("", defaultFilters)
} else { } else {
searchWithQuery(genreName) searchWithQuery(genreName)
} }
@@ -740,6 +743,7 @@ open class BrowseSourceController(bundle: Bundle) :
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
val activity = activity ?: return val activity = activity ?: return
val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return
val duplicateManga = presenter.getDuplicateLibraryManga(manga)
if (manga.favorite) { if (manga.favorite) {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
@@ -755,43 +759,53 @@ open class BrowseSourceController(bundle: Bundle) :
} }
.show() .show()
} else { } else {
val categories = presenter.getCategories() if (duplicateManga != null) {
val defaultCategoryId = preferences.defaultCategory() AddDuplicateMangaDialog(this, duplicateManga) { addToLibrary(manga, position) }
val defaultCategory = categories.find { it.id == defaultCategoryId } .showDialog(router)
} else {
addToLibrary(manga, position)
}
}
}
when { private fun addToLibrary(newManga: Manga, position: Int) {
// Default category set val activity = activity ?: return
defaultCategory != null -> { val categories = presenter.getCategories()
presenter.moveMangaToCategory(manga, defaultCategory) val defaultCategoryId = preferences.defaultCategory()
val defaultCategory = categories.find { it.id == defaultCategoryId }
presenter.changeMangaFavorite(manga) when {
adapter?.notifyItemChanged(position) // Default category set
activity.toast(activity.getString(R.string.manga_added_library)) defaultCategory != null -> {
} presenter.moveMangaToCategory(newManga, defaultCategory)
// Automatic 'Default' or no categories presenter.changeMangaFavorite(newManga)
defaultCategoryId == 0 || categories.isEmpty() -> { adapter?.notifyItemChanged(position)
presenter.moveMangaToCategory(manga, null) activity.toast(activity.getString(R.string.manga_added_library))
}
presenter.changeMangaFavorite(manga) // Automatic 'Default' or no categories
adapter?.notifyItemChanged(position) defaultCategoryId == 0 || categories.isEmpty() -> {
activity.toast(activity.getString(R.string.manga_added_library)) presenter.moveMangaToCategory(newManga, null)
}
// Choose a category presenter.changeMangaFavorite(newManga)
else -> { adapter?.notifyItemChanged(position)
val ids = presenter.getMangaCategoryIds(manga) activity.toast(activity.getString(R.string.manga_added_library))
val preselected = categories.map { }
if (it.id in ids) {
QuadStateTextView.State.CHECKED.ordinal
} else {
QuadStateTextView.State.UNCHECKED.ordinal
}
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) // Choose a category
.showDialog(router) else -> {
} val ids = presenter.getMangaCategoryIds(newManga)
val preselected = categories.map {
if (it.id in ids) {
QuadStateTextView.State.CHECKED.ordinal
} else {
QuadStateTextView.State.UNCHECKED.ordinal
}
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(newManga), categories, preselected)
.showDialog(router)
} }
} }
} }
@@ -435,6 +435,10 @@ open class BrowseSourcePresenter(
return db.getCategories().executeAsBlocking() return db.getCategories().executeAsBlocking()
} }
fun getDuplicateLibraryManga(manga: Manga): Manga? {
return db.getDuplicateLibraryManga(manga).executeAsBlocking()
}
/** /**
* Gets the category id's the manga is in, if the manga is not in a category, returns the default id. * Gets the category id's the manga is in, if the manga is not in a category, returns the default id.
* *
@@ -28,8 +28,7 @@ class DownloadHeaderHolder(view: View, adapter: FlexibleAdapter<*>) : Expandable
override fun onItemReleased(position: Int) { override fun onItemReleased(position: Int) {
super.onItemReleased(position) super.onItemReleased(position)
binding.container.isDragged = false binding.container.isDragged = false
mAdapter as DownloadAdapter
mAdapter.expandAll() mAdapter.expandAll()
mAdapter.downloadItemListener.onItemReleased(position) (mAdapter as DownloadAdapter).downloadItemListener.onItemReleased(position)
} }
} }
@@ -442,7 +442,7 @@ class LibraryController(
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
createOptionsMenu(menu, inflater, R.menu.library, R.id.action_search) createOptionsMenu(menu, inflater, R.menu.library, R.id.action_search)
// Mutate the filter icon because it needs to be tinted and the resource is shared. // Mutate the filter icon because it needs to be tinted and the resource is shared.
menu.findItem(R.id.action_filter).icon.mutate() menu.findItem(R.id.action_filter).icon?.mutate()
// SY --> // SY -->
menu.findItem(R.id.action_sync_favorites).isVisible = preferences.isHentaiEnabled().get() menu.findItem(R.id.action_sync_favorites).isVisible = preferences.isHentaiEnabled().get()
@@ -472,7 +472,7 @@ class LibraryController(
// Tint icon if there's a filter active // Tint icon if there's a filter active
if (settingsSheet.filters.hasActiveFilters()) { if (settingsSheet.filters.hasActiveFilters()) {
val filterColor = activity!!.getResourceColor(R.attr.colorFilterActive) val filterColor = activity!!.getResourceColor(R.attr.colorFilterActive)
filterItem.icon.setTint(filterColor) filterItem.icon?.setTint(filterColor)
} }
} }
@@ -451,6 +451,7 @@ class LibrarySettingsSheet(
unreadBadge -> preferences.unreadBadge().set((item.checked)) unreadBadge -> preferences.unreadBadge().set((item.checked))
localBadge -> preferences.localBadge().set((item.checked)) localBadge -> preferences.localBadge().set((item.checked))
languageBadge -> preferences.languageBadge().set((item.checked)) languageBadge -> preferences.languageBadge().set((item.checked))
else -> {}
} }
adapter.notifyItemChanged(item) adapter.notifyItemChanged(item)
} }
@@ -473,6 +474,7 @@ class LibrarySettingsSheet(
item.checked = !item.checked item.checked = !item.checked
when (item) { when (item) {
startReadingButton -> preferences.startReadingButton().set((item.checked)) startReadingButton -> preferences.startReadingButton().set((item.checked))
else -> Unit
} }
adapter.notifyItemChanged(item) adapter.notifyItemChanged(item)
} }
@@ -498,6 +500,7 @@ class LibrarySettingsSheet(
when (item) { when (item) {
showTabs -> preferences.categoryTabs().set(item.checked) showTabs -> preferences.categoryTabs().set(item.checked)
showNumberOfItems -> preferences.categoryNumberOfItems().set(item.checked) showNumberOfItems -> preferences.categoryNumberOfItems().set(item.checked)
else -> {}
} }
adapter.notifyItemChanged(item) adapter.notifyItemChanged(item)
} }
@@ -1,23 +0,0 @@
package eu.kanade.tachiyomi.ui.library
@Deprecated("Deprecated in favor for SortModeSetting")
object LibrarySort {
const val ALPHA = 0
const val LAST_READ = 1
const val LAST_CHECKED = 2
const val UNREAD = 3
const val TOTAL = 4
const val LATEST_CHAPTER = 6
const val CHAPTER_FETCH_DATE = 10
const val DATE_ADDED = 8
// SY -->
const val DRAG_AND_DROP = 7
const val TAG_LIST = 9
// SY <--
@Deprecated("Removed in favor of searching by source")
const val SOURCE = 5
}
@@ -520,7 +520,7 @@ class MainActivity : BaseActivity() {
// Binding sometimes isn't actually instantiated yet somehow // Binding sometimes isn't actually instantiated yet somehow
nav?.setOnItemSelectedListener(null) nav?.setOnItemSelectedListener(null)
binding?.toolbar.setNavigationOnClickListener(null) binding?.toolbar?.setNavigationOnClickListener(null)
} }
override fun onBackPressed() { override fun onBackPressed() {
@@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Dialog
import android.os.Bundle
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import uy.kohesive.injekt.injectLazy
class AddDuplicateMangaDialog(bundle: Bundle? = null) : DialogController(bundle) {
private val sourceManager: SourceManager by injectLazy()
private lateinit var libraryManga: Manga
private lateinit var onAddToLibrary: () -> Unit
constructor(
target: Controller,
libraryManga: Manga,
onAddToLibrary: () -> Unit,
) : this() {
targetController = target
this.libraryManga = libraryManga
this.onAddToLibrary = onAddToLibrary
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val source = sourceManager.getOrStub(libraryManga.source)
return MaterialAlertDialogBuilder(activity!!)
.setMessage(activity?.getString(R.string.confirm_manga_add_duplicate, source.name))
.setPositiveButton(activity?.getString(R.string.action_add)) { _, _ ->
onAddToLibrary()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(activity?.getString(R.string.action_show_manga)) { _, _ ->
dismissDialog()
router.pushController(MangaController(libraryManga.id!!).withFadeTransaction())
}
.setCancelable(true)
.create()
}
}
@@ -673,18 +673,8 @@ class MangaController :
private fun showAddDuplicateDialog(newManga: Manga, libraryManga: Manga) { private fun showAddDuplicateDialog(newManga: Manga, libraryManga: Manga) {
activity?.let { activity?.let {
val source = sourceManager.getOrStub(libraryManga.source) AddDuplicateMangaDialog(this, libraryManga) { addToLibrary(newManga) }
MaterialAlertDialogBuilder(it).apply { .showDialog(router)
setMessage(activity?.getString(R.string.confirm_manga_add_duplicate, source.name))
setPositiveButton(activity?.getString(R.string.action_add)) { _, _ ->
addToLibrary(newManga)
}
setNegativeButton(activity?.getString(R.string.action_cancel)) { _, _ -> }
setNeutralButton(activity?.getString(R.string.action_show_manga)) { _, _ ->
router.pushController(MangaController(libraryManga).withFadeTransaction())
}
setCancelable(true)
}.create().show()
} }
} }
@@ -161,6 +161,7 @@ class ChaptersSettingsSheet(
downloaded -> presenter.setDownloadedFilter(newState) downloaded -> presenter.setDownloadedFilter(newState)
unread -> presenter.setUnreadFilter(newState) unread -> presenter.setUnreadFilter(newState)
bookmarked -> presenter.setBookmarkedFilter(newState) bookmarked -> presenter.setBookmarkedFilter(newState)
else -> {}
} }
initModels() initModels()
@@ -19,6 +19,7 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
constructor(update: AppUpdateResult.NewUpdate) : this( constructor(update: AppUpdateResult.NewUpdate) : this(
bundleOf( bundleOf(
BODY_KEY to update.release.info, BODY_KEY to update.release.info,
VERSION_KEY to update.release.version,
RELEASE_URL_KEY to update.release.releaseLink, RELEASE_URL_KEY to update.release.releaseLink,
DOWNLOAD_URL_KEY to update.release.getDownloadLink(), DOWNLOAD_URL_KEY to update.release.getDownloadLink(),
), ),
@@ -36,7 +37,8 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
applicationContext?.let { context -> applicationContext?.let { context ->
// Start download // Start download
val url = args.getString(DOWNLOAD_URL_KEY)!! val url = args.getString(DOWNLOAD_URL_KEY)!!
AppUpdateService.start(context, url) val version = args.getString(VERSION_KEY)
AppUpdateService.start(context, url, version)
} }
} }
.setNeutralButton(R.string.update_check_open) { _, _ -> .setNeutralButton(R.string.update_check_open) { _, _ ->
@@ -55,5 +57,6 @@ class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundl
} }
private const val BODY_KEY = "NewUpdateDialogController.body" private const val BODY_KEY = "NewUpdateDialogController.body"
private const val VERSION_KEY = "NewUpdateDialogController.version"
private const val RELEASE_URL_KEY = "NewUpdateDialogController.release_url" private const val RELEASE_URL_KEY = "NewUpdateDialogController.release_url"
private const val DOWNLOAD_URL_KEY = "NewUpdateDialogController.download_url" private const val DOWNLOAD_URL_KEY = "NewUpdateDialogController.download_url"
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.reader.loader package eu.kanade.tachiyomi.ui.reader.loader
import android.content.Context import android.content.Context
import com.github.junrar.exception.UnsupportedRarV5Exception
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -116,7 +117,11 @@ class ChapterLoader(
when (format) { when (format) {
is LocalSource.Format.Directory -> DirectoryPageLoader(format.file) is LocalSource.Format.Directory -> DirectoryPageLoader(format.file)
is LocalSource.Format.Zip -> ZipPageLoader(format.file) is LocalSource.Format.Zip -> ZipPageLoader(format.file)
is LocalSource.Format.Rar -> RarPageLoader(format.file) is LocalSource.Format.Rar -> try {
RarPageLoader(format.file)
} catch (e: UnsupportedRarV5Exception) {
error(context.getString(R.string.loader_rar5_error))
}
is LocalSource.Format.Epub -> EpubPageLoader(format.file) is LocalSource.Format.Epub -> EpubPageLoader(format.file)
} }
} }
@@ -10,9 +10,9 @@ data class ReaderChapter(val chapter: Chapter) {
var state: State = var state: State =
State.Wait State.Wait
set(value) { set(value) {
field = value field = value
stateRelay.call(value) stateRelay.call(value)
} }
private val stateRelay by lazy { BehaviorRelay.create(state) } private val stateRelay by lazy { BehaviorRelay.create(state) }
@@ -34,27 +34,28 @@ class ReaderSettingsSheet(
behavior.halfExpandedRatio = 0.25f behavior.halfExpandedRatio = 0.25f
val filterTabIndex = getTabViews().indexOf(colorFilterSettings) val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() { binding.tabs.addOnTabSelectedListener(
override fun onTabSelected(tab: TabLayout.Tab?) { object : SimpleTabSelectedListener() {
val isFilterTab = tab?.position == filterTabIndex override fun onTabSelected(tab: TabLayout.Tab?) {
val isFilterTab = tab?.position == filterTabIndex
// Remove dimmed backdrop so color filter changes can be previewed // Remove dimmed backdrop so color filter changes can be previewed
backgroundDimAnimator.run { backgroundDimAnimator.run {
if (isFilterTab) { if (isFilterTab) {
if (animatedFraction < 1f) { if (animatedFraction < 1f) {
start() start()
}
} else if (animatedFraction > 0f) {
reverse()
} }
} else if (animatedFraction > 0f) { }
reverse()
// Hide toolbars
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
} }
} }
},
// Hide toolbars
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
}
}
},
) )
if (showColorFilterSettings) { if (showColorFilterSettings) {
@@ -249,6 +249,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
ZoomStartPosition.LEFT -> setScaleAndCenter(scale, PointF(0F, 0F)) ZoomStartPosition.LEFT -> setScaleAndCenter(scale, PointF(0F, 0F))
ZoomStartPosition.RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0F)) ZoomStartPosition.RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0F))
ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center.also { it?.y = 0F }) ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center.also { it?.y = 0F })
else -> {}
} }
} }
@@ -310,7 +311,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
return true return true
} }
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
this@ReaderPageImageView.onViewClicked() this@ReaderPageImageView.onViewClicked()
return super.onSingleTapConfirmed(e) return super.onSingleTapConfirmed(e)
} }
@@ -1,15 +1,24 @@
package eu.kanade.tachiyomi.ui.reader.viewer package eu.kanade.tachiyomi.ui.reader.viewer
import android.content.Context import android.content.Context
import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.text.bold import androidx.core.text.bold
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isVisible import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.util.system.dpToPx
import kotlin.math.roundToInt
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) { LinearLayout(context, attrs) {
@@ -21,10 +30,11 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
} }
fun bind(transition: ChapterTransition) { fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) {
manga ?: return
when (transition) { when (transition) {
is ChapterTransition.Prev -> bindPrevChapterTransition(transition) is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga)
is ChapterTransition.Next -> bindNextChapterTransition(transition) is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga)
} }
missingChapterWarning(transition) missingChapterWarning(transition)
} }
@@ -32,20 +42,30 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
/** /**
* Binds a previous chapter transition on this view and subscribes to the page load status. * Binds a previous chapter transition on this view and subscribes to the page load status.
*/ */
private fun bindPrevChapterTransition(transition: ChapterTransition) { private fun bindPrevChapterTransition(
val prevChapter = transition.to transition: ChapterTransition,
downloadManager: DownloadManager,
manga: Manga,
) {
val prevChapter = transition.to?.chapter
val hasPrevChapter = prevChapter != null binding.lowerText.isVisible = prevChapter != null
binding.lowerText.isVisible = hasPrevChapter if (prevChapter != null) {
if (hasPrevChapter) {
binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
val isPrevDownloaded = downloadManager.isChapterDownloaded(
prevChapter,
manga,
)
val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader
binding.upperText.text = buildSpannedString { binding.upperText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_previous)) } bold { append(context.getString(R.string.transition_previous)) }
append("\n${prevChapter!!.chapter.name}") append("\n${prevChapter.name}")
if (isPrevDownloaded) addDLImageSpan()
} }
binding.lowerText.text = buildSpannedString { binding.lowerText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_current)) } bold { append(context.getString(R.string.transition_current)) }
append("\n${transition.from.chapter.name}") append("\n${transition.from.chapter.name}")
if (isCurrentDownloaded) addDLImageSpan()
} }
} else { } else {
binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
@@ -56,20 +76,30 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
/** /**
* Binds a next chapter transition on this view and subscribes to the load status. * Binds a next chapter transition on this view and subscribes to the load status.
*/ */
private fun bindNextChapterTransition(transition: ChapterTransition) { private fun bindNextChapterTransition(
val nextChapter = transition.to transition: ChapterTransition,
downloadManager: DownloadManager,
manga: Manga,
) {
val nextChapter = transition.to?.chapter
val hasNextChapter = nextChapter != null binding.lowerText.isVisible = nextChapter != null
binding.lowerText.isVisible = hasNextChapter if (nextChapter != null) {
if (hasNextChapter) {
binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader
val isNextDownloaded = downloadManager.isChapterDownloaded(
nextChapter,
manga,
)
binding.upperText.text = buildSpannedString { binding.upperText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_finished)) } bold { append(context.getString(R.string.transition_finished)) }
append("\n${transition.from.chapter.name}") append("\n${transition.from.chapter.name}")
if (isCurrentDownloaded) addDLImageSpan()
} }
binding.lowerText.text = buildSpannedString { binding.lowerText.text = buildSpannedString {
bold { append(context.getString(R.string.transition_next)) } bold { append(context.getString(R.string.transition_next)) }
append("\n${nextChapter!!.chapter.name}") append("\n${nextChapter.name}")
if (isNextDownloaded) addDLImageSpan()
} }
} else { } else {
binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
@@ -77,6 +107,17 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
} }
} }
private fun SpannableStringBuilder.addDLImageSpan() {
val icon = ContextCompat.getDrawable(context, R.drawable.ic_offline_pin_24dp)?.mutate()
?.apply {
val size = binding.lowerText.textSize + 4.dpToPx
setTint(binding.lowerText.currentTextColor)
setBounds(0, 0, size.roundToInt(), size.roundToInt())
} ?: return
append(" ")
inSpans(ImageSpan(icon)) { append("image") }
}
private fun missingChapterWarning(transition: ChapterTransition) { private fun missingChapterWarning(transition: ChapterTransition) {
if (transition.to == null) { if (transition.to == null) {
binding.warning.isVisible = false binding.warning.isVisible = false
@@ -24,6 +24,7 @@ import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -332,7 +333,7 @@ class PagerPageHolder(
.subscribe({}, {}) .subscribe({}, {})
} }
private fun process(page: ReaderPage, imageStream: InputStream): InputStream { private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
if (!viewer.config.dualPageSplit) { if (!viewer.config.dualPageSplit) {
return imageStream return imageStream
} }
@@ -341,7 +342,7 @@ class PagerPageHolder(
return splitInHalf(imageStream) return splitInHalf(imageStream)
} }
val isDoublePage = ImageUtil.isDoublePage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageStream)
if (!isDoublePage) { if (!isDoublePage) {
return imageStream return imageStream
} }
@@ -61,7 +61,7 @@ class PagerTransitionHolder(
addView(transitionView) addView(transitionView)
addView(pagesContainer) addView(pagesContainer)
transitionView.bind(transition) transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
transition.to?.let { observeStatus(it) } transition.to?.let { observeStatus(it) }
} }
@@ -11,6 +11,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.InsertPage
@@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import uy.kohesive.injekt.injectLazy
import kotlin.math.min import kotlin.math.min
/** /**
@@ -29,6 +31,8 @@ import kotlin.math.min
@Suppress("LeakingThis") @Suppress("LeakingThis")
abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
val downloadManager: DownloadManager by injectLazy()
val scope = MainScope() val scope = MainScope()
/** /**
@@ -52,7 +52,7 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
* Scale listener used to delegate events to the recycler view. * Scale listener used to delegate events to the recycler view.
*/ */
inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean { override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
recycler?.onScaleBegin() recycler?.onScaleBegin()
return true return true
} }
@@ -71,13 +71,13 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
* Fling listener used to delegate events to the recycler view. * Fling listener used to delegate events to the recycler view.
*/ */
inner class FlingListener : GestureDetector.SimpleOnGestureListener() { inner class FlingListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean { override fun onDown(e: MotionEvent): Boolean {
return true return true
} }
override fun onFling( override fun onFling(
e1: MotionEvent?, e1: MotionEvent,
e2: MotionEvent?, e2: MotionEvent,
velocityX: Float, velocityX: Float,
velocityY: Float, velocityY: Float,
): Boolean { ): Boolean {
@@ -23,6 +23,7 @@ import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.io.BufferedInputStream
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -272,12 +273,12 @@ class WebtoonPageHolder(
addSubscription(readImageHeaderSubscription) addSubscription(readImageHeaderSubscription)
} }
private fun process(imageStream: InputStream): InputStream { private fun process(imageStream: BufferedInputStream): InputStream {
if (!viewer.config.dualPageSplit) { if (!viewer.config.dualPageSplit) {
return imageStream return imageStream
} }
val isDoublePage = ImageUtil.isDoublePage(imageStream) val isDoublePage = ImageUtil.isWideImage(imageStream)
if (!isDoublePage) { if (!isDoublePage) {
return imageStream return imageStream
} }
@@ -63,7 +63,7 @@ class WebtoonTransitionHolder(
* Binds the given [transition] with this view holder, subscribing to its state. * Binds the given [transition] with this view holder, subscribing to its state.
*/ */
fun bind(transition: ChapterTransition) { fun bind(transition: ChapterTransition) {
transitionView.bind(transition) transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
transition.to?.let { observeStatus(it, transition) } transition.to?.let { observeStatus(it, transition) }
} }
@@ -11,6 +11,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.WebtoonLayoutManager import androidx.recyclerview.widget.WebtoonLayoutManager
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
@@ -24,6 +25,7 @@ import kotlinx.coroutines.cancel
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -32,6 +34,8 @@ import kotlin.math.min
*/ */
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, private val tapByPage: Boolean = false) : BaseViewer { class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, private val tapByPage: Boolean = false) : BaseViewer {
val downloadManager: DownloadManager by injectLazy()
private val scope = MainScope() private val scope = MainScope()
/** /**
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority import logcat.LogPriority
import java.util.Date
/** /**
* Blank activity with a BiometricPrompt. * Blank activity with a BiometricPrompt.
@@ -39,7 +38,6 @@ class UnlockActivity : BaseActivity() {
) { ) {
super.onAuthenticationSucceeded(activity, result) super.onAuthenticationSucceeded(activity, result)
SecureActivityDelegate.locked = false SecureActivityDelegate.locked = false
preferences.lastAppUnlock().set(Date().time)
finish() finish()
} }
}, },
@@ -219,6 +219,28 @@ class SettingsAdvancedController : SettingsController() {
true true
} }
} }
editTextPreference {
key = Keys.defaultUserAgent
titleRes = R.string.pref_user_agent_string
text = preferences.defaultUserAgent().get()
summary = network.defaultUserAgent
onChange {
activity?.toast(R.string.requires_app_restart)
true
}
}
if (preferences.defaultUserAgent().isSet()) {
preference {
key = "pref_reset_user_agent"
titleRes = R.string.pref_reset_user_agent_string
onClick {
preferences.defaultUserAgent().delete()
activity?.toast(R.string.requires_app_restart)
}
}
}
} }
preferenceCategory { preferenceCategory {
@@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.util.preference.multiSelectListPreference
import eu.kanade.tachiyomi.util.preference.onClick import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@@ -72,6 +73,12 @@ class SettingsDownloadController : SettingsController() {
bindTo(preferences.saveChaptersAsCBZ()) bindTo(preferences.saveChaptersAsCBZ())
titleRes = R.string.save_chapter_as_cbz titleRes = R.string.save_chapter_as_cbz
} }
switchPreference {
bindTo(preferences.splitTallImages())
titleRes = R.string.split_tall_images
summaryRes = R.string.split_tall_images_summary
}
preferenceCategory { preferenceCategory {
titleRes = R.string.pref_category_delete_chapters titleRes = R.string.pref_category_delete_chapters
@@ -121,13 +121,13 @@ class SettingsMainController : SettingsController() {
searchItem.setOnActionExpandListener( searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener { object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
preferences.lastSearchQuerySearchSettings().set("") // reset saved search query preferences.lastSearchQuerySearchSettings().set("") // reset saved search query
router.pushController(SettingsSearchController().withFadeTransaction()) router.pushController(SettingsSearchController().withFadeTransaction())
return true return true
} }
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
return true return true
} }
}, },
@@ -74,11 +74,11 @@ class SettingsSearchController :
searchItem.setOnActionExpandListener( searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener { object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return true return true
} }
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
router.popCurrentController() router.popCurrentController()
return false return false
} }
@@ -166,12 +166,12 @@ class WebViewActivity : BaseActivity() {
menu.findItem(R.id.action_web_back).apply { menu.findItem(R.id.action_web_back).apply {
isEnabled = binding.webview.canGoBack() isEnabled = binding.webview.canGoBack()
icon.setTint(if (binding.webview.canGoBack()) iconTintColor else translucentIconTintColor) icon?.setTint(if (binding.webview.canGoBack()) iconTintColor else translucentIconTintColor)
} }
menu.findItem(R.id.action_web_forward).apply { menu.findItem(R.id.action_web_forward).apply {
isEnabled = binding.webview.canGoForward() isEnabled = binding.webview.canGoForward()
icon.setTint(if (binding.webview.canGoForward()) iconTintColor else translucentIconTintColor) icon?.setTint(if (binding.webview.canGoForward()) iconTintColor else translucentIconTintColor)
} }
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)
@@ -46,8 +46,8 @@ object ChapterRecognition {
// Get chapter title with lower case // Get chapter title with lower case
var name = chapter.name.lowercase() var name = chapter.name.lowercase()
// Remove comma's from chapter. // Remove comma's or hyphens.
name = name.replace(',', '.') name = name.replace(',', '.').replace('-', '.')
// Remove unwanted white spaces. // Remove unwanted white spaces.
unwantedWhiteSpace.findAll(name).let { unwantedWhiteSpace.findAll(name).let {
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending()): (Chapter, Chapter) -> Int { fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending()): (Chapter, Chapter) -> Int {
return when (manga.sorting) { return when (manga.sorting) {
@@ -11,13 +10,13 @@ fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending(
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
} }
Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending) { Manga.CHAPTER_SORTING_NUMBER -> when (sortDescending) {
true -> { c1, c2 -> c2.chapter_number.toString().compareToCaseInsensitiveNaturalOrder(c1.chapter_number.toString()) } true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) }
false -> { c1, c2 -> c1.chapter_number.toString().compareToCaseInsensitiveNaturalOrder(c2.chapter_number.toString()) } false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) }
} }
Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending) { Manga.CHAPTER_SORTING_UPLOAD_DATE -> when (sortDescending) {
true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) } true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) }
false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
} }
else -> throw NotImplementedError("Unimplemented sorting method") else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
} }
} }
@@ -1,12 +1,11 @@
package eu.kanade.tachiyomi.util.storage package eu.kanade.tachiyomi.util.storage
import android.content.Context import android.content.Context
import android.content.Intent import android.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.os.StatFs import android.os.StatFs
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.Hash import eu.kanade.tachiyomi.util.lang.Hash
import java.io.File import java.io.File
@@ -74,21 +73,11 @@ object DiskUtil {
} }
} }
/**
* Scans the given file so that it can be shown in gallery apps, for example.
*/
fun scanMedia(context: Context, file: File) {
scanMedia(context, file.toUri())
}
/** /**
* Scans the given file so that it can be shown in gallery apps, for example. * Scans the given file so that it can be shown in gallery apps, for example.
*/ */
fun scanMedia(context: Context, uri: Uri) { fun scanMedia(context: Context, uri: Uri) {
val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE MediaScannerConnection.scanFile(context, arrayOf(uri.path), null, null)
val mediaScanIntent = Intent(action)
mediaScanIntent.data = uri
context.sendBroadcast(mediaScanIntent)
} }
/** /**
@@ -47,6 +47,7 @@ import logcat.LogPriority
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720 private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720
@@ -166,6 +167,9 @@ fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermissio
} }
} }
val getDisplayMaxHeightInPx: Int
get() = Resources.getSystem().displayMetrics.let { max(it.heightPixels, it.widthPixels) }
/** /**
* Converts to dp. * Converts to dp.
*/ */
@@ -258,7 +262,7 @@ fun Context.openInBrowser(uri: Uri, forceDefaultBrowser: Boolean = false) {
} }
fun Context.defaultBrowserPackageName(): String? { fun Context.defaultBrowserPackageName(): String? {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://")) val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri())
return packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY) return packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
?.activityInfo?.packageName ?.activityInfo?.packageName
?.takeUnless { it in DeviceUtil.invalidDefaultBrowsers } ?.takeUnless { it in DeviceUtil.invalidDefaultBrowsers }
@@ -315,8 +319,8 @@ fun Context.isNightMode(): Boolean {
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=348;drc=e28752c96fc3fb4d3354781469a1af3dbded4898 * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=348;drc=e28752c96fc3fb4d3354781469a1af3dbded4898
*/ */
fun Context.createReaderThemeContext(): Context { fun Context.createReaderThemeContext(): Context {
val prefs = Injekt.get<PreferencesHelper>() val preferences = Injekt.get<PreferencesHelper>()
val isDarkBackground = when (prefs.readerTheme().get()) { val isDarkBackground = when (preferences.readerTheme().get()) {
1, 2 -> true // Black, Gray 1, 2 -> true // Black, Gray
3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default 3 -> applicationContext.isNightMode() // Automatic bg uses activity background by default
else -> false // White else -> false // White
@@ -329,7 +333,7 @@ fun Context.createReaderThemeContext(): Context {
val wrappedContext = ContextThemeWrapper(this, R.style.Theme_Tachiyomi) val wrappedContext = ContextThemeWrapper(this, R.style.Theme_Tachiyomi)
wrappedContext.applyOverrideConfiguration(overrideConf) wrappedContext.applyOverrideConfiguration(overrideConf)
ThemingDelegate.getThemeResIds(prefs.appTheme().get(), prefs.themeDarkAmoled().get()) ThemingDelegate.getThemeResIds(preferences.appTheme().get(), preferences.themeDarkAmoled().get())
.forEach { wrappedContext.theme.applyStyle(it, true) } .forEach { wrappedContext.theme.applyStyle(it, true) }
return wrappedContext return wrappedContext
} }
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.Rect import android.graphics.Rect
@@ -17,16 +18,23 @@ import androidx.core.graphics.alpha
import androidx.core.graphics.applyCanvas import androidx.core.graphics.applyCanvas
import androidx.core.graphics.blue import androidx.core.graphics.blue
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import androidx.core.graphics.get
import androidx.core.graphics.green import androidx.core.graphics.green
import androidx.core.graphics.red import androidx.core.graphics.red
import com.hippo.unifile.UniFile
import logcat.LogPriority
import tachiyomi.decoder.Format import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
import java.net.URLConnection import java.net.URLConnection
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
object ImageUtil { object ImageUtil {
@@ -76,8 +84,7 @@ object ImageUtil {
Format.Webp -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P Format.Webp -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
else -> false else -> false
} }
} catch (e: Exception) { } catch (e: Exception) { /* Do Nothing */ }
}
return false return false
} }
@@ -109,19 +116,12 @@ object ImageUtil {
} }
/** /**
* Check whether the image is a double-page spread * Check whether the image is wide (which we consider a double-page spread).
*
* @return true if the width is greater than the height * @return true if the width is greater than the height
*/ */
fun isDoublePage(imageStream: InputStream): Boolean { fun isWideImage(imageStream: BufferedInputStream): Boolean {
imageStream.mark(imageStream.available() + 1) val options = extractImageOptions(imageStream)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
imageStream.reset()
return options.outWidth > options.outHeight return options.outWidth > options.outHeight
} }
@@ -188,6 +188,111 @@ object ImageUtil {
RIGHT, LEFT RIGHT, LEFT
} }
/**
* Check whether the image is considered a tall image.
*
* @return true if the height:width ratio is greater than 3.
*/
private fun isTallImage(imageStream: InputStream): Boolean {
val options = extractImageOptions(imageStream, resetAfterExtraction = false)
return (options.outHeight / options.outWidth) > 3
}
/**
* Splits tall images to improve performance of reader
*/
fun splitTallImage(imageFile: UniFile, imageFilePath: String): Boolean {
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
return true
}
val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
// Values are stored as they get modified during split loop
val imageHeight = options.outHeight
val imageWidth = options.outWidth
val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
// -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx
val partCount = (imageHeight - 1) / splitHeight + 1
val optimalSplitHeight = imageHeight / partCount
val splitDataList = (0 until partCount).fold(mutableListOf<SplitData>()) { list, index ->
list.apply {
// Only continue if the list is empty or there is image remaining
if (isEmpty() || imageHeight > last().bottomOffset) {
val topOffset = index * optimalSplitHeight
var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
val remainingHeight = imageHeight - (topOffset + outputImageHeight)
// If remaining height is smaller or equal to 1/3th of
// optimal split height then include it in current page
if (remainingHeight <= (optimalSplitHeight / 3)) {
outputImageHeight += remainingHeight
}
add(SplitData(index, topOffset, outputImageHeight))
}
}
}
val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BitmapRegionDecoder.newInstance(imageFile.openInputStream())
} else {
@Suppress("DEPRECATION")
BitmapRegionDecoder.newInstance(imageFile.openInputStream(), false)
}
if (bitmapRegionDecoder == null) {
logcat { "Failed to create new instance of BitmapRegionDecoder" }
return false
}
logcat {
"Splitting image with height of $imageHeight into $partCount part " +
"with estimated ${optimalSplitHeight}px height per split"
}
return try {
splitDataList.forEach { splitData ->
val splitPath = splitImagePath(imageFilePath, splitData.index)
val region = Rect(0, splitData.topOffset, imageWidth, splitData.bottomOffset)
FileOutputStream(splitPath).use { outputStream ->
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
splitBitmap.recycle()
}
logcat {
"Success: Split #${splitData.index + 1} with topOffset=${splitData.topOffset} " +
"height=${splitData.outputImageHeight} bottomOffset=${splitData.bottomOffset}"
}
}
imageFile.delete()
true
} catch (e: Exception) {
// Image splits were not successfully saved so delete them and keep the original image
splitDataList
.map { splitImagePath(imageFilePath, it.index) }
.forEach { File(it).delete() }
logcat(LogPriority.ERROR, e)
false
} finally {
bitmapRegionDecoder.recycle()
}
}
private fun splitImagePath(imageFilePath: String, index: Int) =
imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
data class SplitData(
val index: Int,
val topOffset: Int,
val outputImageHeight: Int,
) {
val bottomOffset = topOffset + outputImageHeight
}
/** /**
* Algorithm for determining what background to accompany a comic/manga page * Algorithm for determining what background to accompany a comic/manga page
*/ */
@@ -212,14 +317,14 @@ object ImageUtil {
val leftOffsetX = left - offsetX val leftOffsetX = left - offsetX
val rightOffsetX = right + offsetX val rightOffsetX = right + offsetX
val topLeftPixel = image.getPixel(left, top) val topLeftPixel = image[left, top]
val topRightPixel = image.getPixel(right, top) val topRightPixel = image[right, top]
val midLeftPixel = image.getPixel(left, midY) val midLeftPixel = image[left, midY]
val midRightPixel = image.getPixel(right, midY) val midRightPixel = image[right, midY]
val topCenterPixel = image.getPixel(midX, top) val topCenterPixel = image[midX, top]
val botLeftPixel = image.getPixel(left, bot) val botLeftPixel = image[left, bot]
val bottomCenterPixel = image.getPixel(midX, bot) val bottomCenterPixel = image[midX, bot]
val botRightPixel = image.getPixel(right, bot) val botRightPixel = image[right, bot]
val topLeftIsDark = topLeftPixel.isDark() val topLeftIsDark = topLeftPixel.isDark()
val topRightIsDark = topRightPixel.isDark() val topRightIsDark = topRightPixel.isDark()
@@ -272,8 +377,8 @@ object ImageUtil {
var whiteStreak = false var whiteStreak = false
val notOffset = x == left || x == right val notOffset = x == left || x == right
inner@ for ((index, y) in (0 until image.height step image.height / 25).withIndex()) { inner@ for ((index, y) in (0 until image.height step image.height / 25).withIndex()) {
val pixel = image.getPixel(x, y) val pixel = image[x, y]
val pixelOff = image.getPixel(x + (if (x < image.width / 2) -offsetX else offsetX), y) val pixelOff = image[x + (if (x < image.width / 2) -offsetX else offsetX), y]
if (pixel.isWhite()) { if (pixel.isWhite()) {
whitePixelsStreak++ whitePixelsStreak++
whitePixels++ whitePixels++
@@ -364,8 +469,8 @@ object ImageUtil {
val topCornersIsDark = topLeftIsDark && topRightIsDark val topCornersIsDark = topLeftIsDark && topRightIsDark
val botCornersIsDark = botLeftIsDark && botRightIsDark val botCornersIsDark = botLeftIsDark && botRightIsDark
val topOffsetCornersIsDark = image.getPixel(leftOffsetX, top).isDark() && image.getPixel(rightOffsetX, top).isDark() val topOffsetCornersIsDark = image[leftOffsetX, top].isDark() && image[rightOffsetX, top].isDark()
val botOffsetCornersIsDark = image.getPixel(leftOffsetX, bot).isDark() && image.getPixel(rightOffsetX, bot).isDark() val botOffsetCornersIsDark = image[leftOffsetX, bot].isDark() && image[rightOffsetX, bot].isDark()
val gradient = when { val gradient = when {
darkBG && botCornersIsWhite -> { darkBG && botCornersIsWhite -> {
@@ -394,15 +499,31 @@ object ImageUtil {
) )
} }
private fun Int.isDark(): Boolean = private fun @receiver:ColorInt Int.isDark(): Boolean =
red < 40 && blue < 40 && green < 40 && alpha > 200 red < 40 && blue < 40 && green < 40 && alpha > 200
private fun Int.isCloseTo(other: Int): Boolean = private fun @receiver:ColorInt Int.isCloseTo(other: Int): Boolean =
abs(red - other.red) < 30 && abs(green - other.green) < 30 && abs(blue - other.blue) < 30 abs(red - other.red) < 30 && abs(green - other.green) < 30 && abs(blue - other.blue) < 30
private fun Int.isWhite(): Boolean = private fun @receiver:ColorInt Int.isWhite(): Boolean =
red + blue + green > 740 red + blue + green > 740
/**
* Used to check an image's dimensions without loading it in the memory.
*/
private fun extractImageOptions(
imageStream: InputStream,
resetAfterExtraction: Boolean = true,
): BitmapFactory.Options {
imageStream.mark(imageStream.available() + 1)
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
if (resetAfterExtraction) imageStream.reset()
return options
}
// Android doesn't include some mappings // Android doesn't include some mappings
private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf( private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf(
// https://issuetracker.google.com/issues/182703810 // https://issuetracker.google.com/issues/182703810
@@ -115,12 +115,13 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor(
.setInterpolator(interpolator) .setInterpolator(interpolator)
.setDuration(duration) .setDuration(duration)
.applySystemAnimatorScale(context) .applySystemAnimatorScale(context)
.setListener(object : AnimatorListenerAdapter() { .setListener(
override fun onAnimationEnd(animation: Animator?) { object : AnimatorListenerAdapter() {
currentAnimator = null override fun onAnimationEnd(animation: Animator) {
postInvalidate() currentAnimator = null
} postInvalidate()
}, }
},
) )
} }
@@ -37,12 +37,13 @@ class ThemesPreference @JvmOverloads constructor(context: Context, attrs: Attrib
recycler?.adapter = adapter recycler?.adapter = adapter
// Retain scroll position on activity recreate after changing theme // Retain scroll position on activity recreate after changing theme
recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() { recycler?.addOnScrollListener(
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { object : RecyclerView.OnScrollListener() {
super.onScrolled(recyclerView, dx, dy) override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
lastScrollPosition = recyclerView.computeHorizontalScrollOffset() super.onScrolled(recyclerView, dx, dy)
} lastScrollPosition = recyclerView.computeHorizontalScrollOffset()
}, }
},
) )
lastScrollPosition?.let { scrollToOffset(it) } lastScrollPosition?.let { scrollToOffset(it) }
} }
@@ -45,11 +45,12 @@ class BottomSheetViewPager @JvmOverloads constructor(
} }
init { init {
addOnPageChangeListener(object : SimpleOnPageChangeListener() { addOnPageChangeListener(
override fun onPageSelected(position: Int) { object : SimpleOnPageChangeListener() {
requestLayout() override fun onPageSelected(position: Int) {
} requestLayout()
}, }
},
) )
} }
} }
+31 -27
View File
@@ -31,13 +31,13 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.Hitomi import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.util.preference.minusAssign import eu.kanade.tachiyomi.util.preference.minusAssign
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.logcat
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE import exh.log.xLogE
import exh.log.xLogW import exh.log.xLogW
@@ -308,36 +308,40 @@ object EXHMigrations {
} }
} }
if (oldVersion under 20) { if (oldVersion under 20) {
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0) try {
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true) val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0 /* ALPHABETICAL */)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
val newSortingMode = when (oldSortingMode) { val newSortingMode = when (oldSortingMode) {
LibrarySort.ALPHA -> SortModeSetting.ALPHABETICAL 0 -> SortModeSetting.ALPHABETICAL
LibrarySort.LAST_READ -> SortModeSetting.LAST_READ 1 -> SortModeSetting.LAST_READ
LibrarySort.LAST_CHECKED -> SortModeSetting.LAST_CHECKED 2 -> SortModeSetting.LAST_CHECKED
LibrarySort.UNREAD -> SortModeSetting.UNREAD 3 -> SortModeSetting.UNREAD
LibrarySort.TOTAL -> SortModeSetting.TOTAL_CHAPTERS 4 -> SortModeSetting.TOTAL_CHAPTERS
LibrarySort.LATEST_CHAPTER -> SortModeSetting.LATEST_CHAPTER 6 -> SortModeSetting.LATEST_CHAPTER
LibrarySort.CHAPTER_FETCH_DATE -> SortModeSetting.DATE_FETCHED 7 -> SortModeSetting.DRAG_AND_DROP
LibrarySort.DATE_ADDED -> SortModeSetting.DATE_ADDED 8 -> SortModeSetting.DATE_ADDED
LibrarySort.DRAG_AND_DROP -> SortModeSetting.DRAG_AND_DROP 9 -> SortModeSetting.TAG_LIST
LibrarySort.TAG_LIST -> SortModeSetting.TAG_LIST 10 -> SortModeSetting.DATE_FETCHED
else -> SortModeSetting.ALPHABETICAL else -> SortModeSetting.ALPHABETICAL
} }
val newSortingDirection = when (oldSortingDirection) { val newSortingDirection = when (oldSortingDirection) {
true -> SortDirectionSetting.ASCENDING true -> SortDirectionSetting.ASCENDING
else -> SortDirectionSetting.DESCENDING else -> SortDirectionSetting.DESCENDING
} }
prefs.edit(commit = true) { prefs.edit(commit = true) {
remove(PreferenceKeys.librarySortingMode) remove(PreferenceKeys.librarySortingMode)
remove(PreferenceKeys.librarySortingDirection) remove(PreferenceKeys.librarySortingDirection)
} }
prefs.edit { prefs.edit {
putString(PreferenceKeys.librarySortingMode, newSortingMode.name) putString(PreferenceKeys.librarySortingMode, newSortingMode.name)
putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name) putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
}
} catch (e: Exception) {
logcat(throwable = e) { "Already done migration" }
} }
} }
if (oldVersion under 21) { if (oldVersion under 21) {
+6 -5
View File
@@ -6,31 +6,32 @@ import exh.eh.tags.Character
import exh.eh.tags.Cosplayer import exh.eh.tags.Cosplayer
import exh.eh.tags.Female import exh.eh.tags.Female
import exh.eh.tags.Group import exh.eh.tags.Group
import exh.eh.tags.Group2
import exh.eh.tags.Language import exh.eh.tags.Language
import exh.eh.tags.Male import exh.eh.tags.Male
import exh.eh.tags.Mixed import exh.eh.tags.Mixed
import exh.eh.tags.Other import exh.eh.tags.Other
import exh.eh.tags.Parody import exh.eh.tags.Parody
import exh.eh.tags.ReClass import exh.eh.tags.Reclass
object EHTags { object EHTags {
fun getAllTags(): List<String> = listOf(
fun getAllTags() = listOf(
Female.getTags(), Female.getTags(),
Male.getTags(), Male.getTags(),
Language.getTags(), Language.getTags(),
ReClass.getTags(), Reclass.getTags(),
Mixed.getTags(), Mixed.getTags(),
Other.getTags(), Other.getTags(),
Cosplayer.getTags(), Cosplayer.getTags(),
Parody.getTags(), Parody.getTags(),
Character.getTags(), Character.getTags(),
Group.getTags(), Group.getTags(),
Group2.getTags(),
Artist.getTags(), Artist.getTags(),
Artist2.getTags(), Artist2.getTags(),
).flatten().flatten() ).flatten().flatten()
fun getNamespaces0Tags() = listOf( fun getNamespaces(): List<String> = listOf(
"reclass", "reclass",
"language", "language",
"parody", "parody",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+77 -2
View File
@@ -1,46 +1,121 @@
package exh.eh.tags package exh.eh.tags
object Cosplayer : TagList { object Cosplayer : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf( "cosplayer:abaoyeshituniang",
"cosplayer:ai lei jiang",
"cosplayer:akane araragi", "cosplayer:akane araragi",
"cosplayer:akemi101xoxo",
"cosplayer:aleksandra bodler", "cosplayer:aleksandra bodler",
"cosplayer:alicekyo",
"cosplayer:alin ma",
"cosplayer:alisa kiss",
"cosplayer:alodia gosiengfiao",
"cosplayer:amanda welp",
"cosplayer:anastasia komori",
"cosplayer:aokotan", "cosplayer:aokotan",
"cosplayer:arisa mizuhara",
"cosplayer:arty huang", "cosplayer:arty huang",
"cosplayer:ashiya noriko",
"cosplayer:atsuki", "cosplayer:atsuki",
"cosplayer:ayaka matsunaga",
"cosplayer:bailey jay",
"cosplayer:banbanko",
"cosplayer:bishoujomom", "cosplayer:bishoujomom",
"cosplayer:bobbi starr",
"cosplayer:carry key", "cosplayer:carry key",
"cosplayer:charles dera",
"cosplayer:chunmomo", "cosplayer:chunmomo",
"cosplayer:comonun",
"cosplayer:danielle vedovelli",
"cosplayer:darling cute",
"cosplayer:dillion harper",
"cosplayer:donnaloli",
"cosplayer:evenink",
"cosplayer:fe galvao",
"cosplayer:fluffy nemu",
"cosplayer:franxcos",
"cosplayer:g44 wa kizutsukanai",
"cosplayer:gumiho hannya", "cosplayer:gumiho hannya",
"cosplayer:hane ame", "cosplayer:hane ame",
"cosplayer:helly von valentine",
"cosplayer:hessakai",
"cosplayer:hey shika",
"cosplayer:higurashi rin",
"cosplayer:himeecosplay",
"cosplayer:hinaughtya", "cosplayer:hinaughtya",
"cosplayer:holly wolf", "cosplayer:holly wolf",
"cosplayer:imokawa naoko",
"cosplayer:iori moe", "cosplayer:iori moe",
"cosplayer:ishikawa asami",
"cosplayer:jaycee",
"cosplayer:jessica nigri",
"cosplayer:jill", "cosplayer:jill",
"cosplayer:kalinka fox", "cosplayer:kalinka fox",
"cosplayer:kanda midori",
"cosplayer:kaya huang", "cosplayer:kaya huang",
"cosplayer:kimmie mi",
"cosplayer:kitami eri",
"cosplayer:koyama rikako",
"cosplayer:kqueentsun",
"cosplayer:kurumi.",
"cosplayer:kuuko w", "cosplayer:kuuko w",
"cosplayer:lenfried", "cosplayer:lenfried",
"cosplayer:lewdoart",
"cosplayer:lovelyspacekitten",
"cosplayer:marie-claude bourbonnais",
"cosplayer:meikoui",
"cosplayer:miih cosplay", "cosplayer:miih cosplay",
"cosplayer:mikomin", "cosplayer:mikomin",
"cosplayer:misa daidai", "cosplayer:misa daidai",
"cosplayer:mizhimaoqiu",
"cosplayer:mochizuki eiko",
"cosplayer:momoiro reku", "cosplayer:momoiro reku",
"cosplayer:momokun", "cosplayer:momokun",
"cosplayer:nadyasonika", "cosplayer:nadyasonika",
"cosplayer:neroko kaigan",
"cosplayer:niannian d",
"cosplayer:nicky",
"cosplayer:niyeye",
"cosplayer:nora fawn", "cosplayer:nora fawn",
"cosplayer:octokuro", "cosplayer:octokuro",
"cosplayer:oichi", "cosplayer:oichi",
"cosplayer:okada yui",
"cosplayer:penkarui",
"cosplayer:punk macarroni",
"cosplayer:queenie",
"cosplayer:rio-chan",
"cosplayer:rioko", "cosplayer:rioko",
"cosplayer:rocksy light", "cosplayer:rocksy light",
"cosplayer:rolyatistaylor",
"cosplayer:saiwari ph",
"cosplayer:saku", "cosplayer:saku",
"cosplayer:sakurai hinoki", "cosplayer:sakurai hinoki",
"cosplayer:sandykuroneko",
"cosplayer:saotome love",
"cosplayer:sawaka",
"cosplayer:sexyflowerwater",
"cosplayer:shibuya kaho",
"cosplayer:shiro kitsune", "cosplayer:shiro kitsune",
"cosplayer:siao ding", "cosplayer:siao ding",
"cosplayer:smoettii", "cosplayer:smoettii",
"cosplayer:sneaky",
"cosplayer:soa lianna",
"cosplayer:son yeeun",
"cosplayer:sunnyvier",
"cosplayer:tanaka hitomi",
"cosplayer:tenleid",
"cosplayer:todopokie",
"cosplayer:tsubaki zakuro",
"cosplayer:tsuki desu",
"cosplayer:tyouduki maryou",
"cosplayer:uchida mahiro",
"cosplayer:valery himera", "cosplayer:valery himera",
"cosplayer:velvet", "cosplayer:velvet",
"cosplayer:wildhoney423", "cosplayer:wildhoney423",
"cosplayer:yume", "cosplayer:yume",
"cosplayer:yunocos69",
"cosplayer:yuzupyon", "cosplayer:yuzupyon",
"cosplayer:zara durose",
) )
} }
+75 -2
View File
@@ -1,11 +1,13 @@
package exh.eh.tags package exh.eh.tags
object Female : TagList { object Female : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"female:abortion", "female:abortion",
"female:absorption", "female:absorption",
"female:adventitious mouth",
"female:adventitious penis",
"female:adventitious vagina", "female:adventitious vagina",
"female:afro",
"female:age progression", "female:age progression",
"female:age regression", "female:age regression",
"female:ahegao", "female:ahegao",
@@ -17,10 +19,13 @@ object Female : TagList {
"female:anal birth", "female:anal birth",
"female:anal intercourse", "female:anal intercourse",
"female:anal prolapse", "female:anal prolapse",
"female:analphagia",
"female:angel", "female:angel",
"female:animal on animal",
"female:animal on furry", "female:animal on furry",
"female:animegao", "female:animegao",
"female:anorexic", "female:anorexic",
"female:apparel bukkake",
"female:apron", "female:apron",
"female:armpit licking", "female:armpit licking",
"female:armpit sex", "female:armpit sex",
@@ -33,8 +38,10 @@ object Female : TagList {
"female:bald", "female:bald",
"female:ball sucking", "female:ball sucking",
"female:balljob", "female:balljob",
"female:balls expansion",
"female:bandages", "female:bandages",
"female:bandaid", "female:bandaid",
"female:bat girl",
"female:bbw", "female:bbw",
"female:bdsm", "female:bdsm",
"female:bear", "female:bear",
@@ -47,6 +54,8 @@ object Female : TagList {
"female:big balls", "female:big balls",
"female:big breasts", "female:big breasts",
"female:big clit", "female:big clit",
"female:big lips",
"female:big muscles",
"female:big nipples", "female:big nipples",
"female:big penis", "female:big penis",
"female:big vagina", "female:big vagina",
@@ -68,6 +77,7 @@ object Female : TagList {
"female:bodystocking", "female:bodystocking",
"female:bodysuit", "female:bodysuit",
"female:bondage", "female:bondage",
"female:braces",
"female:brain fuck", "female:brain fuck",
"female:breast expansion", "female:breast expansion",
"female:breast feeding", "female:breast feeding",
@@ -80,6 +90,7 @@ object Female : TagList {
"female:butler", "female:butler",
"female:cannibalism", "female:cannibalism",
"female:cashier", "female:cashier",
"female:cat",
"female:catfight", "female:catfight",
"female:catgirl", "female:catgirl",
"female:cbt", "female:cbt",
@@ -95,11 +106,16 @@ object Female : TagList {
"female:christmas", "female:christmas",
"female:clamp", "female:clamp",
"female:clit growth", "female:clit growth",
"female:clit insertion",
"female:clit stimulation", "female:clit stimulation",
"female:clone", "female:clone",
"female:closed eyes",
"female:clothed male nude female", "female:clothed male nude female",
"female:clothed paizuri",
"female:clown",
"female:coach", "female:coach",
"female:cock ring", "female:cock ring",
"female:cockphagia",
"female:cockslapping", "female:cockslapping",
"female:collar", "female:collar",
"female:condom", "female:condom",
@@ -115,11 +131,13 @@ object Female : TagList {
"female:crossdressing", "female:crossdressing",
"female:crotch tattoo", "female:crotch tattoo",
"female:crown", "female:crown",
"female:crying",
"female:cum bath", "female:cum bath",
"female:cum in eye", "female:cum in eye",
"female:cum swap", "female:cum swap",
"female:cumflation", "female:cumflation",
"female:cunnilingus", "female:cunnilingus",
"female:cuntbusting",
"female:dark nipples", "female:dark nipples",
"female:dark sclera", "female:dark sclera",
"female:dark skin", "female:dark skin",
@@ -129,6 +147,8 @@ object Female : TagList {
"female:deer girl", "female:deer girl",
"female:defloration", "female:defloration",
"female:demon girl", "female:demon girl",
"female:denki anma",
"female:detached sleeves",
"female:diaper", "female:diaper",
"female:dick growth", "female:dick growth",
"female:dickgirl on dickgirl", "female:dickgirl on dickgirl",
@@ -138,6 +158,8 @@ object Female : TagList {
"female:dog", "female:dog",
"female:dog girl", "female:dog girl",
"female:doll joints", "female:doll joints",
"female:dolphin",
"female:domination loss",
"female:donkey", "female:donkey",
"female:double anal", "female:double anal",
"female:double blowjob", "female:double blowjob",
@@ -169,6 +191,7 @@ object Female : TagList {
"female:farting", "female:farting",
"female:females only", "female:females only",
"female:femdom", "female:femdom",
"female:fff threesome",
"female:fft threesome", "female:fft threesome",
"female:filming", "female:filming",
"female:fingering", "female:fingering",
@@ -183,11 +206,13 @@ object Female : TagList {
"female:foot insertion", "female:foot insertion",
"female:foot licking", "female:foot licking",
"female:footjob", "female:footjob",
"female:forced exposure",
"female:forniphilia", "female:forniphilia",
"female:fox", "female:fox",
"female:fox girl", "female:fox girl",
"female:freckles", "female:freckles",
"female:frog", "female:frog",
"female:frog girl",
"female:frottage", "female:frottage",
"female:fundoshi", "female:fundoshi",
"female:furry", "female:furry",
@@ -199,11 +224,15 @@ object Female : TagList {
"female:gender change", "female:gender change",
"female:gender morph", "female:gender morph",
"female:ghost", "female:ghost",
"female:giant sperm",
"female:giantess", "female:giantess",
"female:gigantic breasts", "female:gigantic breasts",
"female:gijinka",
"female:giraffe girl",
"female:glasses", "female:glasses",
"female:glory hole", "female:glory hole",
"female:gloves", "female:gloves",
"female:goblin",
"female:gokkun", "female:gokkun",
"female:gothic lolita", "female:gothic lolita",
"female:granddaughter", "female:granddaughter",
@@ -213,12 +242,14 @@ object Female : TagList {
"female:guro", "female:guro",
"female:gyaru", "female:gyaru",
"female:gymshorts", "female:gymshorts",
"female:haigure",
"female:hair buns", "female:hair buns",
"female:hairjob", "female:hairjob",
"female:hairy", "female:hairy",
"female:hairy armpits", "female:hairy armpits",
"female:handicapped", "female:handicapped",
"female:handjob", "female:handjob",
"female:hanging",
"female:harem", "female:harem",
"female:harness", "female:harness",
"female:harpy", "female:harpy",
@@ -237,18 +268,24 @@ object Female : TagList {
"female:human cattle", "female:human cattle",
"female:human on furry", "female:human on furry",
"female:humiliation", "female:humiliation",
"female:hyena girl",
"female:impregnation", "female:impregnation",
"female:incest", "female:incest",
"female:infantilism", "female:infantilism",
"female:inflation", "female:inflation",
"female:insect", "female:insect",
"female:insect girl", "female:insect girl",
"female:internal urination",
"female:inverted nipples", "female:inverted nipples",
"female:invisible", "female:invisible",
"female:kangaroo",
"female:kappa",
"female:kemonomimi", "female:kemonomimi",
"female:kigurumi pajama", "female:kigurumi pajama",
"female:kimono", "female:kimono",
"female:kindergarten uniform",
"female:kissing", "female:kissing",
"female:kneepit sex",
"female:kunoichi", "female:kunoichi",
"female:lab coat", "female:lab coat",
"female:lactation", "female:lactation",
@@ -258,6 +295,7 @@ object Female : TagList {
"female:layer cake", "female:layer cake",
"female:leash", "female:leash",
"female:leg lock", "female:leg lock",
"female:legjob",
"female:leotard", "female:leotard",
"female:lingerie", "female:lingerie",
"female:lioness", "female:lioness",
@@ -266,7 +304,10 @@ object Female : TagList {
"female:lolicon", "female:lolicon",
"female:long tongue", "female:long tongue",
"female:low bestiality", "female:low bestiality",
"female:low guro",
"female:low lolicon", "female:low lolicon",
"female:low scat",
"female:low smegma",
"female:machine", "female:machine",
"female:maggot", "female:maggot",
"female:magical girl", "female:magical girl",
@@ -290,20 +331,25 @@ object Female : TagList {
"female:minigirl", "female:minigirl",
"female:monkey", "female:monkey",
"female:monkey girl", "female:monkey girl",
"female:monoeye",
"female:monster girl", "female:monster girl",
"female:moral degeneration", "female:moral degeneration",
"female:mother", "female:mother",
"female:mouse", "female:mouse",
"female:mouse girl", "female:mouse girl",
"female:mouth mask", "female:mouth mask",
"female:multimouth blowjob",
"female:multiple arms", "female:multiple arms",
"female:multiple assjob", "female:multiple assjob",
"female:multiple breasts", "female:multiple breasts",
"female:multiple footjob", "female:multiple footjob",
"female:multiple handjob", "female:multiple handjob",
"female:multiple nipples",
"female:multiple orgasms", "female:multiple orgasms",
"female:multiple paizuri", "female:multiple paizuri",
"female:multiple penises", "female:multiple penises",
"female:multiple straddling",
"female:multiple vaginas",
"female:muscle", "female:muscle",
"female:muscle growth", "female:muscle growth",
"female:nakadashi", "female:nakadashi",
@@ -315,6 +361,7 @@ object Female : TagList {
"female:nipple birth", "female:nipple birth",
"female:nipple expansion", "female:nipple expansion",
"female:nipple fuck", "female:nipple fuck",
"female:nipple stimulation",
"female:nose fuck", "female:nose fuck",
"female:nose hook", "female:nose hook",
"female:nun", "female:nun",
@@ -327,12 +374,16 @@ object Female : TagList {
"female:oppai loli", "female:oppai loli",
"female:orc", "female:orc",
"female:orgasm denial", "female:orgasm denial",
"female:otter girl",
"female:oyakodon",
"female:paizuri", "female:paizuri",
"female:panda girl",
"female:pantyhose", "female:pantyhose",
"female:pantyjob", "female:pantyjob",
"female:parasite", "female:parasite",
"female:pasties", "female:pasties",
"female:penis birth", "female:penis birth",
"female:personality excretion",
"female:petplay", "female:petplay",
"female:petrification", "female:petrification",
"female:phimosis", "female:phimosis",
@@ -364,13 +415,16 @@ object Female : TagList {
"female:rape", "female:rape",
"female:real doll", "female:real doll",
"female:reptile", "female:reptile",
"female:retractable penis",
"female:rhinoceros", "female:rhinoceros",
"female:rimjob", "female:rimjob",
"female:robot", "female:robot",
"female:ryona", "female:ryona",
"female:saliva", "female:saliva",
"female:sarashi",
"female:scar", "female:scar",
"female:scat", "female:scat",
"female:scat insertion",
"female:school gym uniform", "female:school gym uniform",
"female:school swimsuit", "female:school swimsuit",
"female:schoolboy uniform", "female:schoolboy uniform",
@@ -378,22 +432,29 @@ object Female : TagList {
"female:scrotal lingerie", "female:scrotal lingerie",
"female:selfcest", "female:selfcest",
"female:sex toys", "female:sex toys",
"female:shapening",
"female:shared senses", "female:shared senses",
"female:shark", "female:shark",
"female:shark girl",
"female:shaved head",
"female:sheep", "female:sheep",
"female:sheep girl", "female:sheep girl",
"female:shemale", "female:shemale",
"female:shibari", "female:shibari",
"female:shimaidon",
"female:shimapan", "female:shimapan",
"female:shrinking", "female:shrinking",
"female:sister", "female:sister",
"female:skinsuit", "female:skinsuit",
"female:skunk girl",
"female:slave", "female:slave",
"female:sleeping", "female:sleeping",
"female:slime", "female:slime",
"female:slime girl", "female:slime girl",
"female:slug", "female:slug",
"female:small breasts", "female:small breasts",
"female:small penis",
"female:smalldom",
"female:smegma", "female:smegma",
"female:smell", "female:smell",
"female:smoking", "female:smoking",
@@ -401,6 +462,7 @@ object Female : TagList {
"female:snake", "female:snake",
"female:snake girl", "female:snake girl",
"female:snuff", "female:snuff",
"female:sockjob",
"female:sole dickgirl", "female:sole dickgirl",
"female:sole female", "female:sole female",
"female:solo action", "female:solo action",
@@ -412,8 +474,10 @@ object Female : TagList {
"female:squirting", "female:squirting",
"female:ssbbw", "female:ssbbw",
"female:stewardess", "female:stewardess",
"female:stirrup legwear",
"female:stockings", "female:stockings",
"female:stomach deformation", "female:stomach deformation",
"female:straitjacket",
"female:strap-on", "female:strap-on",
"female:stretching", "female:stretching",
"female:stuck in wall", "female:stuck in wall",
@@ -427,10 +491,13 @@ object Female : TagList {
"female:table masturbation", "female:table masturbation",
"female:tail", "female:tail",
"female:tail plug", "female:tail plug",
"female:tailjob",
"female:tailphagia",
"female:tall girl", "female:tall girl",
"female:tanlines", "female:tanlines",
"female:teacher", "female:teacher",
"female:tentacles", "female:tentacles",
"female:thick eyebrows",
"female:thigh high boots", "female:thigh high boots",
"female:tiara", "female:tiara",
"female:tickling", "female:tickling",
@@ -443,11 +510,13 @@ object Female : TagList {
"female:tracksuit", "female:tracksuit",
"female:trampling", "female:trampling",
"female:transformation", "female:transformation",
"female:transparent clothing",
"female:tribadism", "female:tribadism",
"female:triple anal", "female:triple anal",
"female:triple penetration", "female:triple penetration",
"female:triple vaginal", "female:triple vaginal",
"female:ttf threesome", "female:ttf threesome",
"female:ttt threesome",
"female:tube", "female:tube",
"female:turtle", "female:turtle",
"female:tutor", "female:tutor",
@@ -472,7 +541,10 @@ object Female : TagList {
"female:waiter", "female:waiter",
"female:waitress", "female:waitress",
"female:weight gain", "female:weight gain",
"female:wet clothes",
"female:whale",
"female:whip", "female:whip",
"female:wingjob",
"female:wings", "female:wings",
"female:witch", "female:witch",
"female:wolf", "female:wolf",
@@ -484,6 +556,7 @@ object Female : TagList {
"female:x-ray", "female:x-ray",
"female:yandere", "female:yandere",
"female:yuri", "female:yuri",
"female:zebra",
"female:zombie", "female:zombie",
) )
} }
+71 -344
View File
@@ -1,8 +1,7 @@
package exh.eh.tags package exh.eh.tags
object Group : TagList { object Group : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"group:---", "group:---",
"group:... mou ii desu.", "group:... mou ii desu.",
"group:...with my tears.", "group:...with my tears.",
@@ -72,6 +71,7 @@ object Group : TagList {
"group:38.5 c", "group:38.5 c",
"group:3d adult comics", "group:3d adult comics",
"group:3d bdsm dungeon", "group:3d bdsm dungeon",
"group:3d live",
"group:3d pose shuu", "group:3d pose shuu",
"group:3darlings", "group:3darlings",
"group:3dfiends", "group:3dfiends",
@@ -402,6 +402,7 @@ object Group : TagList {
"group:anakichi", "group:anakichi",
"group:anal crisis", "group:anal crisis",
"group:analog store", "group:analog store",
"group:analyst freedom",
"group:ananwanco", "group:ananwanco",
"group:anata o aishite yamazu", "group:anata o aishite yamazu",
"group:anatawo haijindesu", "group:anatawo haijindesu",
@@ -514,6 +515,7 @@ object Group : TagList {
"group:arikui paradise", "group:arikui paradise",
"group:arinko.", "group:arinko.",
"group:arisan-antenna", "group:arisan-antenna",
"group:aristogracy",
"group:ariyon dou", "group:ariyon dou",
"group:ark emerald", "group:ark emerald",
"group:ark nantoka", "group:ark nantoka",
@@ -586,6 +588,7 @@ object Group : TagList {
"group:asunaro-shiki bakudan", "group:asunaro-shiki bakudan",
"group:at 1level", "group:at 1level",
"group:at down", "group:at down",
"group:at e.com",
"group:at kenkyuujo", "group:at kenkyuujo",
"group:at m-gun", "group:at m-gun",
"group:at mztm", "group:at mztm",
@@ -690,6 +693,7 @@ object Group : TagList {
"group:balklash.", "group:balklash.",
"group:ball colon s", "group:ball colon s",
"group:ballet cohort", "group:ballet cohort",
"group:bambi",
"group:banana koubou", "group:banana koubou",
"group:banana musume", "group:banana musume",
"group:banana no kawa", "group:banana no kawa",
@@ -752,6 +756,7 @@ object Group : TagList {
"group:bery manjhr", "group:bery manjhr",
"group:bessungou", "group:bessungou",
"group:beta houkai", "group:beta houkai",
"group:beta kikaku hanbai",
"group:beta na kanzume", "group:beta na kanzume",
"group:betaneta", "group:betaneta",
"group:betsu ni suki janai yo", "group:betsu ni suki janai yo",
@@ -829,6 +834,7 @@ object Group : TagList {
"group:blue topaz", "group:blue topaz",
"group:blue24", "group:blue24",
"group:bluebox", "group:bluebox",
"group:bluehistory",
"group:bluehot plus", "group:bluehot plus",
"group:bluejelly", "group:bluejelly",
"group:bluemage", "group:bluemage",
@@ -1038,6 +1044,7 @@ object Group : TagList {
"group:chikutakudoh", "group:chikutakudoh",
"group:chikuwano kimochi", "group:chikuwano kimochi",
"group:chikyuugai seimeitai mokyu", "group:chikyuugai seimeitai mokyu",
"group:child box",
"group:childmaid", "group:childmaid",
"group:childwife", "group:childwife",
"group:chill-out", "group:chill-out",
@@ -1143,6 +1150,7 @@ object Group : TagList {
"group:circle yuki", "group:circle yuki",
"group:circlesprocket", "group:circlesprocket",
"group:citoron soft", "group:citoron soft",
"group:citric acid1350",
"group:clammbon", "group:clammbon",
"group:cleanliness.", "group:cleanliness.",
"group:cleari tei", "group:cleari tei",
@@ -1408,6 +1416,7 @@ object Group : TagList {
"group:dolcecanto", "group:dolcecanto",
"group:dollproject", "group:dollproject",
"group:dom joshidan", "group:dom joshidan",
"group:donaora",
"group:donburi beya", "group:donburi beya",
"group:dondondon", "group:dondondon",
"group:dont understand", "group:dont understand",
@@ -1458,6 +1467,7 @@ object Group : TagList {
"group:drill", "group:drill",
"group:drill biyori", "group:drill biyori",
"group:dro-ya", "group:dro-ya",
"group:dropwortbell",
"group:drug slash tag slash 21", "group:drug slash tag slash 21",
"group:dual beat", "group:dual beat",
"group:dualtail", "group:dualtail",
@@ -1531,6 +1541,7 @@ object Group : TagList {
"group:enshu spirits", "group:enshu spirits",
"group:entgegen", "group:entgegen",
"group:enuhuo", "group:enuhuo",
"group:enyidou",
"group:ephese", "group:ephese",
"group:epic lust", "group:epic lust",
"group:epicureansyndrome", "group:epicureansyndrome",
@@ -1726,6 +1737,7 @@ object Group : TagList {
"group:fuwa fuwa pinkchan", "group:fuwa fuwa pinkchan",
"group:fuwamoko honpo", "group:fuwamoko honpo",
"group:fuwaten", "group:fuwaten",
"group:fuyuzora izumo",
"group:fuzukikai", "group:fuzukikai",
"group:g area", "group:g area",
"group:g equals kundow", "group:g equals kundow",
@@ -1768,9 +1780,11 @@ object Group : TagList {
"group:garaku dusk", "group:garaku dusk",
"group:garakuta shoujo", "group:garakuta shoujo",
"group:garakuta-ya", "group:garakuta-ya",
"group:garasu hokou",
"group:garbage", "group:garbage",
"group:gardening bulldog", "group:gardening bulldog",
"group:garnet-works.", "group:garnet-works.",
"group:garunansa mk-2",
"group:garyuh-chitai", "group:garyuh-chitai",
"group:gas ketsu jinsei", "group:gas ketsu jinsei",
"group:gasshuukoku netamekoru", "group:gasshuukoku netamekoru",
@@ -1885,6 +1899,7 @@ object Group : TagList {
"group:gpen", "group:gpen",
"group:gpx", "group:gpx",
"group:grand cross", "group:grand cross",
"group:grand plie.",
"group:graphic l", "group:graphic l",
"group:gravidan", "group:gravidan",
"group:great acta", "group:great acta",
@@ -1987,6 +2002,9 @@ object Group : TagList {
"group:halleluya.", "group:halleluya.",
"group:hallenchi planet", "group:hallenchi planet",
"group:hallucigenia", "group:hallucigenia",
)
override fun getTags2(): List<String> = listOf(
"group:halopack", "group:halopack",
"group:ham string", "group:ham string",
"group:ham.", "group:ham.",
@@ -2002,13 +2020,12 @@ object Group : TagList {
"group:hamurabi-dou", "group:hamurabi-dou",
"group:hamustar", "group:hamustar",
"group:hamusuta-nonikomi", "group:hamusuta-nonikomi",
"group:hana ni arashi.",
"group:hana tabako", "group:hana tabako",
)
override fun getTags2() = listOf(
"group:hana to ribon", "group:hana to ribon",
"group:hana x mezo", "group:hana x mezo",
"group:hanafubuki gorilla", "group:hanafubuki gorilla",
"group:hanahubu",
"group:hanaji koubou", "group:hanaji koubou",
"group:hanamachi shimaiten", "group:hanamachi shimaiten",
"group:hanamaru mugen gym", "group:hanamaru mugen gym",
@@ -2037,6 +2054,7 @@ object Group : TagList {
"group:happo ryuu", "group:happo ryuu",
"group:happy birthday", "group:happy birthday",
"group:happy flame time", "group:happy flame time",
"group:happy log",
"group:happy strawberry", "group:happy strawberry",
"group:happydrop", "group:happydrop",
"group:happywest", "group:happywest",
@@ -2136,6 +2154,7 @@ object Group : TagList {
"group:hetaretch", "group:hetaretch",
"group:hetaruya", "group:hetaruya",
"group:heya no sumi.", "group:heya no sumi.",
"group:hgt labo",
"group:hi at skip", "group:hi at skip",
"group:hi plus us", "group:hi plus us",
"group:hi-b", "group:hi-b",
@@ -2199,6 +2218,7 @@ object Group : TagList {
"group:hiroq", "group:hiroq",
"group:hirouguma", "group:hirouguma",
"group:hisagoya", "group:hisagoya",
"group:hisou and anchoku",
"group:hisuitei", "group:hisuitei",
"group:hisyoku no tansansui", "group:hisyoku no tansansui",
"group:hito no fundoshi", "group:hito no fundoshi",
@@ -2229,6 +2249,7 @@ object Group : TagList {
"group:hokoushayou shingou", "group:hokoushayou shingou",
"group:hokuroza", "group:hokuroza",
"group:holiday school", "group:holiday school",
"group:home not found",
"group:homepie koubou", "group:homepie koubou",
"group:homerun chaya", "group:homerun chaya",
"group:homuras r comics", "group:homuras r comics",
@@ -2248,6 +2269,7 @@ object Group : TagList {
"group:hook", "group:hook",
"group:hooliganism", "group:hooliganism",
"group:horiishi horuto", "group:horiishi horuto",
"group:horonabe ken",
"group:horrorbabecentral", "group:horrorbabecentral",
"group:horsetail", "group:horsetail",
"group:hoshi no hako", "group:hoshi no hako",
@@ -2367,6 +2389,7 @@ object Group : TagList {
"group:imobatake", "group:imobatake",
"group:imokenpi", "group:imokenpi",
"group:imomushi kyouiku madoguchi", "group:imomushi kyouiku madoguchi",
"group:inakahaishin",
"group:inaridou shoten", "group:inaridou shoten",
"group:inc satsujinsha", "group:inc satsujinsha",
"group:inceton games", "group:inceton games",
@@ -2395,6 +2418,7 @@ object Group : TagList {
"group:intoku.info", "group:intoku.info",
"group:inudrill.", "group:inudrill.",
"group:inukamedou", "group:inukamedou",
"group:inukichi club",
"group:inukorohouse", "group:inukorohouse",
"group:inunabe", "group:inunabe",
"group:inuteikoku", "group:inuteikoku",
@@ -2414,6 +2438,7 @@ object Group : TagList {
"group:isada-ke", "group:isada-ke",
"group:isami kaihatsu jigyoudan", "group:isami kaihatsu jigyoudan",
"group:isamu. no oheya", "group:isamu. no oheya",
"group:isanayoruho",
"group:ishikari shake nabe doukoukai", "group:ishikari shake nabe doukoukai",
"group:ishikorodou", "group:ishikorodou",
"group:ishimuraya", "group:ishimuraya",
@@ -2460,6 +2485,7 @@ object Group : TagList {
"group:jamadai oukoku", "group:jamadai oukoku",
"group:janculsoft", "group:janculsoft",
"group:jangarian", "group:jangarian",
"group:jar of elements",
"group:jasmin universal village", "group:jasmin universal village",
"group:jeepney.cony", "group:jeepney.cony",
"group:jei c1on-ri", "group:jei c1on-ri",
@@ -2535,6 +2561,7 @@ object Group : TagList {
"group:k.a.d", "group:k.a.d",
"group:k.f.d.", "group:k.f.d.",
"group:k.o.store", "group:k.o.store",
"group:k.z.z. gundan",
"group:k2 company", "group:k2 company",
"group:k2 tomo no kai", "group:k2 tomo no kai",
"group:k3", "group:k3",
@@ -2555,6 +2582,7 @@ object Group : TagList {
"group:kaeri no kai 2", "group:kaeri no kai 2",
"group:kaeru soft", "group:kaeru soft",
"group:kagaku-shitsu.", "group:kagaku-shitsu.",
"group:kage mitsu",
"group:kagisawadou", "group:kagisawadou",
"group:kagishippo", "group:kagishippo",
"group:kagiyama baking co ltd", "group:kagiyama baking co ltd",
@@ -2584,6 +2612,7 @@ object Group : TagList {
"group:kaki purin", "group:kaki purin",
"group:kakiabura", "group:kakiabura",
"group:kakinotanehitotsubu", "group:kakinotanehitotsubu",
"group:kaku shoujo",
"group:kakunetu neko punch", "group:kakunetu neko punch",
"group:kakuzato-ichi", "group:kakuzato-ichi",
"group:kamaboko higii", "group:kamaboko higii",
@@ -2612,6 +2641,7 @@ object Group : TagList {
"group:kangaroo kick", "group:kangaroo kick",
"group:kanimiso pan", "group:kanimiso pan",
"group:kanimiso-tei", "group:kanimiso-tei",
"group:kankitudou",
"group:kankituteien", "group:kankituteien",
"group:kanmi ningyou", "group:kanmi ningyou",
"group:kanmidokoro usb", "group:kanmidokoro usb",
@@ -2840,12 +2870,14 @@ object Group : TagList {
"group:kokkishin", "group:kokkishin",
"group:koko sou iu mise janainde", "group:koko sou iu mise janainde",
"group:kokochikyuu", "group:kokochikyuu",
"group:kokonji honpo",
"group:kokonoe", "group:kokonoe",
"group:kokonokaya", "group:kokonokaya",
"group:kokonokiya", "group:kokonokiya",
"group:kokoro ha koi iro", "group:kokoro ha koi iro",
"group:kokoro no bookmark", "group:kokoro no bookmark",
"group:kokou no gokutsubushi", "group:kokou no gokutsubushi",
"group:koks k yokochou",
"group:koku-from-shojo", "group:koku-from-shojo",
"group:kokumaro chousei tounyuu", "group:kokumaro chousei tounyuu",
"group:kokuritsu hinanjo", "group:kokuritsu hinanjo",
@@ -2916,6 +2948,7 @@ object Group : TagList {
"group:kouni yuu", "group:kouni yuu",
"group:kousaien", "group:kousaien",
"group:kousoku gurihari-tei", "group:kousoku gurihari-tei",
"group:kousoku purin",
"group:kouzaka-san to makino jimusho", "group:kouzaka-san to makino jimusho",
"group:kouzu shoukai", "group:kouzu shoukai",
"group:kouzuya", "group:kouzuya",
@@ -3047,8 +3080,10 @@ object Group : TagList {
"group:lab-ideas", "group:lab-ideas",
"group:labomagi", "group:labomagi",
"group:lagrangian-point", "group:lagrangian-point",
"group:lagunaseca",
"group:laikaloid", "group:laikaloid",
"group:lala la", "group:lala la",
"group:lalapaloosa",
"group:lamchat", "group:lamchat",
"group:lamia advisers", "group:lamia advisers",
"group:landurchin", "group:landurchin",
@@ -3056,6 +3091,7 @@ object Group : TagList {
"group:lantern chord", "group:lantern chord",
"group:lapis lazuli", "group:lapis lazuli",
"group:lapislazuli triple star", "group:lapislazuli triple star",
"group:laser beam",
"group:lathimania kyouwakoku", "group:lathimania kyouwakoku",
"group:latte chaba", "group:latte chaba",
"group:lavenderblue", "group:lavenderblue",
@@ -3171,6 +3207,7 @@ object Group : TagList {
"group:m kichibeya", "group:m kichibeya",
"group:m plus dilore", "group:m plus dilore",
"group:m slash k club", "group:m slash k club",
"group:m-i-p",
"group:m-keifu", "group:m-keifu",
"group:m-koujou", "group:m-koujou",
"group:m-lab.", "group:m-lab.",
@@ -3460,6 +3497,7 @@ object Group : TagList {
"group:mindcontrolcomics", "group:mindcontrolcomics",
"group:mine slash mine", "group:mine slash mine",
"group:minemine kikaku", "group:minemine kikaku",
"group:minimum fuusen",
"group:minimum lab", "group:minimum lab",
"group:miniomlet ongakudan", "group:miniomlet ongakudan",
"group:minisuka fx", "group:minisuka fx",
@@ -3481,6 +3519,7 @@ object Group : TagList {
"group:misin koujou", "group:misin koujou",
"group:misonodenpatou", "group:misonodenpatou",
"group:misoyahonpo", "group:misoyahonpo",
"group:misssail",
"group:misuterutein", "group:misuterutein",
"group:misutta", "group:misutta",
"group:misuzu dennou gijutsukenkyuujo", "group:misuzu dennou gijutsukenkyuujo",
@@ -3531,6 +3570,7 @@ object Group : TagList {
"group:moe hentai", "group:moe hentai",
"group:moe hina", "group:moe hina",
"group:moe shoujo ryouiki", "group:moe shoujo ryouiki",
"group:moegara",
"group:moeyuki soft", "group:moeyuki soft",
"group:moezilla-gumi", "group:moezilla-gumi",
"group:mofu mofu sheep", "group:mofu mofu sheep",
@@ -3582,6 +3622,7 @@ object Group : TagList {
"group:moon night kitten", "group:moon night kitten",
"group:moon ruler", "group:moon ruler",
"group:moonlegacy", "group:moonlegacy",
"group:moonlight laboratory",
"group:moonrevenge", "group:moonrevenge",
"group:moonshell", "group:moonshell",
"group:moonsorrow", "group:moonsorrow",
@@ -3639,6 +3680,7 @@ object Group : TagList {
"group:mozuku dokokai", "group:mozuku dokokai",
"group:mp", "group:mp",
"group:mp0", "group:mp0",
"group:mr. hokke",
"group:mr.jack plus", "group:mr.jack plus",
"group:mr.k", "group:mr.k",
"group:mr.outside", "group:mr.outside",
@@ -3765,6 +3807,7 @@ object Group : TagList {
"group:namakura dou", "group:namakura dou",
"group:namanecotei", "group:namanecotei",
"group:namasute koubou", "group:namasute koubou",
"group:namazuchaya",
"group:namekataya", "group:namekataya",
"group:nami-nami restaurant", "group:nami-nami restaurant",
"group:namida no teinen taishoku", "group:namida no teinen taishoku",
@@ -3906,6 +3949,7 @@ object Group : TagList {
"group:nekozamedan", "group:nekozamedan",
"group:nekuronomikon", "group:nekuronomikon",
"group:nel-zel formula", "group:nel-zel formula",
"group:nemu wa yakiniku ga tabetai",
"group:nemurineko", "group:nemurineko",
"group:nenashigusa no ie", "group:nenashigusa no ie",
"group:nendo ningyo", "group:nendo ningyo",
@@ -3961,6 +4005,9 @@ object Group : TagList {
"group:nikomark", "group:nikomark",
"group:nikomi omurice", "group:nikomi omurice",
"group:nikoniko company", "group:nikoniko company",
)
override fun getTags3(): List<String> = listOf(
"group:niku ringo", "group:niku ringo",
"group:nikubenki seisakusho", "group:nikubenki seisakusho",
"group:nikudan", "group:nikudan",
@@ -4006,9 +4053,6 @@ object Group : TagList {
"group:niwatori", "group:niwatori",
"group:niwatori-ya", "group:niwatori-ya",
"group:niwatoritowani", "group:niwatoritowani",
)
override fun getTags3() = listOf(
"group:niy koubou", "group:niy koubou",
"group:niziro", "group:niziro",
"group:niziyumedokoro", "group:niziyumedokoro",
@@ -4025,6 +4069,7 @@ object Group : TagList {
"group:nocohica", "group:nocohica",
"group:nodobotoke kingyo", "group:nodobotoke kingyo",
"group:nogusaw puzzle", "group:nogusaw puzzle",
"group:noir jou entrance",
"group:nokishita no rakuen.", "group:nokishita no rakuen.",
"group:nomerikomu", "group:nomerikomu",
"group:nomigoro.", "group:nomigoro.",
@@ -4040,7 +4085,6 @@ object Group : TagList {
"group:norakurari.", "group:norakurari.",
"group:noraneko koubou", "group:noraneko koubou",
"group:noraneko-no-tama", "group:noraneko-no-tama",
"group:nori5rou",
"group:norihee ginjou", "group:norihee ginjou",
"group:noritama-gozen", "group:noritama-gozen",
"group:norn", "group:norn",
@@ -4175,6 +4219,7 @@ object Group : TagList {
"group:omusubi koubou", "group:omusubi koubou",
"group:on my way", "group:on my way",
"group:on-show", "group:on-show",
"group:onabe no naka.",
"group:onaka suita domei", "group:onaka suita domei",
"group:onasuga 99-yen", "group:onasuga 99-yen",
"group:one dollar", "group:one dollar",
@@ -4220,6 +4265,7 @@ object Group : TagList {
"group:ororiya enpitsudo", "group:ororiya enpitsudo",
"group:osanagokoro no kimi ni", "group:osanagokoro no kimi ni",
"group:osaru-san panic", "group:osaru-san panic",
"group:oshaburi tengoku",
"group:oshigoto no jikan", "group:oshigoto no jikan",
"group:osouzaiya-san", "group:osouzaiya-san",
"group:osova", "group:osova",
@@ -4439,6 +4485,7 @@ object Group : TagList {
"group:pockyfactory", "group:pockyfactory",
"group:poco black", "group:poco black",
"group:poco poco", "group:poco poco",
"group:poino",
"group:poisonblues", "group:poisonblues",
"group:poiyo dimension", "group:poiyo dimension",
"group:pokachutei", "group:pokachutei",
@@ -4464,6 +4511,7 @@ object Group : TagList {
"group:poppin stompin", "group:poppin stompin",
"group:popship", "group:popship",
"group:popularplus", "group:popularplus",
"group:porcini",
"group:porno maker", "group:porno maker",
"group:potato house", "group:potato house",
"group:poteto dango", "group:poteto dango",
@@ -4544,7 +4592,6 @@ object Group : TagList {
"group:rabbit kuukan", "group:rabbit kuukan",
"group:rabbits", "group:rabbits",
"group:rabitan", "group:rabitan",
"group:rabu.",
"group:raccoondog", "group:raccoondog",
"group:radiant slash h plus", "group:radiant slash h plus",
"group:radiostar", "group:radiostar",
@@ -4797,6 +4844,7 @@ object Group : TagList {
"group:sakura mangekyou", "group:sakura mangekyou",
"group:sakura naomiki", "group:sakura naomiki",
"group:sakura nigou", "group:sakura nigou",
"group:sakura no tomoru hie",
"group:sakura prin", "group:sakura prin",
"group:sakura zensen", "group:sakura zensen",
"group:sakuradou", "group:sakuradou",
@@ -4827,6 +4875,7 @@ object Group : TagList {
"group:sangenshokudou", "group:sangenshokudou",
"group:sanjuuhachi shiki kikanjuu", "group:sanjuuhachi shiki kikanjuu",
"group:sankaku apron", "group:sankaku apron",
"group:sankaku button",
"group:sankaku doumei", "group:sankaku doumei",
"group:sankokudo", "group:sankokudo",
"group:sanma kizoku", "group:sanma kizoku",
@@ -5081,6 +5130,7 @@ object Group : TagList {
"group:shonen gekikuukan", "group:shonen gekikuukan",
"group:shonnaka-dou", "group:shonnaka-dou",
"group:shootouts", "group:shootouts",
"group:short kami",
"group:shortcut koubou", "group:shortcut koubou",
"group:shosekido", "group:shosekido",
"group:shouchuu mac", "group:shouchuu mac",
@@ -5110,6 +5160,7 @@ object Group : TagList {
"group:shuryousha", "group:shuryousha",
"group:shuto reccara", "group:shuto reccara",
"group:shuueisha", "group:shuueisha",
"group:shuukyuu 8-ka",
"group:shuukyuu itsukasei", "group:shuukyuu itsukasei",
"group:shuutaisei", "group:shuutaisei",
"group:shyness over drive", "group:shyness over drive",
@@ -5143,9 +5194,9 @@ object Group : TagList {
"group:siop", "group:siop",
"group:sioyaki", "group:sioyaki",
"group:sioyude", "group:sioyude",
"group:sirisiri denbu club",
"group:sirius soft", "group:sirius soft",
"group:siro house", "group:siro house",
"group:siropome",
"group:sirouto plan", "group:sirouto plan",
"group:sirubedou", "group:sirubedou",
"group:sisinabeya", "group:sisinabeya",
@@ -5261,6 +5312,7 @@ object Group : TagList {
"group:st. rio", "group:st. rio",
"group:st. rororo", "group:st. rororo",
"group:staccato squirrel", "group:staccato squirrel",
"group:stamina teishoku",
"group:standard azarashi", "group:standard azarashi",
"group:star link", "group:star link",
"group:star-dreamer tei", "group:star-dreamer tei",
@@ -5402,7 +5454,6 @@ object Group : TagList {
"group:sushi-go-round", "group:sushi-go-round",
"group:suteinu nursery", "group:suteinu nursery",
"group:sutekiplan", "group:sutekiplan",
"group:suzuki masahisa",
"group:suzuki shouten", "group:suzuki shouten",
"group:suzunaridou", "group:suzunaridou",
"group:suzupony", "group:suzupony",
@@ -5421,6 +5472,7 @@ object Group : TagList {
"group:syamisen koubou", "group:syamisen koubou",
"group:synthetic garden", "group:synthetic garden",
"group:syonen-kikakugai.", "group:syonen-kikakugai.",
"group:syounan rakujin society",
"group:syounen heroine", "group:syounen heroine",
"group:syounen kouraku", "group:syounen kouraku",
"group:syouseki kessyou", "group:syouseki kessyou",
@@ -5474,6 +5526,7 @@ object Group : TagList {
"group:takara no suzunari", "group:takara no suzunari",
"group:takashi-ya", "group:takashi-ya",
"group:takayashiki kaihatsu", "group:takayashiki kaihatsu",
"group:take4 project",
"group:takeda syouten", "group:takeda syouten",
"group:takegamiya", "group:takegamiya",
"group:takeshidou-chou", "group:takeshidou-chou",
@@ -5494,6 +5547,7 @@ object Group : TagList {
"group:tamatamasanmyaku", "group:tamatamasanmyaku",
"group:tamokuteki hall", "group:tamokuteki hall",
"group:tamokuteki kuukan", "group:tamokuteki kuukan",
"group:tanajou",
"group:tanaka shouten", "group:tanaka shouten",
"group:tanaura honpo", "group:tanaura honpo",
"group:tanetsuke ichinengo", "group:tanetsuke ichinengo",
@@ -5526,6 +5580,7 @@ object Group : TagList {
"group:team tanabe", "group:team tanabe",
"group:team z and 3n", "group:team z and 3n",
"group:team zero", "group:team zero",
"group:team4000",
"group:tears of ymir", "group:tears of ymir",
"group:teatime", "group:teatime",
"group:tecchitecchi", "group:tecchitecchi",
@@ -5534,9 +5589,11 @@ object Group : TagList {
"group:tedaingu", "group:tedaingu",
"group:teddy-plaza", "group:teddy-plaza",
"group:teemonk", "group:teemonk",
"group:teiesuya",
"group:teihatu syouzyo titai", "group:teihatu syouzyo titai",
"group:teikiatu de ikou", "group:teikiatu de ikou",
"group:teikoku club", "group:teikoku club",
"group:tekirororock",
"group:tekken neko gourmet", "group:tekken neko gourmet",
"group:tekoman-dou", "group:tekoman-dou",
"group:telomere limiter", "group:telomere limiter",
@@ -5718,6 +5775,7 @@ object Group : TagList {
"group:toumei tsuushin", "group:toumei tsuushin",
"group:toushi ryoku kenkyuujo", "group:toushi ryoku kenkyuujo",
"group:toutaku tuyagadou", "group:toutaku tuyagadou",
"group:touyou bujutsu gakkou",
"group:touyu okiba kari", "group:touyu okiba kari",
"group:touyu stand", "group:touyu stand",
"group:touzoku tachi no rakuda no mure", "group:touzoku tachi no rakuda no mure",
@@ -5918,6 +5976,7 @@ object Group : TagList {
"group:usagi youjinbou", "group:usagi youjinbou",
"group:usagijiru", "group:usagijiru",
"group:usagikoara", "group:usagikoara",
"group:usaginoheya",
"group:usagitei", "group:usagitei",
"group:usako kf", "group:usako kf",
"group:usamimi syndrome", "group:usamimi syndrome",
@@ -5949,337 +6008,5 @@ object Group : TagList {
"group:vagina dentata", "group:vagina dentata",
"group:valiant", "group:valiant",
"group:valkyria", "group:valkyria",
"group:valkyrieharlem",
"group:vanilla fuumi shoujo",
"group:vanilla star",
"group:vanilla-dou max",
"group:variety pot",
"group:vashadow",
"group:vc productions",
"group:venom",
"group:veronica no ha",
"group:violence asia team",
"group:visual biscuits",
"group:vitamin gohan",
"group:vitamin soft",
"group:vitamin x",
"group:vivi-sectr",
"group:vivid color",
"group:vivid dot",
"group:vivido",
"group:viweb",
"group:voltcompany.",
"group:vpans extasy",
"group:vulcannu",
"group:w at nd",
"group:wabi sabi wasabi",
"group:wagamama dou",
"group:wakaba syokei",
"group:wakarase seisaku iinkai",
"group:wakatobi",
"group:wakusei-teki shukou",
"group:wakuwaku boycott",
"group:wakuwaku doubutsuen",
"group:walhalla illusion",
"group:wamusho",
"group:wanko-tei",
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:waretama",
"group:warp loop",
"group:washikul.",
"group:washokudeniku.",
"group:wata 120 percent",
"group:wataame",
"group:watagashi maker",
"group:watanabe tou",
"group:wataru kuya",
"group:water ducts",
"group:waterfall",
"group:waterspoon",
"group:waterstudio",
"group:waterwheel",
"group:weather report",
"group:webstudioofflimits",
"group:wellca",
"group:wendybell",
"group:west vision",
"group:whirl-wind",
"group:white out",
"group:white plus dk",
"group:white rocket",
)
override fun getTags4() = listOf(
"group:whitecloth",
"group:whitesoft",
"group:whitewill",
"group:wi-fe hacker",
"group:wicked heart",
"group:wild goat",
"group:will tame",
"group:willow soft",
"group:wind mail",
"group:wind of the keep valley",
"group:windarteam",
"group:winger in mind",
"group:winghills",
"group:wish",
"group:wish kibou no tsubasa",
"group:witchflame",
"group:witching hour entertainment",
"group:wizard",
"group:wolf fang dou",
"group:wonderland 203",
"group:wope-retta",
"group:world of porncraft",
"group:world temperament",
"group:worstworks",
"group:wriggle souzeme tomonokai",
"group:x model",
"group:x-tei",
"group:xephs artwork",
"group:xl-toons",
"group:xx koubou",
"group:xxxxxxx",
"group:xyzyroh",
"group:y slash s slash k",
"group:y.d.l",
"group:y.m.sensha",
"group:yaboudo project",
"group:yabougumi",
"group:yabukaradou",
"group:yaburi dokoro",
"group:yadapot",
"group:yadokugaeru",
"group:yadoo van yahdoo",
"group:yagi q-syah",
"group:yago no ana",
"group:yajilshi plus",
"group:yajirushi key",
"group:yajiya",
"group:yakata",
"group:yakimisomura",
"group:yakinasu teishoku",
"group:yakiniku teishoku",
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
"group:yakumi sarai",
"group:yakusyo",
"group:yakutai",
"group:yam x 2 dai teikoku",
"group:yamada ichizoku.",
"group:yamadamaya",
"group:yamadaya",
"group:yamagiwa art cg studio",
"group:yamaguchirou",
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamanaka no naka",
"group:yamano murao",
"group:yami ni ugomeku",
"group:yamikumo tsuushin",
"group:yamotodou rakugakiichi",
"group:yanagiba dai",
"group:yanagisegawa",
"group:yanasegawabeya",
"group:yangyang nickbow",
"group:yanmarumaa",
"group:yaou keikaku",
"group:yaoyorozu-kobo",
"group:yaoyorozudo",
"group:yasai batake",
"group:yaseuma lo-ru",
"group:yashiya",
"group:yashock kaigi",
"group:yasrin-do",
"group:yasudajuku",
"group:yasuomi-craft",
"group:yasyokutei",
"group:yatuumi no kagami",
"group:yawaraka okashiya",
"group:yaya hinata-ya",
"group:yes sir.",
"group:yggdrasil",
"group:yo-metdo",
"group:yoban left",
"group:yobigakka",
"group:yohsyuan",
"group:yojouhan factory",
"group:yojouhan shobou",
"group:yojouhan toshi",
"group:yokan musume",
"group:yokazetei",
"group:yokohama zza koubou",
"group:yokoshimanchi.",
"group:yokoshimaya",
"group:yokoshoku ice",
"group:yomamagoto",
"group:yomosue doukoukai",
"group:yomothuhirasaka",
"group:yonatan black mutou",
"group:yonbangai garo",
"group:yonjuichi",
"group:yonmasuya",
"group:yonurime",
"group:yorando",
"group:yoru no benkyoukai",
"group:yoru no okazu shokudou",
"group:yoruyama no kyuukeijo",
"group:yoseatume tekina nanika",
"group:yoshida gorou shoukai",
"group:yoshiga dokoro",
"group:yoshii tech sha",
"group:yotukuro",
"group:you",
"group:you you tsuushin",
"group:you you you",
"group:youchi-na-ochakai",
"group:youen bijo no garou",
"group:youkai ankake",
"group:youkai tamanokoshi",
"group:youkandou",
"group:youki m.k.c.",
"group:youmusya",
"group:yours-wow",
"group:yousei allergen",
"group:youseimangasya",
"group:youtoujirushi",
"group:youtsuu transmitter",
"group:youyukai",
"group:yowatari kouba",
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
"group:yukagenikaga",
"group:yukeyuke ryuseigo",
"group:yuki daruma koujou",
"group:yuki no iori",
"group:yukijirushi nyuugyou",
"group:yukikagerou",
"group:yukimi honpo",
"group:yukimura",
"group:yukinko okeya",
"group:yukino koubou",
"group:yukirinrin",
"group:yukyu-kyuka",
"group:yume bouei shoujo tai",
"group:yume yori suteki na",
"group:yume-zakura",
"group:yumeiro-goromo",
"group:yumeizukosya",
"group:yumemigachi campus",
"group:yumemigokoti",
"group:yumemiru-kikai",
"group:yumenamakon",
"group:yumenokage",
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yurayuraseyuura",
"group:yureika blade",
"group:yuri dokidoki",
"group:yuri equal 18l",
"group:yuriai kojinshi kai",
"group:yurinyurin",
"group:yuriru-rarika",
"group:yurumebox",
"group:yuruyakatou",
"group:yuruyuru seisakusho",
"group:yusuzumi",
"group:yuu adashino suisan",
"group:yuu heya",
"group:yuubin basha",
"group:yuudachitei",
"group:yuuendou",
"group:yuugai tosho kikaku",
"group:yuugen kaisha sokuhou seisakusho",
"group:yuugensangyou sukimakaze",
"group:yuugure koubou",
"group:yuuhodou",
"group:yuujikouji",
"group:yuukakumin",
"group:yuuki kagoubutsu",
"group:yuukyuu shinden",
"group:yuukyuu suisenkan",
"group:yuunagi gaibutai",
"group:yuuriko",
"group:yuusha kandenchi",
"group:yuutopia",
"group:yuuya-yuu",
"group:yuyake box",
"group:yuzu soft",
"group:yuzucha",
"group:yuzuen",
"group:yuzumomo jam",
"group:yuzuonsen",
"group:yuzuponz",
"group:yuzurihaya",
"group:z",
"group:z-tabukuroneko house",
"group:z-vector",
"group:za da carjya",
"group:zakkin kougyou",
"group:zan-sei",
"group:zankirow",
"group:zankoku doumei",
"group:zankoku na kami ga shihai suru",
"group:zantetuken",
"group:zasshu-ken",
"group:zassoubatake",
"group:zassoya",
"group:zatouichi",
"group:zatuyou gakari",
"group:zawameki jambo",
"group:zeiniku shoujotai",
"group:zenmai koubou",
"group:zenmai kourogi",
"group:zennihon do-m jitsuryoku kentei kousa",
"group:zenoside",
"group:zenra restaurant",
"group:zensekai yakenohara doumei",
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
"group:zero ni kaeru tsuki",
"group:zero-one",
"group:zero-sen",
"group:zero-xx",
"group:zerocool",
"group:zeroinfinityone",
"group:zeryishi",
"group:zettai shoujo",
"group:zettaitensei",
"group:zgf",
"group:zi",
"group:zion",
"group:zmey no soukutsu",
"group:zokubutsu.zip",
"group:zombie to yukaina nakamatachi",
"group:zonzon sharp 4",
"group:zooerastia",
"group:zouri no sato",
"group:zozalist",
"group:zugaikotsu marudashi",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
"group:zzz comics",
) )
} }
+342
View File
@@ -0,0 +1,342 @@
package exh.eh.tags
object Group2 : TagList {
override fun getTags1(): List<String> = listOf(
"group:valkyrieharlem",
"group:vanilla fuumi shoujo",
"group:vanilla star",
"group:vanilla-dou max",
"group:variety pot",
"group:vashadow",
"group:vc productions",
"group:venom",
"group:veronica no ha",
"group:violence asia team",
"group:visual biscuits",
"group:vitamin gohan",
"group:vitamin soft",
"group:vitamin x",
"group:vivi-sectr",
"group:vivid color",
"group:vivid dot",
"group:vivido",
"group:viweb",
"group:voltcompany.",
"group:vpans extasy",
"group:vulcannu",
"group:w at nd",
"group:wabi sabi wasabi",
"group:wagamama dou",
"group:wakaba syokei",
"group:wakarase seisaku iinkai",
"group:wakatobi",
"group:wakusei-teki shukou",
"group:wakuwaku boycott",
"group:wakuwaku doubutsuen",
"group:walhalla illusion",
"group:wamusho",
"group:wanko-tei",
"group:wankyoku canvas",
"group:wanwandoh",
"group:warabimochi",
"group:waretama",
"group:warp loop",
"group:wasa wasa",
"group:washikul.",
"group:washokudeniku.",
"group:wata 120 percent",
"group:wataame",
"group:watagashi maker",
"group:watanabe tou",
"group:wataru kuya",
"group:water ducts",
"group:waterfall",
"group:waterspoon",
"group:waterstudio",
"group:waterwheel",
"group:weather report",
"group:webstudioofflimits",
"group:wellca",
"group:wendybell",
"group:west vision",
"group:whirl-wind",
"group:white out",
"group:white plus dk",
"group:white rocket",
"group:whitecloth",
"group:whitesoft",
"group:whitewill",
"group:wi-fe hacker",
"group:wicked heart",
"group:wild goat",
"group:will tame",
"group:willow soft",
"group:wind mail",
"group:wind of the keep valley",
"group:windarteam",
"group:winger in mind",
"group:winghills",
"group:wish",
"group:wish kibou no tsubasa",
"group:witchflame",
"group:witching hour entertainment",
"group:wizard",
"group:wolf fang dou",
"group:wonderland 203",
"group:wope-retta",
"group:world of porncraft",
"group:world temperament",
"group:worstworks",
"group:wriggle souzeme tomonokai",
"group:x model",
"group:x-tei",
"group:xephs artwork",
"group:xl-toons",
"group:xx koubou",
"group:xxxxxxx",
"group:xyzyroh",
"group:y slash s slash k",
"group:y.d.l",
"group:y.m.sensha",
"group:yaboudo project",
"group:yabougumi",
"group:yabukaradou",
"group:yaburi dokoro",
"group:yadapot",
"group:yadokugaeru",
"group:yadoo van yahdoo",
"group:yagi q-syah",
"group:yago no ana",
"group:yajilshi plus",
"group:yajirushi key",
"group:yajiya",
"group:yakata",
"group:yakimisomura",
"group:yakinasu teishoku",
"group:yakiniku teishoku",
"group:yakisaketeishoku",
"group:yakisoba pants",
"group:yakisoba rengo",
"group:yakiubu",
"group:yakousei fan club",
"group:yaku 40 man sarad",
"group:yakumi sarai",
"group:yakusyo",
"group:yakutai",
"group:yam x 2 dai teikoku",
"group:yamada ichizoku.",
"group:yamadamaya",
"group:yamadaya",
"group:yamagiwa art cg studio",
"group:yamaguchirou",
"group:yamakawa denenhuukei",
"group:yamami no yado",
"group:yamamori gohan",
"group:yamanaka no naka",
"group:yamano murao",
"group:yamato nadeshiko club",
"group:yami ni ugomeku",
"group:yamikumo tsuushin",
"group:yamotodou rakugakiichi",
"group:yanagiba dai",
"group:yanagisegawa",
"group:yanasegawabeya",
"group:yangyang nickbow",
"group:yanmarumaa",
"group:yaou keikaku",
"group:yaoyorozu-kobo",
"group:yaoyorozudo",
"group:yasai batake",
"group:yaseuma lo-ru",
"group:yashiya",
"group:yashock kaigi",
"group:yasrin-do",
"group:yasudajuku",
"group:yasuomi-craft",
"group:yasyokutei",
"group:yatomomin",
"group:yatuumi no kagami",
"group:yawaraka okashiya",
"group:yaya hinata-ya",
"group:yes sir.",
"group:yggdrasil",
"group:yo-metdo",
"group:yoban left",
"group:yobigakka",
"group:yohsyuan",
"group:yojouhan factory",
"group:yojouhan shobou",
"group:yojouhan toshi",
"group:yokan musume",
"group:yokazetei",
"group:yokohama zza koubou",
"group:yokoshimanchi.",
"group:yokoshimaya",
"group:yokoshoku ice",
"group:yoku mireba beta",
"group:yomamagoto",
"group:yomosue doukoukai",
"group:yomothuhirasaka",
"group:yonatan black mutou",
"group:yonbangai garo",
"group:yonjuichi",
"group:yonmasuya",
"group:yonurime",
"group:yorando",
"group:yoru no benkyoukai",
"group:yoru no okazu shokudou",
"group:yoruyama no kyuukeijo",
"group:yoseatume tekina nanika",
"group:yoshida gorou shoukai",
"group:yoshiga dokoro",
"group:yoshii tech sha",
"group:yotukuro",
"group:you",
"group:you you tsuushin",
"group:you you you",
"group:youchi-na-ochakai",
"group:youen bijo no garou",
"group:yougensya",
"group:youkai ankake",
"group:youkai tamanokoshi",
"group:youkandou",
"group:youki m.k.c.",
"group:youmusya",
"group:yours-wow",
"group:yousei allergen",
"group:youseimangasya",
"group:youtoujirushi",
"group:youtsuu transmitter",
"group:youyukai",
"group:yowatari kouba",
"group:yoyude ikemasu",
"group:yozorairodrops",
"group:ys company",
"group:yu-ta.18",
"group:yu-yu-tei",
"group:yuasa rengou",
"group:yubidou",
"group:yudenakya nama-beer",
"group:yudokuya",
"group:yuhshiki",
"group:yukagenikaga",
"group:yukan high zakura",
"group:yukeyuke ryuseigo",
"group:yuki daruma koujou",
"group:yuki no iori",
"group:yukijirushi nyuugyou",
"group:yukikagerou",
"group:yukimi honpo",
"group:yukimura",
"group:yukinko okeya",
"group:yukino koubou",
"group:yukirinrin",
"group:yukyu-kyuka",
"group:yume bouei shoujo tai",
"group:yume yori suteki na",
"group:yume-zakura",
"group:yumeiro-goromo",
"group:yumeizukosya",
"group:yumemigachi campus",
"group:yumemigokoti",
"group:yumemiru-kikai",
"group:yumenamakon",
"group:yumenokage",
"group:yumeoikyounouta",
"group:yumeoukoku",
"group:yunabon",
"group:yurayuraseyuura",
"group:yureika blade",
"group:yuri dokidoki",
"group:yuri equal 18l",
"group:yuriai kojinshi kai",
"group:yurinyurin",
"group:yuriru-rarika",
"group:yurumebox",
"group:yuruyakatou",
"group:yuruyuru seisakusho",
"group:yusuzumi",
"group:yuu adashino suisan",
"group:yuu heya",
"group:yuubin basha",
"group:yuudachitei",
"group:yuuendou",
"group:yuugai tosho kikaku",
"group:yuugen kaisha sokuhou seisakusho",
"group:yuugensangyou sukimakaze",
"group:yuugure koubou",
"group:yuuhodou",
"group:yuujikouji",
"group:yuukakumin",
"group:yuuki kagoubutsu",
"group:yuukyuu shinden",
"group:yuukyuu suisenkan",
"group:yuunagi gaibutai",
"group:yuuriko",
"group:yuusha kandenchi",
"group:yuutopia",
"group:yuuya-yuu",
"group:yuyake box",
"group:yuzu soft",
"group:yuzucha",
"group:yuzuen",
"group:yuzumomo jam",
"group:yuzuonsen",
"group:yuzuponz",
"group:yuzurihaya",
"group:z",
"group:z-tabukuroneko house",
"group:z-vector",
"group:za da carjya",
"group:zakkin kougyou",
"group:zan-sei",
"group:zankirow",
"group:zankoku doumei",
"group:zankoku na kami ga shihai suru",
"group:zantetuken",
"group:zasshu-ken",
"group:zassoubatake",
"group:zassoya",
"group:zatouichi",
"group:zatuyou gakari",
"group:zawameki jambo",
"group:zeiniku shoujotai",
"group:zenmai koubou",
"group:zenmai kourogi",
"group:zennihon do-m jitsuryoku kentei kousa",
"group:zenoside",
"group:zenra restaurant",
"group:zenryoku sissou cat",
"group:zensekai yakenohara doumei",
"group:zenshuu bougyo",
"group:zensoku zenkai.",
"group:zensun habaku",
"group:zenzidou kosyubenjo",
"group:zero byte",
"group:zero equals mono",
"group:zero ni kaeru tsuki",
"group:zero-one",
"group:zero-sen",
"group:zero-xx",
"group:zerocool",
"group:zeroinfinityone",
"group:zeryishi",
"group:zettai shoujo",
"group:zettaitensei",
"group:zgf",
"group:zi",
"group:zion",
"group:zmey no soukutsu",
"group:zokubutsu.zip",
"group:zombie to yukaina nakamatachi",
"group:zonzon sharp 4",
"group:zooerastia",
"group:zouri no sato",
"group:zozalist",
"group:zugaikotsu marudashi",
"group:zvizva-dan",
"group:zydan",
"group:zyulokuya",
"group:zzz comics",
)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package exh.eh.tags package exh.eh.tags
object Language : TagList { object Language : TagList {
override fun getTags1() = listOf( override fun getTags1(): List<String> = listOf(
"language:arabic", "language:arabic",
"language:bulgarian", "language:bulgarian",
"language:catalan", "language:catalan",
+136 -2
View File
@@ -1,28 +1,39 @@
package exh.eh.tags package exh.eh.tags
object Male : TagList { object Male : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"male:abortion", "male:abortion",
"male:absorption", "male:absorption",
"male:adventitious penis",
"male:afro",
"male:age progression", "male:age progression",
"male:age regression", "male:age regression",
"male:ahegao", "male:ahegao",
"male:albino",
"male:alien", "male:alien",
"male:all the way through",
"male:amputee", "male:amputee",
"male:anal", "male:anal",
"male:anal birth", "male:anal birth",
"male:anal intercourse", "male:anal intercourse",
"male:anal prolapse", "male:anal prolapse",
"male:analphagia",
"male:angel", "male:angel",
"male:animal on animal",
"male:animal on furry", "male:animal on furry",
"male:animegao", "male:animegao",
"male:anorexic",
"male:apparel bukkake", "male:apparel bukkake",
"male:apron", "male:apron",
"male:armpit licking", "male:armpit licking",
"male:armpit sex",
"male:asphyxiation", "male:asphyxiation",
"male:ass expansion",
"male:assjob",
"male:autofellatio",
"male:bald", "male:bald",
"male:ball sucking", "male:ball sucking",
"male:balljob",
"male:balls expansion", "male:balls expansion",
"male:bandages", "male:bandages",
"male:bat boy", "male:bat boy",
@@ -30,10 +41,14 @@ object Male : TagList {
"male:bdsm", "male:bdsm",
"male:bear", "male:bear",
"male:bear boy", "male:bear boy",
"male:beauty mark",
"male:bestiality", "male:bestiality",
"male:big areolae",
"male:big ass", "male:big ass",
"male:big balls", "male:big balls",
"male:big breasts", "male:big breasts",
"male:big lips",
"male:big muscles",
"male:big nipples", "male:big nipples",
"male:big penis", "male:big penis",
"male:bike shorts", "male:bike shorts",
@@ -47,12 +62,16 @@ object Male : TagList {
"male:bloomers", "male:bloomers",
"male:blowjob", "male:blowjob",
"male:blowjob face", "male:blowjob face",
"male:body modification",
"male:body painting", "male:body painting",
"male:body swap", "male:body swap",
"male:body writing", "male:body writing",
"male:bodystocking", "male:bodystocking",
"male:bodysuit", "male:bodysuit",
"male:bondage", "male:bondage",
"male:braces",
"male:brain fuck",
"male:breast expansion",
"male:breast feeding", "male:breast feeding",
"male:bride", "male:bride",
"male:brother", "male:brother",
@@ -62,11 +81,13 @@ object Male : TagList {
"male:burping", "male:burping",
"male:business suit", "male:business suit",
"male:butler", "male:butler",
"male:camel",
"male:cannibalism", "male:cannibalism",
"male:cashier", "male:cashier",
"male:cat", "male:cat",
"male:catboy", "male:catboy",
"male:cbt", "male:cbt",
"male:centaur",
"male:cervix prolapse", "male:cervix prolapse",
"male:chastity belt", "male:chastity belt",
"male:cheating", "male:cheating",
@@ -75,13 +96,20 @@ object Male : TagList {
"male:chinese dress", "male:chinese dress",
"male:chloroform", "male:chloroform",
"male:christmas", "male:christmas",
"male:clamp",
"male:clit insertion",
"male:clit stimulation", "male:clit stimulation",
"male:clone", "male:clone",
"male:closed eyes",
"male:clothed female nude male", "male:clothed female nude male",
"male:clown",
"male:coach", "male:coach",
"male:cock ring", "male:cock ring",
"male:cockphagia",
"male:cockslapping",
"male:collar", "male:collar",
"male:condom", "male:condom",
"male:conjoined",
"male:coprophagia", "male:coprophagia",
"male:corruption", "male:corruption",
"male:corset", "male:corset",
@@ -90,25 +118,39 @@ object Male : TagList {
"male:cowman", "male:cowman",
"male:crab", "male:crab",
"male:crossdressing", "male:crossdressing",
"male:crotch tattoo",
"male:crown", "male:crown",
"male:crying",
"male:cum bath",
"male:cumflation",
"male:cunnilingus",
"male:cuntboy", "male:cuntboy",
"male:dark nipples", "male:dark nipples",
"male:dark sclera", "male:dark sclera",
"male:dark skin", "male:dark skin",
"male:deepthroat", "male:deepthroat",
"male:deer", "male:deer",
"male:deer boy",
"male:demon", "male:demon",
"male:denki anma",
"male:detached sleeves",
"male:diaper",
"male:dick growth", "male:dick growth",
"male:dickgirl on male", "male:dickgirl on male",
"male:dicknipples",
"male:dilf", "male:dilf",
"male:dinosaur", "male:dinosaur",
"male:dog", "male:dog",
"male:dog boy", "male:dog boy",
"male:doll joints",
"male:dolphin",
"male:domination loss",
"male:donkey", "male:donkey",
"male:double anal", "male:double anal",
"male:double blowjob", "male:double blowjob",
"male:double penetration", "male:double penetration",
"male:dougi", "male:dougi",
"male:draenei",
"male:dragon", "male:dragon",
"male:drill hair", "male:drill hair",
"male:drugs", "male:drugs",
@@ -126,6 +168,7 @@ object Male : TagList {
"male:eye-covering bang", "male:eye-covering bang",
"male:eyemask", "male:eyemask",
"male:eyepatch", "male:eyepatch",
"male:facesitting",
"male:facial hair", "male:facial hair",
"male:fairy", "male:fairy",
"male:farting", "male:farting",
@@ -138,8 +181,10 @@ object Male : TagList {
"male:fisting", "male:fisting",
"male:focus paizuri", "male:focus paizuri",
"male:food on body", "male:food on body",
"male:foot insertion",
"male:foot licking", "male:foot licking",
"male:footjob", "male:footjob",
"male:forced exposure",
"male:forniphilia", "male:forniphilia",
"male:fox", "male:fox",
"male:fox boy", "male:fox boy",
@@ -156,8 +201,13 @@ object Male : TagList {
"male:gender morph", "male:gender morph",
"male:ghost", "male:ghost",
"male:giant", "male:giant",
"male:giant sperm",
"male:gijinka",
"male:giraffe boy",
"male:glasses", "male:glasses",
"male:glory hole", "male:glory hole",
"male:gloves",
"male:goat",
"male:goblin", "male:goblin",
"male:gokkun", "male:gokkun",
"male:gorilla", "male:gorilla",
@@ -169,42 +219,63 @@ object Male : TagList {
"male:gyaru-oh", "male:gyaru-oh",
"male:gymshorts", "male:gymshorts",
"male:hair buns", "male:hair buns",
"male:hairjob",
"male:hairy", "male:hairy",
"male:hairy armpits",
"male:handjob", "male:handjob",
"male:hanging",
"male:harem", "male:harem",
"male:harpy", "male:harpy",
"male:headphones", "male:headphones",
"male:heterochromia",
"male:hijab",
"male:hood",
"male:horns", "male:horns",
"male:horse", "male:horse",
"male:horse boy", "male:horse boy",
"male:horse cock", "male:horse cock",
"male:hotpants", "male:hotpants",
"male:huge penis",
"male:human on furry", "male:human on furry",
"male:humiliation", "male:humiliation",
"male:hyena boy",
"male:impregnation", "male:impregnation",
"male:incest", "male:incest",
"male:infantilism", "male:infantilism",
"male:inflation", "male:inflation",
"male:insect", "male:insect",
"male:insect boy", "male:insect boy",
"male:internal urination",
"male:inverted nipples",
"male:invisible", "male:invisible",
"male:josou seme", "male:josou seme",
"male:kangaroo",
"male:kappa",
"male:kemonomimi", "male:kemonomimi",
"male:kigurumi pajama", "male:kigurumi pajama",
"male:kimono", "male:kimono",
"male:kindergarten uniform",
"male:kissing", "male:kissing",
"male:kunoichi",
"male:lab coat", "male:lab coat",
"male:lactation",
"male:large insertions", "male:large insertions",
"male:large tattoo", "male:large tattoo",
"male:latex", "male:latex",
"male:layer cake", "male:layer cake",
"male:leash",
"male:leg lock",
"male:leotard", "male:leotard",
"male:lingerie", "male:lingerie",
"male:lion", "male:lion",
"male:living clothes",
"male:lizard guy", "male:lizard guy",
"male:long tongue", "male:long tongue",
"male:low bestiality", "male:low bestiality",
"male:low guro",
"male:low scat",
"male:low shotacon", "male:low shotacon",
"male:low smegma",
"male:machine", "male:machine",
"male:maggot", "male:maggot",
"male:magical girl", "male:magical girl",
@@ -213,33 +284,46 @@ object Male : TagList {
"male:males only", "male:males only",
"male:masked face", "male:masked face",
"male:masturbation", "male:masturbation",
"male:mecha boy",
"male:merman", "male:merman",
"male:mesuiki", "male:mesuiki",
"male:metal armor", "male:metal armor",
"male:midget", "male:midget",
"male:miko", "male:miko",
"male:military", "male:military",
"male:milking",
"male:mind break", "male:mind break",
"male:mind control", "male:mind control",
"male:miniguy", "male:miniguy",
"male:minotaur", "male:minotaur",
"male:mmm threesome",
"male:monkey", "male:monkey",
"male:monkey boy", "male:monkey boy",
"male:monoeye",
"male:monster", "male:monster",
"male:moral degeneration", "male:moral degeneration",
"male:mouse", "male:mouse",
"male:mouse boy", "male:mouse boy",
"male:mouth mask",
"male:multimouth blowjob",
"male:multiple arms",
"male:multiple assjob", "male:multiple assjob",
"male:multiple footjob", "male:multiple footjob",
"male:multiple handjob", "male:multiple handjob",
"male:multiple orgasms", "male:multiple orgasms",
"male:multiple penises", "male:multiple penises",
"male:multiple straddling",
"male:muscle", "male:muscle",
"male:muscle growth",
"male:nakadashi", "male:nakadashi",
"male:navel fuck",
"male:nazi",
"male:necrophilia", "male:necrophilia",
"male:netorare", "male:netorare",
"male:ninja", "male:ninja",
"male:nipple birth", "male:nipple birth",
"male:nipple fuck",
"male:nipple stimulation",
"male:nose fuck", "male:nose fuck",
"male:nose hook", "male:nose hook",
"male:nun", "male:nun",
@@ -248,94 +332,131 @@ object Male : TagList {
"male:oil", "male:oil",
"male:old man", "male:old man",
"male:onahole", "male:onahole",
"male:oni",
"male:orc", "male:orc",
"male:orgasm denial", "male:orgasm denial",
"male:ostrich",
"male:otokofutanari", "male:otokofutanari",
"male:otter boy",
"male:oyakodon",
"male:paizuri", "male:paizuri",
"male:panda boy",
"male:panther", "male:panther",
"male:pantyhose", "male:pantyhose",
"male:pantyjob",
"male:parasite",
"male:pasties", "male:pasties",
"male:pegasus",
"male:pegging", "male:pegging",
"male:penis birth", "male:penis birth",
"male:personality excretion",
"male:petplay", "male:petplay",
"male:petrification",
"male:phimosis", "male:phimosis",
"male:piercing", "male:piercing",
"male:pig", "male:pig",
"male:pig man", "male:pig man",
"male:pillory", "male:pillory",
"male:pirate",
"male:piss drinking", "male:piss drinking",
"male:pixie cut",
"male:plant boy", "male:plant boy",
"male:pole dancing", "male:pole dancing",
"male:policeman", "male:policeman",
"male:ponytail",
"male:possession", "male:possession",
"male:pregnant", "male:pregnant",
"male:prehensile hair",
"male:priest", "male:priest",
"male:prolapse", "male:prolapse",
"male:prostate massage", "male:prostate massage",
"male:prostitution", "male:prostitution",
"male:pubic stubble", "male:pubic stubble",
"male:public use", "male:public use",
"male:pussyboys only",
"male:rabbit", "male:rabbit",
"male:raccoon boy",
"male:randoseru", "male:randoseru",
"male:rape", "male:rape",
"male:reptile", "male:reptile",
"male:retractable penis",
"male:rhinoceros", "male:rhinoceros",
"male:rimjob", "male:rimjob",
"male:robot", "male:robot",
"male:ryona", "male:ryona",
"male:saliva",
"male:scar", "male:scar",
"male:scat", "male:scat",
"male:school gym uniform", "male:school gym uniform",
"male:school swimsuit", "male:school swimsuit",
"male:schoolboy uniform", "male:schoolboy uniform",
"male:schoolgirl uniform", "male:schoolgirl uniform",
"male:scrotal lingerie",
"male:selfcest", "male:selfcest",
"male:sex toys", "male:sex toys",
"male:shared senses", "male:shared senses",
"male:shark", "male:shark",
"male:shark boy", "male:shark boy",
"male:shaved head",
"male:sheep", "male:sheep",
"male:sheep boy", "male:sheep boy",
"male:shibari", "male:shibari",
"male:shimaidon",
"male:shimapan", "male:shimapan",
"male:shotacon", "male:shotacon",
"male:shrinking", "male:shrinking",
"male:skinsuit", "male:skinsuit",
"male:skunk boy",
"male:slave", "male:slave",
"male:sleeping", "male:sleeping",
"male:slime", "male:slime",
"male:slime boy", "male:slime boy",
"male:slug", "male:slug",
"male:small penis", "male:small penis",
"male:smalldom",
"male:smegma", "male:smegma",
"male:smell", "male:smell",
"male:smoking", "male:smoking",
"male:snake", "male:snake",
"male:snake boy", "male:snake boy",
"male:snuff", "male:snuff",
"male:sockjob",
"male:sole male", "male:sole male",
"male:sole pussyboy",
"male:solo action", "male:solo action",
"male:spanking", "male:spanking",
"male:speculum", "male:speculum",
"male:spider", "male:spider",
"male:spider boy",
"male:squid boy", "male:squid boy",
"male:squirrel boy",
"male:ssbbm",
"male:steward",
"male:stewardess", "male:stewardess",
"male:stirrup legwear",
"male:stockings", "male:stockings",
"male:stomach deformation", "male:stomach deformation",
"male:straitjacket",
"male:strap-on",
"male:stretching", "male:stretching",
"male:stuck in wall", "male:stuck in wall",
"male:sumata",
"male:sundress", "male:sundress",
"male:sunglasses", "male:sunglasses",
"male:sweating", "male:sweating",
"male:swimsuit", "male:swimsuit",
"male:swinging", "male:swinging",
"male:syringe", "male:syringe",
"male:table masturbation",
"male:tail", "male:tail",
"male:tail plug", "male:tail plug",
"male:tailjob",
"male:tailphagia",
"male:tall man", "male:tall man",
"male:tanlines", "male:tanlines",
"male:teacher", "male:teacher",
"male:tentacles", "male:tentacles",
"male:thick eyebrows",
"male:thigh high boots", "male:thigh high boots",
"male:tiara", "male:tiara",
"male:tickling", "male:tickling",
@@ -348,16 +469,23 @@ object Male : TagList {
"male:tracksuit", "male:tracksuit",
"male:trampling", "male:trampling",
"male:transformation", "male:transformation",
"male:triple anal",
"male:triple penetration",
"male:tube", "male:tube",
"male:turtle", "male:turtle",
"male:tutor", "male:tutor",
"male:twins", "male:twins",
"male:twintails",
"male:unbirth", "male:unbirth",
"male:uncle", "male:uncle",
"male:underwater",
"male:unicorn", "male:unicorn",
"male:unusual insertions",
"male:unusual pupils", "male:unusual pupils",
"male:unusual teeth",
"male:urethra insertion", "male:urethra insertion",
"male:urination", "male:urination",
"male:vacbed",
"male:vampire", "male:vampire",
"male:very long hair", "male:very long hair",
"male:virginity", "male:virginity",
@@ -366,6 +494,9 @@ object Male : TagList {
"male:voyeurism", "male:voyeurism",
"male:waiter", "male:waiter",
"male:waitress", "male:waitress",
"male:weight gain",
"male:wet clothes",
"male:whale",
"male:whip", "male:whip",
"male:wings", "male:wings",
"male:witch", "male:witch",
@@ -374,8 +505,11 @@ object Male : TagList {
"male:wooden horse", "male:wooden horse",
"male:worm", "male:worm",
"male:wormhole", "male:wormhole",
"male:wrestling",
"male:x-ray", "male:x-ray",
"male:yandere", "male:yandere",
"male:yaoi", "male:yaoi",
"male:zebra",
"male:zombie",
) )
} }
+2 -2
View File
@@ -1,8 +1,7 @@
package exh.eh.tags package exh.eh.tags
object Mixed : TagList { object Mixed : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"mixed:animal on animal", "mixed:animal on animal",
"mixed:body swap", "mixed:body swap",
"mixed:ffm threesome", "mixed:ffm threesome",
@@ -16,6 +15,7 @@ object Mixed : TagList {
"mixed:multiple assjob", "mixed:multiple assjob",
"mixed:multiple footjob", "mixed:multiple footjob",
"mixed:multiple handjob", "mixed:multiple handjob",
"mixed:nudism",
"mixed:oyakodon", "mixed:oyakodon",
"mixed:shimaidon", "mixed:shimaidon",
"mixed:ttm threesome", "mixed:ttm threesome",
+5 -3
View File
@@ -1,9 +1,9 @@
package exh.eh.tags package exh.eh.tags
object Other : TagList { object Other : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"other:3d", "other:3d",
"other:3d imageset",
"other:already uploaded", "other:already uploaded",
"other:anaglyph", "other:anaglyph",
"other:animated", "other:animated",
@@ -13,10 +13,12 @@ object Other : TagList {
"other:comic", "other:comic",
"other:compilation", "other:compilation",
"other:dakimakura", "other:dakimakura",
"other:defaced",
"other:figure", "other:figure",
"other:forbidden content", "other:forbidden content",
"other:full censorship", "other:full censorship",
"other:full color", "other:full color",
"other:game manual",
"other:game sprite", "other:game sprite",
"other:goudoushi", "other:goudoushi",
"other:hardcore", "other:hardcore",
@@ -33,10 +35,10 @@ object Other : TagList {
"other:nudity only", "other:nudity only",
"other:out of order", "other:out of order",
"other:paperchild", "other:paperchild",
"other:poor grammar",
"other:realporn", "other:realporn",
"other:redraw", "other:redraw",
"other:replaced", "other:replaced",
"other:rough grammar",
"other:rough translation", "other:rough translation",
"other:sample", "other:sample",
"other:scanmark", "other:scanmark",
+68 -7
View File
@@ -1,7 +1,7 @@
package exh.eh.tags package exh.eh.tags
object Parody : TagList { object Parody : TagList {
override fun getTags1() = listOf( override fun getTags1(): List<String> = listOf(
"parody:.hack", "parody:.hack",
"parody:.hackg.u.", "parody:.hackg.u.",
"parody:.hacklegend of the twilight", "parody:.hacklegend of the twilight",
@@ -31,6 +31,7 @@ object Parody : TagList {
"parody:a town where you live", "parody:a town where you live",
"parody:a vampyre story", "parody:a vampyre story",
"parody:a.d.police", "parody:a.d.police",
"parody:a.i. ga tomaranai",
"parody:abenobashi mahou shoutengai", "parody:abenobashi mahou shoutengai",
"parody:acca 13-ku kansatsu-ka", "parody:acca 13-ku kansatsu-ka",
"parody:accel world", "parody:accel world",
@@ -66,8 +67,10 @@ object Parody : TagList {
"parody:akira", "parody:akira",
"parody:aku no musume", "parody:aku no musume",
"parody:aku no onna kanbu", "parody:aku no onna kanbu",
"parody:akuma no memumemu-chan",
"parody:akuyaku reijou nanode last boss o kattemimashita", "parody:akuyaku reijou nanode last boss o kattemimashita",
"parody:aladdin", "parody:aladdin",
"parody:albatross koukairoku",
"parody:aldnoah.zero", "parody:aldnoah.zero",
"parody:alice gear aegis", "parody:alice gear aegis",
"parody:alice in wonderland", "parody:alice in wonderland",
@@ -124,6 +127,7 @@ object Parody : TagList {
"parody:ar nosurge", "parody:ar nosurge",
"parody:ar tonelico", "parody:ar tonelico",
"parody:ar tonelico qoga", "parody:ar tonelico qoga",
"parody:araburu kisetsu no otome-domo yo",
"parody:araiguma rascal", "parody:araiguma rascal",
"parody:arasaa mama no watashi de ii no", "parody:arasaa mama no watashi de ii no",
"parody:arcana famiglia", "parody:arcana famiglia",
@@ -194,6 +198,7 @@ object Parody : TagList {
"parody:backbeard-sama ga miteru", "parody:backbeard-sama ga miteru",
"parody:bagi the monster of mighty nature", "parody:bagi the monster of mighty nature",
"parody:baka to test to shoukanjuu", "parody:baka to test to shoukanjuu",
"parody:bakemono no ko",
"parody:bakemonogatari", "parody:bakemonogatari",
"parody:bakuen campus guardress", "parody:bakuen campus guardress",
"parody:bakugan", "parody:bakugan",
@@ -206,6 +211,7 @@ object Parody : TagList {
"parody:ballroom e youkoso", "parody:ballroom e youkoso",
"parody:bamboo blade", "parody:bamboo blade",
"parody:band yarouze", "parody:band yarouze",
"parody:banished from the heros party i decided to live a quiet life in the countryside",
"parody:banjo-kazooie", "parody:banjo-kazooie",
"parody:banner of the stars", "parody:banner of the stars",
"parody:baribari densetsu", "parody:baribari densetsu",
@@ -271,6 +277,7 @@ object Parody : TagList {
"parody:bloodstained", "parody:bloodstained",
"parody:bloody roar", "parody:bloody roar",
"parody:blue dragon", "parody:blue dragon",
"parody:blue skin no mori",
"parody:blue spring ride", "parody:blue spring ride",
"parody:blue submarine no. 6", "parody:blue submarine no. 6",
"parody:bna brand new animal", "parody:bna brand new animal",
@@ -355,6 +362,7 @@ object Parody : TagList {
"parody:childs play", "parody:childs play",
"parody:chio-chan no tsuugakuro", "parody:chio-chan no tsuugakuro",
"parody:chip n dale rescue rangers", "parody:chip n dale rescue rangers",
"parody:cho aniki",
"parody:chobits", "parody:chobits",
"parody:chogattai majutsu robot ginguiser", "parody:chogattai majutsu robot ginguiser",
"parody:chokotto sister", "parody:chokotto sister",
@@ -428,6 +436,7 @@ object Parody : TagList {
"parody:daiakuji", "parody:daiakuji",
"parody:daibanchou -big bang age-", "parody:daibanchou -big bang age-",
"parody:daicon", "parody:daicon",
"parody:daihanjou manpuku marche",
"parody:daikoukai jidai", "parody:daikoukai jidai",
"parody:daisenryaku", "parody:daisenryaku",
"parody:daiya no ace", "parody:daiya no ace",
@@ -440,6 +449,7 @@ object Parody : TagList {
"parody:dantalian no shoka", "parody:dantalian no shoka",
"parody:daphne in the brilliant blue", "parody:daphne in the brilliant blue",
"parody:dark chronicle", "parody:dark chronicle",
"parody:dark messiah",
"parody:dark water", "parody:dark water",
"parody:darker than black", "parody:darker than black",
"parody:darkstalkers", "parody:darkstalkers",
@@ -457,6 +467,7 @@ object Parody : TagList {
"parody:defenders", "parody:defenders",
"parody:defense devil", "parody:defense devil",
"parody:defense of the ancients", "parody:defense of the ancients",
"parody:delicious party precure",
"parody:demento", "parody:demento",
"parody:demi-chan wa kataritai", "parody:demi-chan wa kataritai",
"parody:demonbane", "parody:demonbane",
@@ -480,6 +491,7 @@ object Parody : TagList {
"parody:devil may cry", "parody:devil may cry",
"parody:devil summoner soul hackers", "parody:devil summoner soul hackers",
"parody:devil survivor", "parody:devil survivor",
"parody:devilman lady",
"parody:dexters laboratory", "parody:dexters laboratory",
"parody:di gi charat", "parody:di gi charat",
"parody:diablo", "parody:diablo",
@@ -550,6 +562,7 @@ object Parody : TagList {
"parody:dragonaut", "parody:dragonaut",
"parody:dragonica", "parody:dragonica",
"parody:dragons crown", "parody:dragons crown",
"parody:dragons raiden",
"parody:drakengard", "parody:drakengard",
"parody:drawn together", "parody:drawn together",
"parody:dream c club", "parody:dream c club",
@@ -619,6 +632,7 @@ object Parody : TagList {
"parody:family project", "parody:family project",
"parody:fancy lala", "parody:fancy lala",
"parody:fantastic four", "parody:fantastic four",
"parody:fantasy bishoujo juniku ojisan to",
"parody:fantasy earth zero", "parody:fantasy earth zero",
"parody:far east of eden kabuki klash", "parody:far east of eden kabuki klash",
"parody:fatal frame", "parody:fatal frame",
@@ -706,6 +720,7 @@ object Parody : TagList {
"parody:fushigi no umi no nadia", "parody:fushigi no umi no nadia",
"parody:fushigi yuugi", "parody:fushigi yuugi",
"parody:fushigiboshi no futagohime", "parody:fushigiboshi no futagohime",
"parody:futaba channel",
"parody:futakoi", "parody:futakoi",
"parody:futari ecchi", "parody:futari ecchi",
"parody:futari wa precure splash star", "parody:futari wa precure splash star",
@@ -723,6 +738,7 @@ object Parody : TagList {
"parody:ga-rei", "parody:ga-rei",
"parody:gad guard", "parody:gad guard",
"parody:gaiking", "parody:gaiking",
"parody:gaikotsu kishi-sama tadaima isekai e odekakechuu",
"parody:gakkou gurashi", "parody:gakkou gurashi",
"parody:gakkou no kaidan", "parody:gakkou no kaidan",
"parody:gakuen alice", "parody:gakuen alice",
@@ -733,6 +749,7 @@ object Parody : TagList {
"parody:galaxy cyclone braiger", "parody:galaxy cyclone braiger",
"parody:galaxy express 999", "parody:galaxy express 999",
"parody:galaxy fraulein yuna", "parody:galaxy fraulein yuna",
"parody:galleria no chika meikyuu to majo no ryodan",
"parody:gan kon", "parody:gan kon",
"parody:ganbare goemon", "parody:ganbare goemon",
"parody:gangsta.", "parody:gangsta.",
@@ -753,6 +770,7 @@ object Parody : TagList {
"parody:gen 13", "parody:gen 13",
"parody:gen colon lock", "parody:gen colon lock",
"parody:genji tsuushin agedama", "parody:genji tsuushin agedama",
"parody:genkai tokki monster monpiece",
"parody:genmu no tou to tsurugi no okite", "parody:genmu no tou to tsurugi no okite",
"parody:genmu senki leda", "parody:genmu senki leda",
"parody:genroh", "parody:genroh",
@@ -824,6 +842,7 @@ object Parody : TagList {
"parody:gundam 00", "parody:gundam 00",
"parody:gundam 0083", "parody:gundam 0083",
"parody:gundam age", "parody:gundam age",
"parody:gundam breaker mobile",
"parody:gundam build fighters", "parody:gundam build fighters",
"parody:gundam g no reconguista", "parody:gundam g no reconguista",
"parody:gundam seed", "parody:gundam seed",
@@ -856,6 +875,7 @@ object Parody : TagList {
"parody:hakumei to mikochi", "parody:hakumei to mikochi",
"parody:hakuouki", "parody:hakuouki",
"parody:hakushaku to yousei", "parody:hakushaku to yousei",
"parody:hakushon daimaou",
"parody:half-life", "parody:half-life",
"parody:halo", "parody:halo",
"parody:hamtaro", "parody:hamtaro",
@@ -906,8 +926,10 @@ object Parody : TagList {
"parody:highschool dxd", "parody:highschool dxd",
"parody:highschool of the dead", "parody:highschool of the dead",
"parody:higurashi no naku koro ni", "parody:higurashi no naku koro ni",
"parody:higyaku no noel",
"parody:hikarian", "parody:hikarian",
"parody:hikaru no go", "parody:hikaru no go",
"parody:hikounin sentai akibaranger",
"parody:hime chen otogi chikku idol lilpri", "parody:hime chen otogi chikku idol lilpri",
"parody:hime kishi lilia", "parody:hime kishi lilia",
"parody:hime-chans ribbon", "parody:hime-chans ribbon",
@@ -918,6 +940,7 @@ object Parody : TagList {
"parody:history kikan", "parody:history kikan",
"parody:historys strongest disciple kenichi", "parody:historys strongest disciple kenichi",
"parody:hitomi no karte", "parody:hitomi no karte",
"parody:hitomi-chan wa hitomishiri",
"parody:hitomi-sensei no hokenshitsu", "parody:hitomi-sensei no hokenshitsu",
"parody:hitori bocchi no marumaru seikatsu", "parody:hitori bocchi no marumaru seikatsu",
"parody:hitsugi katsugi no kuro.", "parody:hitsugi katsugi no kuro.",
@@ -978,6 +1001,7 @@ object Parody : TagList {
"parody:ikoku meiro no croisee", "parody:ikoku meiro no croisee",
"parody:imouto sae ireba ii.", "parody:imouto sae ireba ii.",
"parody:in search of the lost future", "parody:in search of the lost future",
"parody:inaka ni kaeru to yakeni natsuita kasshoku ponytail shota ga iru",
"parody:inazuma eleven", "parody:inazuma eleven",
"parody:inazuman", "parody:inazuman",
"parody:inda no himekishi janne", "parody:inda no himekishi janne",
@@ -998,6 +1022,7 @@ object Parody : TagList {
"parody:is", "parody:is",
"parody:isekai izakaya nobu", "parody:isekai izakaya nobu",
"parody:isekai maou to shoukan shoujo no dorei majutsu", "parody:isekai maou to shoukan shoujo no dorei majutsu",
"parody:isekai meikyuu de harem o",
"parody:isekai no seikishi monogatari", "parody:isekai no seikishi monogatari",
"parody:isekai shokudou", "parody:isekai shokudou",
"parody:isekai wa smartphone to tomo ni.", "parody:isekai wa smartphone to tomo ni.",
@@ -1007,6 +1032,7 @@ object Parody : TagList {
"parody:its not my fault that im not popular", "parody:its not my fault that im not popular",
"parody:iwa kakeru", "parody:iwa kakeru",
"parody:ixion saga dt", "parody:ixion saga dt",
"parody:iya na kao sare nagara opantsu misete moraitai",
"parody:izuna legend of the unemployed ninja", "parody:izuna legend of the unemployed ninja",
"parody:jackie chan adventures", "parody:jackie chan adventures",
"parody:jaja uma grooming up", "parody:jaja uma grooming up",
@@ -1035,9 +1061,11 @@ object Parody : TagList {
"parody:johnny bravo", "parody:johnny bravo",
"parody:johnny test", "parody:johnny test",
"parody:jojos bizarre adventure", "parody:jojos bizarre adventure",
"parody:joshikousei girls high",
"parody:joshikousei no mudazukai", "parody:joshikousei no mudazukai",
"parody:joukamachi no dandelion", "parody:joukamachi no dandelion",
"parody:jouki toshi no tantei shoujo", "parody:jouki toshi no tantei shoujo",
"parody:ju-on",
"parody:jubei-chan", "parody:jubei-chan",
"parody:jumping rabbit", "parody:jumping rabbit",
"parody:jungle de ikou", "parody:jungle de ikou",
@@ -1060,11 +1088,13 @@ object Parody : TagList {
"parody:k.o. beast", "parody:k.o. beast",
"parody:kaerunyo panyorn", "parody:kaerunyo panyorn",
"parody:kage no densetsu", "parody:kage no densetsu",
"parody:kageki shojo",
"parody:kagihime monogatari eikyuu alice rondo", "parody:kagihime monogatari eikyuu alice rondo",
"parody:kaguya-sama wa kokurasetai", "parody:kaguya-sama wa kokurasetai",
"parody:kaichou wa maid-sama", "parody:kaichou wa maid-sama",
"parody:kaifuku jutsushi no yarinaoshi", "parody:kaifuku jutsushi no yarinaoshi",
"parody:kaiji", "parody:kaiji",
"parody:kaijin kaihatsubu no kuroitsu-san",
"parody:kaiketsu zorro", "parody:kaiketsu zorro",
"parody:kaitou tenshi twin angel", "parody:kaitou tenshi twin angel",
"parody:kaizoku sentai gokaiger", "parody:kaizoku sentai gokaiger",
@@ -1080,6 +1110,7 @@ object Parody : TagList {
"parody:kami-tachi ni hirowareta otoko", "parody:kami-tachi ni hirowareta otoko",
"parody:kamikaze kaitou jeanne", "parody:kamikaze kaitou jeanne",
"parody:kamisama dolls", "parody:kamisama dolls",
"parody:kamisama hajimemashita",
"parody:kamisama minarai himitsu no cocotama", "parody:kamisama minarai himitsu no cocotama",
"parody:kamisama ni natta hi", "parody:kamisama ni natta hi",
"parody:kampfer", "parody:kampfer",
@@ -1103,6 +1134,7 @@ object Parody : TagList {
"parody:katekyo hitman reborn", "parody:katekyo hitman reborn",
"parody:katsute mahou shoujo to aku wa tekitai shite ita.", "parody:katsute mahou shoujo to aku wa tekitai shite ita.",
"parody:katte ni kaizou", "parody:katte ni kaizou",
"parody:kawaii dake ja nai shikimori-san",
"parody:kawaikereba hentai demo suki ni natte kuremasu ka", "parody:kawaikereba hentai demo suki ni natte kuremasu ka",
"parody:kaze no na wa amnesia", "parody:kaze no na wa amnesia",
"parody:kaze no yojimbo", "parody:kaze no yojimbo",
@@ -1113,6 +1145,7 @@ object Parody : TagList {
"parody:kemeko deluxe", "parody:kemeko deluxe",
"parody:kemono friends", "parody:kemono friends",
"parody:kemono jihen", "parody:kemono jihen",
"parody:kenja no deshi o nanoru kenja",
"parody:kenja no mago", "parody:kenja no mago",
"parody:kenka banchou otome", "parody:kenka banchou otome",
"parody:kenkou zenrakei suieibu umishou", "parody:kenkou zenrakei suieibu umishou",
@@ -1146,6 +1179,7 @@ object Parody : TagList {
"parody:kingdom hearts", "parody:kingdom hearts",
"parody:kinnikuman", "parody:kinnikuman",
"parody:kino no tabi", "parody:kino no tabi",
"parody:kinsou no vermeil",
"parody:kira kira", "parody:kira kira",
"parody:kirakira precure a la mode", "parody:kirakira precure a la mode",
"parody:kirarin revolution", "parody:kirarin revolution",
@@ -1173,6 +1207,7 @@ object Parody : TagList {
"parody:komi-san wa komyushou desu.", "parody:komi-san wa komyushou desu.",
"parody:konjiki no word master", "parody:konjiki no word master",
"parody:kono aozora ni yakusoku o", "parody:kono aozora ni yakusoku o",
"parody:kono healer mendokusai",
"parody:kono oto tomare", "parody:kono oto tomare",
"parody:kono subarashii sekai ni syukufuku o", "parody:kono subarashii sekai ni syukufuku o",
"parody:konto koroshiya 1989", "parody:konto koroshiya 1989",
@@ -1216,9 +1251,11 @@ object Parody : TagList {
"parody:kyoukai no rinne", "parody:kyoukai no rinne",
"parody:kyoukai senjou no horizon", "parody:kyoukai senjou no horizon",
"parody:kyouryuu wakusei", "parody:kyouryuu wakusei",
"parody:kyuuketsuki sugu shinu",
"parody:kyuukyoku choujin r", "parody:kyuukyoku choujin r",
"parody:kyuukyoku hentai kamen", "parody:kyuukyoku hentai kamen",
"parody:kyuukyoku shinka shita full dive rpg ga genjitsu yori mo kusogee dattara", "parody:kyuukyoku shinka shita full dive rpg ga genjitsu yori mo kusogee dattara",
"parody:kyuukyuu sentai gogofive",
"parody:kyuushu sentai danjija", "parody:kyuushu sentai danjija",
"parody:la corda doro", "parody:la corda doro",
"parody:la pucelle", "parody:la pucelle",
@@ -1232,6 +1269,7 @@ object Parody : TagList {
"parody:le ranch", "parody:le ranch",
"parody:league of legends", "parody:league of legends",
"parody:left 4 dead", "parody:left 4 dead",
"parody:legend of legaia",
"parody:legend of lyon flare", "parody:legend of lyon flare",
"parody:legend of queen opala", "parody:legend of queen opala",
"parody:legend of the cryptids", "parody:legend of the cryptids",
@@ -1326,6 +1364,8 @@ object Parody : TagList {
"parody:mahouka koukou no rettousei", "parody:mahouka koukou no rettousei",
"parody:mahoutsukai no yakusoku", "parody:mahoutsukai no yakusoku",
"parody:mahoutsukai no yome", "parody:mahoutsukai no yome",
"parody:mahoutsukai reimeiki",
"parody:mai mai miracle",
"parody:mai-hime", "parody:mai-hime",
"parody:mai-otome", "parody:mai-otome",
"parody:mairimashita iruma-kun", "parody:mairimashita iruma-kun",
@@ -1383,6 +1423,7 @@ object Parody : TagList {
"parody:mayoi neko overrun", "parody:mayoi neko overrun",
"parody:maze runner", "parody:maze runner",
"parody:mazinger z", "parody:mazinger z",
"parody:me me me",
"parody:mecha mote", "parody:mecha mote",
"parody:mechakko dotakon", "parody:mechakko dotakon",
"parody:medabots", "parody:medabots",
@@ -1423,6 +1464,7 @@ object Parody : TagList {
"parody:mirmo de pon", "parody:mirmo de pon",
"parody:miss machiko", "parody:miss machiko",
"parody:mission impossible", "parody:mission impossible",
"parody:misumisou",
"parody:mitsudomoe", "parody:mitsudomoe",
"parody:mitsume ga tooru", "parody:mitsume ga tooru",
"parody:mitsumete knight", "parody:mitsumete knight",
@@ -1479,7 +1521,6 @@ object Parody : TagList {
"parody:muv-luv alternative total eclipse", "parody:muv-luv alternative total eclipse",
"parody:mx0", "parody:mx0",
"parody:my dad the rock star", "parody:my dad the rock star",
"parody:my dress-up darling",
"parody:my hero academia", "parody:my hero academia",
"parody:my life as a teenage robot", "parody:my life as a teenage robot",
"parody:my little pony friendship is magic", "parody:my little pony friendship is magic",
@@ -1508,6 +1549,7 @@ object Parody : TagList {
"parody:nazo no kanojo x", "parody:nazo no kanojo x",
"parody:nee chanto shiyou yo", "parody:nee chanto shiyou yo",
"parody:nee summer", "parody:nee summer",
"parody:needy streamer overload",
"parody:nejimaki seirei senki tenkyou no alderamin", "parody:nejimaki seirei senki tenkyou no alderamin",
"parody:nekketsu saikyou go-saurer", "parody:nekketsu saikyou go-saurer",
"parody:nekome kozou", "parody:nekome kozou",
@@ -1563,6 +1605,7 @@ object Parody : TagList {
"parody:oda nobuna no yabou", "parody:oda nobuna no yabou",
"parody:odin sphere", "parody:odin sphere",
"parody:odoru daisousasen", "parody:odoru daisousasen",
"parody:odyssey",
"parody:oira uchuu no tankoufu", "parody:oira uchuu no tankoufu",
"parody:ojama yurei-kun", "parody:ojama yurei-kun",
"parody:ojamajo doremi", "parody:ojamajo doremi",
@@ -1636,6 +1679,7 @@ object Parody : TagList {
"parody:parappa the rapper", "parody:parappa the rapper",
"parody:parasite eve", "parody:parasite eve",
"parody:parasyte", "parody:parasyte",
"parody:paripi koumei",
"parody:pastel", "parody:pastel",
"parody:pastel yumi", "parody:pastel yumi",
"parody:patlabor", "parody:patlabor",
@@ -1709,6 +1753,7 @@ object Parody : TagList {
"parody:pu-li-ru-la", "parody:pu-li-ru-la",
"parody:puella magi madoka magica", "parody:puella magi madoka magica",
"parody:pumpkin scissors", "parody:pumpkin scissors",
"parody:punch-out",
"parody:puppet princess of marl kingdom", "parody:puppet princess of marl kingdom",
"parody:pussy saga", "parody:pussy saga",
"parody:puyo puyo", "parody:puyo puyo",
@@ -1740,6 +1785,7 @@ object Parody : TagList {
"parody:red pride of eden", "parody:red pride of eden",
"parody:redline", "parody:redline",
"parody:redwall", "parody:redwall",
"parody:refrain no chika meikyuu to majo no ryodan",
"parody:regalia the three sacred stars", "parody:regalia the three sacred stars",
"parody:reibaishi izuna", "parody:reibaishi izuna",
"parody:remi nobodys girl", "parody:remi nobodys girl",
@@ -1822,6 +1868,7 @@ object Parody : TagList {
"parody:sarah and duck", "parody:sarah and duck",
"parody:sasameki koto", "parody:sasameki koto",
"parody:sasami magical girls club", "parody:sasami magical girls club",
"parody:satsukare",
"parody:savage reign", "parody:savage reign",
"parody:sayonara zetsubou sensei", "parody:sayonara zetsubou sensei",
"parody:school days", "parody:school days",
@@ -1838,6 +1885,7 @@ object Parody : TagList {
"parody:seiken densetsu", "parody:seiken densetsu",
"parody:seiken densetsu 2", "parody:seiken densetsu 2",
"parody:seiken densetsu 3", "parody:seiken densetsu 3",
"parody:seiken densetsu ds",
"parody:seikesshou albatross", "parody:seikesshou albatross",
"parody:seikon no qwaser", "parody:seikon no qwaser",
"parody:seirei no moribito", "parody:seirei no moribito",
@@ -1869,12 +1917,14 @@ object Parody : TagList {
"parody:sensei no bulge", "parody:sensei no bulge",
"parody:sentimental graffiti", "parody:sentimental graffiti",
"parody:sentouin hakenshimasu", "parody:sentouin hakenshimasu",
"parody:senyoku no sigrdrifa",
"parody:serial experiments lain", "parody:serial experiments lain",
"parody:seto no hanayome", "parody:seto no hanayome",
"parody:seven mortal sins", "parody:seven mortal sins",
"parody:seven of seven", "parody:seven of seven",
"parody:sewayaki kitsune no senko-san", "parody:sewayaki kitsune no senko-san",
"parody:sexfriend", "parody:sexfriend",
"parody:shachiku-san wa youjo yuurei ni iyasaretai.",
"parody:shadow of the colossus", "parody:shadow of the colossus",
"parody:shakugan no shana", "parody:shakugan no shana",
"parody:shakunetsu no nirai kanai", "parody:shakunetsu no nirai kanai",
@@ -1912,6 +1962,7 @@ object Parody : TagList {
"parody:shinryaku ika musume", "parody:shinryaku ika musume",
"parody:shinseiki inma seiden", "parody:shinseiki inma seiden",
"parody:shinsekai yori", "parody:shinsekai yori",
"parody:shiroi suna no aquatope",
"parody:shironeko project", "parody:shironeko project",
"parody:shisha no teikoku", "parody:shisha no teikoku",
"parody:shishunki renaissance david-kun", "parody:shishunki renaissance david-kun",
@@ -1951,9 +2002,13 @@ object Parody : TagList {
"parody:snow white and the seven dwarfs", "parody:snow white and the seven dwarfs",
"parody:sokihei m.d. geist", "parody:sokihei m.d. geist",
"parody:solatorobo", "parody:solatorobo",
)
override fun getTags2(): List<String> = listOf(
"parody:soltyrei", "parody:soltyrei",
"parody:sonic soldier borgman", "parody:sonic soldier borgman",
"parody:sonic the hedgehog", "parody:sonic the hedgehog",
"parody:sono bisque doll wa koi o suru",
"parody:sono hanabira ni kuchizuke o", "parody:sono hanabira ni kuchizuke o",
"parody:sora no iro mizu no iro", "parody:sora no iro mizu no iro",
"parody:sora no kanata no dystopia", "parody:sora no kanata no dystopia",
@@ -1967,6 +2022,7 @@ object Parody : TagList {
"parody:soredemo machi wa mawatteiru", "parody:soredemo machi wa mawatteiru",
"parody:soredemo tsuma o aishiteru", "parody:soredemo tsuma o aishiteru",
"parody:soromon no kagi", "parody:soromon no kagi",
"parody:sou-bou-tei kowasubeshi",
"parody:soukou kijo iris", "parody:soukou kijo iris",
"parody:soukyuu no fafner", "parody:soukyuu no fafner",
"parody:soul cradle", "parody:soul cradle",
@@ -2002,9 +2058,6 @@ object Parody : TagList {
"parody:star gladiator", "parody:star gladiator",
"parody:star ocean", "parody:star ocean",
"parody:star ocean 2", "parody:star ocean 2",
)
override fun getTags2() = listOf(
"parody:star ocean 3", "parody:star ocean 3",
"parody:star ocean 4", "parody:star ocean 4",
"parody:star trek", "parody:star trek",
@@ -2055,6 +2108,7 @@ object Parody : TagList {
"parody:sword art online", "parody:sword art online",
"parody:sword art online alternative gun gale online", "parody:sword art online alternative gun gale online",
"parody:sword girls", "parody:sword girls",
"parody:sword of the stranger",
"parody:sword world rpg", "parody:sword world rpg",
"parody:sym-bionic titan", "parody:sym-bionic titan",
"parody:t.u.f.f. puppy", "parody:t.u.f.f. puppy",
@@ -2087,6 +2141,7 @@ object Parody : TagList {
"parody:tantei opera milky holmes", "parody:tantei opera milky holmes",
"parody:tantei wa mou shindeiru.", "parody:tantei wa mou shindeiru.",
"parody:tari tari", "parody:tari tari",
"parody:tartaros",
"parody:tasogare otome x amnesia", "parody:tasogare otome x amnesia",
"parody:tasogare sakaba uwabami breakers", "parody:tasogare sakaba uwabami breakers",
"parody:tatakae tarantella", "parody:tatakae tarantella",
@@ -2236,6 +2291,7 @@ object Parody : TagList {
"parody:the x-files", "parody:the x-files",
"parody:they are my noble masters", "parody:they are my noble masters",
"parody:this ugly yet beautiful world", "parody:this ugly yet beautiful world",
"parody:thomas the tank engine and friends",
"parody:threads of fate", "parody:threads of fate",
"parody:three from prostokvashino", "parody:three from prostokvashino",
"parody:thundercats", "parody:thundercats",
@@ -2244,8 +2300,7 @@ object Parody : TagList {
"parody:tiny toons", "parody:tiny toons",
"parody:to heart", "parody:to heart",
"parody:to love-ru", "parody:to love-ru",
"parody:toaru kagaku no railgun", "parody:toaru project",
"parody:toaru majutsu no index",
"parody:tobe isami", "parody:tobe isami",
"parody:togainu no chi", "parody:togainu no chi",
"parody:toheart2", "parody:toheart2",
@@ -2259,6 +2314,7 @@ object Parody : TagList {
"parody:tokyo mew mew", "parody:tokyo mew mew",
"parody:tokyo red hood", "parody:tokyo red hood",
"parody:tom and jerry", "parody:tom and jerry",
"parody:tom clancys ghost recon",
"parody:tom clancys rainbow six", "parody:tom clancys rainbow six",
"parody:tomb raider", "parody:tomb raider",
"parody:tomodachi no imouto ga ore ni dake uzai", "parody:tomodachi no imouto ga ore ni dake uzai",
@@ -2306,6 +2362,7 @@ object Parody : TagList {
"parody:twinkle review", "parody:twinkle review",
"parody:uchi no musume ni te o dasuna", "parody:uchi no musume ni te o dasuna",
"parody:uchouten kazoku", "parody:uchouten kazoku",
"parody:uchuu eiyuu monogatari",
"parody:uchuu kaizoku sara", "parody:uchuu kaizoku sara",
"parody:uchuu kazoku carlvinson", "parody:uchuu kazoku carlvinson",
"parody:uchuu no kishi tekkaman", "parody:uchuu no kishi tekkaman",
@@ -2332,6 +2389,7 @@ object Parody : TagList {
"parody:uta no prince-sama", "parody:uta no prince-sama",
"parody:utawarerumono", "parody:utawarerumono",
"parody:utawarerumono itsuwari no kamen", "parody:utawarerumono itsuwari no kamen",
"parody:utsukushiki sei no dendoushi rei rei",
"parody:uzaki-chan wa asobitai", "parody:uzaki-chan wa asobitai",
"parody:va-11 hall-a", "parody:va-11 hall-a",
"parody:valkyria chronicles", "parody:valkyria chronicles",
@@ -2412,6 +2470,7 @@ object Parody : TagList {
"parody:witchblade", "parody:witchblade",
"parody:witchs weapon", "parody:witchs weapon",
"parody:with you", "parody:with you",
"parody:wixoss diva a live",
"parody:wizard of oz", "parody:wizard of oz",
"parody:wolfs rain", "parody:wolfs rain",
"parody:wooser no sonohigurashi", "parody:wooser no sonohigurashi",
@@ -2437,6 +2496,7 @@ object Parody : TagList {
"parody:yagate kimi ni naru", "parody:yagate kimi ni naru",
"parody:yahari ore no seishun love come wa machigatteiru", "parody:yahari ore no seishun love come wa machigatteiru",
"parody:yakitate japan", "parody:yakitate japan",
"parody:yakumo-san wa ezuke ga shitai.",
"parody:yakushiji ryouko no kaiki jikenbo", "parody:yakushiji ryouko no kaiki jikenbo",
"parody:yakusoku no neverland", "parody:yakusoku no neverland",
"parody:yama no susume", "parody:yama no susume",
@@ -2453,6 +2513,7 @@ object Parody : TagList {
"parody:yiik", "parody:yiik",
"parody:yin yang yo", "parody:yin yang yo",
"parody:yoake mae yori ruriiro na", "parody:yoake mae yori ruriiro na",
"parody:yofukashi no uta",
"parody:yokohama kaidashi kikou", "parody:yokohama kaidashi kikou",
"parody:yomawari", "parody:yomawari",
"parody:yondemasuyo azazel-san", "parody:yondemasuyo azazel-san",
@@ -1,8 +1,7 @@
package exh.eh.tags package exh.eh.tags
object ReClass : TagList { object Reclass : TagList {
override fun getTags1(): List<String> = listOf(
override fun getTags1() = listOf(
"reclass:artistcg", "reclass:artistcg",
"reclass:asianporn", "reclass:asianporn",
"reclass:cosplay", "reclass:cosplay",
+1 -4
View File
@@ -7,12 +7,9 @@ interface TagList {
fun getTags3(): List<String> = emptyList() fun getTags3(): List<String> = emptyList()
fun getTags4(): List<String> = emptyList() fun getTags(): List<List<String>> = listOf(
fun getTags() = listOf(
getTags1(), getTags1(),
getTags2(), getTags2(),
getTags3(), getTags3(),
getTags4(),
) )
} }
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@@ -13,11 +12,11 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
class AzukiHandler(currentClient: OkHttpClient) { class AzukiHandler(currentClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://www.azuki.co" val baseUrl = "https://www.azuki.co"
private val apiUrl = "https://production.api.azuki.co" private val apiUrl = "https://production.api.azuki.co"
val headers = Headers.Builder() val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT) .add("User-Agent", userAgent)
.build() .build()
val client: OkHttpClient = currentClient val client: OkHttpClient = currentClient
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.booleanOrNull
@@ -16,11 +15,11 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
class ComikeyHandler(cloudflareClient: OkHttpClient) { class ComikeyHandler(cloudflareClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://comikey.com" val baseUrl = "https://comikey.com"
private val apiUrl = "$baseUrl/sapi" private val apiUrl = "$baseUrl/sapi"
val headers = Headers.Builder() val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT) .add("User-Agent", userAgent)
.build() .build()
val client: OkHttpClient = cloudflareClient val client: OkHttpClient = cloudflareClient
@@ -3,7 +3,6 @@ package exh.md.handlers
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@@ -12,11 +11,11 @@ import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
class MangaHotHandler(currentClient: OkHttpClient) { class MangaHotHandler(currentClient: OkHttpClient, userAgent: String) {
val baseUrl = "https://mangahot.jp" val baseUrl = "https://mangahot.jp"
private val apiUrl = "https://api.mangahot.jp" private val apiUrl = "https://api.mangahot.jp"
val headers = Headers.Builder() val headers = Headers.Builder()
.add("User-Agent", HttpSource.DEFAULT_USER_AGENT) .add("User-Agent", userAgent)
.build() .build()
val client: OkHttpClient = currentClient val client: OkHttpClient = currentClient
+46 -43
View File
@@ -1,49 +1,52 @@
package exh.md.utils package exh.md.utils
@Suppress("unused") @Suppress("unused")
enum class MdLang(val lang: String, val prettyPrint: String, val extLang: String = lang) { enum class MdLang(val lang: String, val extLang: String = lang) {
ENGLISH("en", "English"), ENGLISH("en"),
JAPANESE("ja", "Japanese"), JAPANESE("ja"),
POLISH("pl", "Polish"), POLISH("pl"),
SERBO_CROATIAN("rs", "Serbo-Croatian", "sh"), SERBO_CROATIAN("rs", "sh"),
DUTCH("nl", "Dutch"), DUTCH("nl"),
ITALIAN("it", "IT"), ITALIAN("it"),
RUSSIAN("ru", "Russian"), RUSSIAN("ru"),
GERMAN("de", "German"), GERMAN("de"),
HUNGARIAN("hu", "Hungarian"), HUNGARIAN("hu"),
FRENCH("fr", "French"), FRENCH("fr"),
FINNISH("fi", "Finnish"), FINNISH("fi"),
VIETNAMESE("vi", "Vietnamese"), VIETNAMESE("vi"),
GREEK("el", "Greek"), GREEK("el"),
BULGARIAN("bg", "BULGARIN"), BULGARIAN("bg"),
SPANISH_ES("es", "Spanish (Es)"), SPANISH_ES("es"),
PORTUGUESE_BR("pt-br", "Portuguese (Br)", "pt-BR"), PORTUGUESE_BR("pt-br", "pt-BR"),
PORTUGUESE("pt", "Portuguese (Pt)"), PORTUGUESE("pt"),
SWEDISH("sv", "Swedish"), SWEDISH("sv"),
ARABIC("ar", "Arabic"), ARABIC("ar"),
DANISH("da", "Danish"), DANISH("da"),
CHINESE_SIMPLIFIED("zh", "Chinese (Simp)", "zh-Hans"), CHINESE_SIMPLIFIED("zh", "zh-Hans"),
BENGALI("bn", "Bengali"), BENGALI("bn"),
ROMANIAN("ro", "Romanian"), ROMANIAN("ro"),
CZECH("cs", "Czech"), CZECH("cs"),
MONGOLIAN("mn", "Mongolian"), MONGOLIAN("mn"),
TURKISH("tr", "Turkish"), TURKISH("tr"),
INDONESIAN("id", "Indonesian"), INDONESIAN("id"),
KOREAN("kr", "Korean", "ko"), KOREAN("kr", "ko"),
SPANISH_LATAM("es-la", "Spanish (LATAM)", "es-419"), SPANISH_LATAM("es-la", "es-419"),
PERSIAN("fa", "Persian"), PERSIAN("fa"),
MALAY("ms", "Malay"), MALAY("ms"),
THAI("th", "Thai"), THAI("th"),
CATALAN("ca", "Catalan"), CATALAN("ca"),
FILIPINO("tl", "Filipino", "fil"), FILIPINO("tl", "fil"),
CHINESE_TRAD("zh-hk", "Chinese (Trad)", "zh-Hant"), CHINESE_TRAD("zh-hk", "zh-Hant"),
UKRAINIAN("uk", "Ukrainian"), UKRAINIAN("uk"),
BURMESE("my", "Burmese"), BURMESE("my"),
LINTHUANIAN("lt", "Lithuanian"), LINTHUANIAN("lt"),
HEBREW("he", "Hebrew"), HEBREW("he"),
HINDI("hi", "Hindi"), HINDI("hi"),
NORWEGIAN("no", "Norwegian"), NORWEGIAN("no"),
NEPALI("ne", "Nepali") NEPALI("ne"),
LATIN("la"),
TAMIL("ta"),
KAZAKH("kk"),
; ;
companion object { companion object {
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM17,18H7v-2h10V18zM10.3,14L7,10.7l1.4,-1.4l1.9,1.9l5.3,-5.3L17,7.3L10.3,14z" />
</vector>
@@ -9,8 +9,7 @@
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="center_horizontal"> android:layout_gravity="center_horizontal">
<TextView <TextView
@@ -20,10 +19,12 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:gravity="center" /> android:gravity="center" />
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/dedupe_switch" android:id="@+id/dedupe_switch"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:paddingVertical="16dp"
android:paddingHorizontal="16dp"/>
</LinearLayout> </LinearLayout>

Some files were not shown because too many files have changed in this diff Show More