Compare commits

12 Commits

Author SHA1 Message Date
88c7b31ecc Translate Kavita filters to Korean
Some checks failed
CI / Prepare job (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Lock threads / lock (push) Has been cancelled
2026-05-24 13:34:03 +09:00
7df02d1a2a Remove Kavita admin chapters 2026-05-24 13:09:40 +09:00
2ce0f2049a Add Kavita admin actions to extension
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-24 12:32:32 +09:00
f0de3aaea3 Fix Kavita search pagination
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-24 11:45:25 +09:00
2ddbcc9fe5 Add Kavita EPUB reader handoff
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-24 09:11:57 +09:00
0f6e579b0b Report Kavita extension client info as OPDS reader
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
Lock threads / lock (push) Has been cancelled
2026-05-24 00:34:37 +09:00
d8937783c5 Include client info in reader image URLs
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-24 00:10:54 +09:00
c85c009e17 Send Kavita client info headers
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-23 23:39:20 +09:00
b7f98dee7e Report reader image progress to Kavita
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-23 22:59:50 +09:00
c551317051 Restore upstream cover URL handling
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-23 18:19:34 +09:00
b38824219f Use raw Kavita cover query URLs
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-23 17:59:58 +09:00
ed33de3580 Build cover image URLs safely
Some checks failed
CI / Prepare job (push) Has been cancelled
CI / Build extensions (${{ matrix.chunk.number }}) (push) Has been cancelled
CI / Publish extension repo (push) Has been cancelled
Create Release on Merge / release (push) Has been cancelled
2026-05-23 17:06:57 +09:00
5 changed files with 188 additions and 66 deletions

View File

@@ -2,7 +2,7 @@ ext {
kmkVersionCode = 1
extName = 'Kavita'
extClass = '.KavitaFactory'
extVersionCode = 25
extVersionCode = 37
isNsfw = false
}

View File

@@ -4,34 +4,34 @@ import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSel
import eu.kanade.tachiyomi.source.model.Filter
class SmartFiltersFilter(smartFilters: Array<String>) :
Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters)
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true))
Filter.Select<String>("스마트 필터", arrayOf(noSmartFilterSelected) + smartFilters)
class SortFilter(sortables: Array<String>) : Filter.Sort("정렬", sortables, Selection(0, true))
val sortableList = listOf(
Pair("Sort name", 1),
Pair("Created", 2),
Pair("Last modified", 3),
Pair("Item added", 4),
Pair("Time to Read", 5),
Pair("Release year", 6),
Pair("Read Progress", 7),
Pair("Average Rating", 8),
Pair("Random", 9),
Pair("정렬명", 1),
Pair("생성일", 2),
Pair("마지막 수정", 3),
Pair("추가일", 4),
Pair("읽는 시간", 5),
Pair("발매 연도", 6),
Pair("읽은 진행률", 7),
Pair("평균 평점", 8),
Pair("무작위", 9),
)
class SpecialListFilter(name: String = "Special Lists") : Filter.Select<String>(
class SpecialListFilter(name: String = "특수 목록") : Filter.Select<String>(
name,
arrayOf("None", "Want to Read", "Reading Lists"),
arrayOf("없음", "읽고 싶음", "읽기 목록"),
)
class StatusFilter : Filter.Select<String>(
"Status",
arrayOf("Any", "Unread", "In Progress", "Read"),
"읽기 상태",
arrayOf("전체", "안 읽음", "읽는 중", "읽음"),
)
class StatusSeparator :
Filter.Separator()
class StatusFilterGroup(name: String = "Status") : Filter.Group<StatusFilter>(
class StatusFilterGroup(name: String = "읽기 상태") : Filter.Group<StatusFilter>(
name,
listOf(StatusFilter()),
)
@@ -39,56 +39,56 @@ class StatusFilterGroup(name: String = "Status") : Filter.Group<StatusFilter>(
// Use Filter.Text directly for release year range
class ReleaseYearRange(name: String) : Filter.Text(name, "")
class ReleaseYearRangeGroup(filters: List<ReleaseYearRange>) :
Filter.Group<ReleaseYearRange>("Release Year", filters)
Filter.Group<ReleaseYearRange>("발매 연도", filters)
// Use Filter.TriState for genres/tags/age ratings/collections/languages/libraries
class GenreFilter(name: String) : Filter.TriState(name)
class GenreFilterGroup(genres: List<GenreFilter>) :
Filter.Group<GenreFilter>("Genres", genres)
Filter.Group<GenreFilter>("장르", genres)
class TagFilter(name: String) : Filter.TriState(name)
class TagFilterGroup(tags: List<TagFilter>) :
Filter.Group<TagFilter>("Tags", tags)
Filter.Group<TagFilter>("태그", tags)
class AgeRatingFilter(name: String) : Filter.TriState(name)
class AgeRatingFilterGroup(ageRatings: List<AgeRatingFilter>) :
Filter.Group<AgeRatingFilter>("Age Rating", ageRatings)
Filter.Group<AgeRatingFilter>("연령 등급", ageRatings)
class FormatFilter(name: String) : Filter.CheckBox(name, false)
class FormatsFilterGroup(formats: List<FormatFilter>) :
Filter.Group<FormatFilter>("Formats", formats)
Filter.Group<FormatFilter>("형식", formats)
class CollectionFilter(name: String) : Filter.TriState(name)
class CollectionFilterGroup(collections: List<CollectionFilter>) :
Filter.Group<CollectionFilter>("Collection", collections)
Filter.Group<CollectionFilter>("컬렉션", collections)
class LanguageFilter(name: String) : Filter.TriState(name)
class LanguageFilterGroup(languages: List<LanguageFilter>) :
Filter.Group<LanguageFilter>("Language", languages)
Filter.Group<LanguageFilter>("언어", languages)
class LibraryFilter(library: String) : Filter.TriState(library)
class LibrariesFilterGroup(libraries: List<LibraryFilter>) :
Filter.Group<LibraryFilter>("Libraries", libraries)
Filter.Group<LibraryFilter>("라이브러리", libraries)
class PubStatusFilter(name: String) : Filter.CheckBox(name, false)
class PubStatusFilterGroup(status: List<PubStatusFilter>) :
Filter.Group<PubStatusFilter>("Publication Status", status)
Filter.Group<PubStatusFilter>("연재 상태", status)
class UserRating : Filter.Select<Int>(
"Minimum Rating",
"최소 평점",
arrayOf(
"Any",
"1 star",
"2 stars",
"3 stars",
"4 stars",
"5 stars",
"전체",
"1",
"2",
"3",
"4",
"5",
).mapIndexed { index, _ -> index }.toTypedArray(),
) {
override fun toString(): String {
return when (state) {
0 -> "Any"
else -> "$state star${if (state > 1) "s" else ""}"
0 -> "전체"
else -> "${state}"
}
}
}
@@ -102,40 +102,40 @@ class PeopleSeparatorFilter :
class WriterPeopleFilter(name: String) : Filter.CheckBox(name, false)
class WriterPeopleFilterGroup(peoples: List<WriterPeopleFilter>) :
Filter.Group<WriterPeopleFilter>("Writer", peoples)
Filter.Group<WriterPeopleFilter>("작가", peoples)
class PencillerPeopleFilter(name: String) : Filter.CheckBox(name, false)
class PencillerPeopleFilterGroup(peoples: List<PencillerPeopleFilter>) :
Filter.Group<PencillerPeopleFilter>("Penciller", peoples)
Filter.Group<PencillerPeopleFilter>("펜슬러", peoples)
class InkerPeopleFilter(name: String) : Filter.CheckBox(name, false)
class InkerPeopleFilterGroup(peoples: List<InkerPeopleFilter>) :
Filter.Group<InkerPeopleFilter>("Inker", peoples)
Filter.Group<InkerPeopleFilter>("잉커", peoples)
class ColoristPeopleFilter(name: String) : Filter.CheckBox(name, false)
class ColoristPeopleFilterGroup(peoples: List<ColoristPeopleFilter>) :
Filter.Group<ColoristPeopleFilter>("Colorist", peoples)
Filter.Group<ColoristPeopleFilter>("컬러리스트", peoples)
class LettererPeopleFilter(name: String) : Filter.CheckBox(name, false)
class LettererPeopleFilterGroup(peoples: List<LettererPeopleFilter>) :
Filter.Group<LettererPeopleFilter>("Letterer", peoples)
Filter.Group<LettererPeopleFilter>("레터러", peoples)
class CoverArtistPeopleFilter(name: String) : Filter.CheckBox(name, false)
class CoverArtistPeopleFilterGroup(peoples: List<CoverArtistPeopleFilter>) :
Filter.Group<CoverArtistPeopleFilter>("Cover Artist", peoples)
Filter.Group<CoverArtistPeopleFilter>("표지 작가", peoples)
class EditorPeopleFilter(name: String) : Filter.CheckBox(name, false)
class EditorPeopleFilterGroup(peoples: List<EditorPeopleFilter>) :
Filter.Group<EditorPeopleFilter>("Editor", peoples)
Filter.Group<EditorPeopleFilter>("편집자", peoples)
class PublisherPeopleFilter(name: String) : Filter.CheckBox(name, false)
class PublisherPeopleFilterGroup(peoples: List<PublisherPeopleFilter>) :
Filter.Group<PublisherPeopleFilter>("Publisher", peoples)
Filter.Group<PublisherPeopleFilter>("출판사", peoples)
class CharacterPeopleFilter(name: String) : Filter.CheckBox(name, false)
class CharacterPeopleFilterGroup(peoples: List<CharacterPeopleFilter>) :
Filter.Group<CharacterPeopleFilter>("Character", peoples)
Filter.Group<CharacterPeopleFilter>("캐릭터", peoples)
class TranslatorPeopleFilter(name: String) : Filter.CheckBox(name, false)
class TranslatorPeopleFilterGroup(peoples: List<TranslatorPeopleFilter>) :
Filter.Group<TranslatorPeopleFilter>("Translator", peoples)
Filter.Group<TranslatorPeopleFilter>("번역자", peoples)

View File

@@ -81,9 +81,11 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.net.URLEncoder
import java.security.MessageDigest
import java.util.Calendar
import java.util.Locale
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlin.let
import kotlin.runCatching
@@ -122,6 +124,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val application: Application by lazy { Injekt.get() }
override val name = "${KavitaInt.KAVITA_NAME} (${preferences.getString(KavitaConstants.customSourceNamePref, suffix)})"
override val lang = "all"
override val supportsLatest = true
@@ -583,12 +586,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
filter.state.forEach { rangeFilter ->
if ((rangeFilter as Filter.Text).state.isNotEmpty()) {
when (rangeFilter.name) {
"Min" -> filterV2.addStatement(
"Min", "최소" -> filterV2.addStatement(
FilterComparison.GreaterThanEqual,
FilterField.ReleaseYear,
rangeFilter.state,
)
"Max" -> filterV2.addStatement(
"Max", "최대" -> filterV2.addStatement(
FilterComparison.LessThanEqual,
FilterField.ReleaseYear,
rangeFilter.state,
@@ -650,12 +653,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
val included = filter.state
.filter { it.state == STATE_INCLUDE }
.mapNotNull { ageFilter ->
ageRatingsListMeta.find { it.title == ageFilter.name }?.value?.toString()
ageRatingsListMeta.find { it.title == ageFilter.name || translateAgeRating(it.title) == ageFilter.name }?.value?.toString()
}
val excluded = filter.state
.filter { it.state == STATE_EXCLUDE }
.mapNotNull { ageFilter ->
ageRatingsListMeta.find { it.title == ageFilter.name }?.value?.toString()
ageRatingsListMeta.find { it.title == ageFilter.name || translateAgeRating(it.title) == ageFilter.name }?.value?.toString()
}
if (included.isNotEmpty()) {
@@ -725,12 +728,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
val included = filter.state
.filter { it.state == STATE_INCLUDE }
.mapNotNull { languageFilter ->
languagesListMeta.find { it.title == languageFilter.name }?.isoCode
languagesListMeta.find { it.title == languageFilter.name || translateLanguage(it.title) == languageFilter.name }?.isoCode
}
val excluded = filter.state
.filter { it.state == STATE_EXCLUDE }
.mapNotNull { languageFilter ->
languagesListMeta.find { it.title == languageFilter.name }?.isoCode
languagesListMeta.find { it.title == languageFilter.name || translateLanguage(it.title) == languageFilter.name }?.isoCode
}
if (included.isNotEmpty()) {
@@ -781,7 +784,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
is PubStatusFilterGroup -> {
filter.state.forEach { pubStatusFilter ->
if ((pubStatusFilter as Filter.CheckBox).state) {
val statusValue = when (pubStatusFilter.name) {
val statusValue = when (normalizePublicationStatus(pubStatusFilter.name)) {
"Ongoing" -> 0
"Hiatus" -> 1
"Completed" -> 2
@@ -1772,6 +1775,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
params += "pages=$pageCount"
}
params += "extractPdf=${shouldExtractPdf(chapter, forceExtractPdf)}"
if (isEpubChapter(chapter)) {
params += "epub=true"
}
if (chapter.fileCount > 1) {
params += "split=${chapter.fileCount}"
}
@@ -1795,6 +1801,16 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
file.format == MangaFormat.Pdf.format || file.extension.equals("pdf", ignoreCase = true)
}
private fun isEpubChapter(chapter: ChapterDto): Boolean =
chapter.files.orEmpty().any { file ->
file.format == MangaFormat.Epub.format || file.extension.equals("epub", ignoreCase = true)
}
private fun isEpubFromChapterUrl(chapter: SChapter): Boolean =
chapter.url.substringAfter("epub=", "")
.substringBefore("&")
.equals("true", ignoreCase = true)
private fun isPdfSeries(seriesId: Int?): Boolean =
seriesId?.let { id ->
series.find { it.id == id }?.format == MangaFormat.Pdf.format ||
@@ -1812,13 +1828,38 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
.equals("true", ignoreCase = true)
private fun readerImageUrl(chapterId: Any, page: Int, extractPdf: Boolean): String =
"$apiUrl/Reader/image?chapterId=$chapterId&page=$page&extractPdf=$extractPdf&apiKey=$apiKey"
"$apiUrl/Reader/image".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("chapterId", chapterId.toString())
.addQueryParameter("page", page.toString())
.addQueryParameter("extractPdf", extractPdf.toString())
.addQueryParameter("saveProgress", "true")
.addQueryParameter("apiKey", apiKey)
.addQueryParameter("kavitaClient", kavitaClientHeader())
.addQueryParameter("deviceId", deviceFingerprint())
.build()
.toString()
private fun readerPage(index: Int, chapterId: Any, page: Int = index, extractPdf: Boolean = false): Page {
val imageUrl = readerImageUrl(chapterId, page, extractPdf)
return Page(index = index, url = imageUrl, imageUrl = imageUrl)
}
private fun readerEpubPage(chapterId: Any, pageCount: Int, title: String? = null): Page {
val params = mutableListOf(
"apiUrl=${apiUrl.urlEncode()}",
"apiKey=${apiKey.urlEncode()}",
"chapterId=${chapterId.toString().urlEncode()}",
"pages=${pageCount.coerceAtLeast(1)}",
)
if (!title.isNullOrBlank()) {
params += "title=${title.urlEncode()}"
}
val url = "kavita-epub://reader?${params.joinToString("&")}"
return Page(index = 0, url = url, imageUrl = null)
}
private fun String.urlEncode(): String = URLEncoder.encode(this, Charsets.UTF_8.name())
override suspend fun getPageList(chapter: SChapter): List<Page> = withContext(Dispatchers.IO) {
// Check if this is a reading list item (has readingListId in URL)
if (chapter.url.contains("readingListId=")) {
@@ -1866,6 +1907,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
}
// Generate pages with consistent URL format
if (isEpubChapter(chapterDetails)) {
return@withContext listOf(readerEpubPage(chapterDetails.id, chapterDetails.pages, chapterDetails.titleName ?: chapterDetails.title))
}
val extractPdf = shouldExtractPdf(chapterDetails, isPdfSeries(seriesId))
return@withContext (0 until chapterDetails.pages).map { i -> readerPage(i, chapterDetails.id, extractPdf = extractPdf) }
} catch (e: Exception) {
@@ -1902,6 +1946,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
.runningFold(0) { acc, chapter -> acc + chapter.pages }
.zip(volume.chapters.sortedBy { it.number.toFloatOrNull() ?: 0f })
.flatMap { (startIndex, chapter) ->
if (isEpubChapter(chapter)) {
return@withContext listOf(readerEpubPage(chapter.id, chapter.pages, chapter.titleName ?: chapter.title))
}
val extractPdf = shouldExtractPdf(chapter, isPdfSeries(volume.seriesId))
(0 until chapter.pages).map { i ->
readerPage(startIndex + i, chapter.id, i, extractPdf)
@@ -1919,6 +1966,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
val extractPdf = extractPdfFromChapterUrl(chapter)
pageCountFromChapterUrl(chapter)?.let { pageCount ->
if (isEpubFromChapterUrl(chapter)) {
return@withContext listOf(readerEpubPage(chapterId, pageCount, chapter.name))
}
return@withContext (0 until pageCount).map { i -> readerPage(i, chapterId, extractPdf = extractPdf) }
}
@@ -1941,6 +1991,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
}
val fetchedExtractPdf = extractPdf || shouldExtractPdf(chapterDetails)
if (isEpubChapter(chapterDetails)) {
return@withContext listOf(readerEpubPage(chapterId, chapterDetails.pages, chapterDetails.titleName ?: chapterDetails.title))
}
return@withContext (0 until chapterDetails.pages).map { i -> readerPage(i, chapterId, extractPdf = fetchedExtractPdf) }
}
} catch (e: Exception) {
@@ -1977,7 +2030,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
}
val mangaList = filteredSeries.map { helper.createSeriesDto(it, apiUrl, apiKey) }
MangasPage(mangaList, false)
MangasPage(mangaList, helper.hasNextPage(response, result.size))
} catch (e: Exception) {
Log.e(LOG_TAG, "Error parsing search results", e)
throw IOException(intl["check_version"], e)
@@ -2025,8 +2078,8 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
// Release year filter
ReleaseYearRangeGroup(
listOf(
ReleaseYearRange("Min"),
ReleaseYearRange("Max"),
ReleaseYearRange("최소"),
ReleaseYearRange("최대"),
),
),
@@ -2034,21 +2087,64 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
TagFilterGroup(tags.map { TagFilter(it) }),
CollectionFilterGroup(collections.map { CollectionFilter(it) }),
LibrariesFilterGroup(libraries.map { LibraryFilter(it) }),
LanguageFilterGroup(languages.map { LanguageFilter(it) }),
PubStatusFilterGroup(pubStatus.map { PubStatusFilter(it) }),
AgeRatingFilterGroup(ageRatings.map { AgeRatingFilter(it) }),
LanguageFilterGroup(languages.map { LanguageFilter(translateLanguage(it)) }),
PubStatusFilterGroup(pubStatus.map { PubStatusFilter(translatePublicationStatus(it)) }),
AgeRatingFilterGroup(ageRatings.map { AgeRatingFilter(translateAgeRating(it)) }),
UserRating(),
UserRatingSeparator(),
// Special filters section
Filter.Header(intl["filters_special"]),
SpecialListFilter(intl["filters_special_list"]),
Filter.Header("특수 필터"),
SpecialListFilter("특수 목록"),
SmartFiltersFilter(smartFilters),
)
}
private fun translatePublicationStatus(value: String): String =
when (value) {
"Ongoing" -> "연재중"
"Hiatus" -> "휴재"
"Completed" -> "완결"
"Cancelled" -> "취소됨"
"Ended" -> "종료됨"
else -> value
}
private fun normalizePublicationStatus(value: String): String =
when (value) {
"연재중" -> "Ongoing"
"휴재" -> "Hiatus"
"완결" -> "Completed"
"취소됨" -> "Cancelled"
"종료됨" -> "Ended"
else -> value
}
private fun translateAgeRating(value: String): String =
when (value) {
"Unknown" -> "알 수 없음"
"Everyone" -> "전체 이용가"
"Teen" -> "청소년"
"Mature" -> "성인"
"Explicit" -> "노골적"
else -> value
}
private fun translateLanguage(value: String): String =
when (value) {
"Unknown" -> "알 수 없음"
"English" -> "영어"
"Korean" -> "한국어"
"Japanese" -> "일본어"
"Chinese" -> "중국어"
"Spanish" -> "스페인어"
"French" -> "프랑스어"
"German" -> "독일어"
else -> value
}
override fun getFilterList(): FilterList {
return getFilterList(
genres = genresListMeta.map { it.title },
@@ -2086,6 +2182,8 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
}
return Headers.Builder()
.add("User-Agent", "Tachiyomi Kavita v${AppInfo.getVersionName()}")
.add("X-Kavita-Client", kavitaClientHeader())
.add("X-Device-Id", deviceFingerprint())
.add("Content-Type", "application/json")
.add("Authorization", "Bearer $jwtToken")
}
@@ -2093,10 +2191,34 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
private fun setupLoginHeaders(): Headers.Builder {
return Headers.Builder()
.add("User-Agent", "Tachiyomi Kavita v${AppInfo.getVersionName()}")
.add("X-Kavita-Client", kavitaClientHeader())
.add("X-Device-Id", deviceFingerprint())
.add("Content-Type", "application/json")
.add("Authorization", "Bearer $jwtToken")
}
private fun kavitaClientHeader(): String {
val resources = application.resources
val configuration = resources.configuration
val smallestWidthDp = configuration.smallestScreenWidthDp
val deviceType = when {
smallestWidthDp >= 600 -> "Tablet"
else -> "Mobile"
}
return "opds-client/${AppInfo.getVersionName()} (Kavita Extension; Android; $deviceType)"
}
private fun deviceFingerprint(): String {
val key = "kavita_extension_device_id"
val existing = preferences.getString(key, null)
if (!existing.isNullOrBlank()) return existing
val generated = UUID.randomUUID().toString()
preferences.edit().putString(key, generated).apply()
return generated
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val opdsAddressPref = screen.editTextPreference(
ADDRESS_TITLE,

View File

@@ -9,7 +9,7 @@ object KavitaConstants {
const val SPECIAL_NUMBER_OFFSET = 10000000000f
const val customSourceNamePref = "customSourceName"
const val noSmartFilterSelected = "No smart filter loaded"
const val noSmartFilterSelected = "스마트 필터 없음"
// Template formatting preferences
const val CHAPTER_TITLE_FORMAT_DEFAULT = "\$CleanTitle"

View File

@@ -45,14 +45,14 @@ class KavitaHelper {
val seriesMap = SeriesMapper()
fun hasNextPage(response: Response): Boolean {
fun hasNextPage(response: Response, itemCount: Int? = null, pageSize: Int = 20): Boolean {
val paginationHeader = response.header("Pagination")
var hasNextPage = false
if (!paginationHeader.isNullOrEmpty()) {
val paginationInfo = json.decodeFromString<PaginationInfo>(paginationHeader)
hasNextPage = paginationInfo.currentPage < paginationInfo.totalPages
return paginationInfo.currentPage < paginationInfo.totalPages
}
return hasNextPage
return itemCount != null && itemCount >= pageSize
}
fun getIdFromUrl(url: String): Int {