- Option to blacklist libraries from Latest/Popular Feeds & Suggestions
- Option to customize Chapter Titles & Scanlator fields
- Option to show the release date instead of added date for chapters/volumes (close #37)
- Added `formats` for GroupTags
- Action to create releases

## Fixes/Enhancements
- Pagination bug (closes #33, thanks to @Freezy)
- Optimized API calls
- Better cache and cleanup
- Better cleaned chapter/volumes titles
- Fixed Chapter Parsing Logic
- Refactored duplicated codes into multiple helpers
- Fixed action to update version badge
- More feedbacks on errors & graceful degradation for partial failures
- Include Suwayomi install & screenshots in README (closes #41)
This commit is contained in:
Mio.
2025-12-10 20:02:30 +01:00
parent 8079d707e9
commit f9632de15b
16 changed files with 1815 additions and 627 deletions

BIN
.github/screenshots/Komikku_1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
.github/screenshots/Komikku_2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
.github/screenshots/Mihon_1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 KiB

BIN
.github/screenshots/Mihon_2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -21,6 +21,7 @@ for module in to_delete:
shutil.copytree(src=LOCAL_REPO.joinpath("apk"), dst=REMOTE_REPO.joinpath("apk"), dirs_exist_ok = True)
shutil.copytree(src=LOCAL_REPO.joinpath("icon"), dst=REMOTE_REPO.joinpath("icon"), dirs_exist_ok = True)
shutil.copy2(src=LOCAL_REPO.joinpath("badge-version.json"), dst=REMOTE_REPO.joinpath("badge-version.json"))
with REMOTE_REPO.joinpath("index.min.json").open() as remote_index_file:
remote_index = json.load(remote_index_file)

148
.github/workflows/create_release.yml vendored Normal file
View File

@@ -0,0 +1,148 @@
name: Create Release on Merge
on:
push:
branches:
- master
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout repo branch
uses: actions/checkout@v4
with:
ref: repo
fetch-depth: 0
# Get the merged PR info (only if not triggered manually)
- name: Get PR information
id: pr_info
if: github.event_name == 'push'
run: |
# Check out the master branch to get the PR info
git fetch origin master:master
git checkout master
# Find the merge commit that triggered this workflow
MERGE_COMMIT=$(git rev-parse HEAD)
# Check if this is a merge commit
if git show --summary $MERGE_COMMIT | grep -q "Merge pull request"; then
# Extract PR number from merge commit message
PR_NUMBER=$(git log -1 --pretty=%B | grep -oP 'Merge pull request #\K\d+')
if [ -n "$PR_NUMBER" ]; then
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
# Get PR details
PR_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER)
PR_TITLE=$(echo "$PR_JSON" | jq -r .title)
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
PR_AUTHOR=$(echo "$PR_JSON" | jq -r .user.login)
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
# Check if first-time contributor
CONTRIBUTOR_JSON=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/contributors/$PR_AUTHOR)
CONTRIBUTIONS=$(echo "$CONTRIBUTOR_JSON" | jq -r .contributions)
if [ "$CONTRIBUTIONS" = "1" ]; then
echo "is_first_contributor=true" >> $GITHUB_OUTPUT
fi
# Get PR body
PR_BODY=$(echo "$PR_JSON" | jq -r .body)
echo "pr_body=$PR_BODY" >> $GITHUB_OUTPUT
fi
fi
# Find the Kavita APK and extract version
- name: Find APK and extract version
id: apk_info
run: |
# Find the Kavita APK in the apk folder
APK_PATH=$(find ./apk -name "tachiyomi-all.kavita-*.apk" -type f | head -n 1)
if [ -z "$APK_PATH" ]; then
echo "No Kavita APK found in ./apk folder!"
exit 1
fi
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
# Extract version from filename (tachiyomi-all.kavita-v1.4.21.apk -> v1.4.21)
APK_FILENAME=$(basename "$APK_PATH")
VERSION=$(echo "$APK_FILENAME" | grep -oP 'v\d+\.\d+\.\d+')
if [ -z "$VERSION" ]; then
echo "Could not extract version from filename: $APK_FILENAME"
VERSION="v$(date +%Y%m%d-%H%M%S)"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "apk_name=$APK_FILENAME" >> $GITHUB_OUTPUT
echo "Found APK: $APK_PATH"
echo "Extracted version: $VERSION"
# Get commit messages since last tag
- name: Get changelog
id: changelog
run: |
# Switch to master branch to get git history
git checkout master
# Get the last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log $LAST_TAG..HEAD --pretty=format:"- %s (%h)")
else
COMMITS=$(git log --pretty=format:"- %s (%h)" -n 20)
fi
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$COMMITS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Create the release (on the repo branch)
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: Release ${{ steps.apk_info.outputs.version }}
tag_name: ${{ steps.apk_info.outputs.version }}
target_commitish: repo
body: |
## 🚀 Release ${{ steps.apk_info.outputs.version }}
### 📝 Pull Request
${{ steps.pr_info.outputs.pr_number && format('- Pull Request #%s by @%s', steps.pr_info.outputs.pr_number, steps.pr_info.outputs.pr_author) || 'Manual release' }}
${{ steps.pr_info.outputs.pr_title && format('- Title: %s', steps.pr_info.outputs.pr_title) || '' }}
${{ steps.pr_info.outputs.is_first_contributor == 'true' && format('### 🎉 Welcome @%s on their first contribution!', steps.pr_info.outputs.pr_author) || '' }}
${{ steps.pr_info.outputs.pr_body && format('### 📋 Description:\n%s', steps.pr_info.outputs.pr_body) || '' }}
### 📦 Changes since last release:
${{ steps.changelog.outputs.changelog }}
---
🤖 *This release was automatically created*
files: ${{ steps.apk_info.outputs.apk_path }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify on failure
if: failure()
run: |
echo "Release creation failed. Check the logs for details."

View File

@@ -14,7 +14,7 @@
This repository contains the Kavita extension, compatible with [Komikku](https://github.com/komikku-app/komikku), [Mihon](https://github.com/mihonapp/mihon), Tachiyomi, and other forks.
## Recommended Apps
## Recommended & Tested
### - [Mihon](https://github.com/mihonapp/mihon)
@@ -26,11 +26,17 @@ This repository contains the Kavita extension, compatible with [Komikku](https:/
<img alt="Install Kavita Repo" src="https://img.shields.io/badge/Click%20to%20Add%20Kavita%20Repo-49CC90?style=for-the-badge&labelColor=555555&color=49CC90&logo=android&logoColor=white">
</a>
Or copy & paste this URL into your app:
Or copy & paste this URL into your instance (Suwayomi included):
```
https://raw.githubusercontent.com/Kareadita/tach-extensions/repo/index.min.json
```
# Screenshots
<img src="https://files.catbox.moe/u41pb0.png" width="22%" alt="Komikku1">
<img src="https://files.catbox.moe/ixxs8u.png" width="22%" alt="Mihon1">
<img src="https://files.catbox.moe/j4cnlx.png" width="22%" alt="Komikku2">
<img src="https://files.catbox.moe/nbetf9.png" width="22%" alt="Mihon2">
# Requests

View File

@@ -1,30 +1,30 @@
[versions]
kotlin_version = "1.7.21"
coroutines_version = "1.6.4"
serialization_version = "1.4.0"
coreVersion = "1.16.0"
commonsText = "1.14.0"
kotlin = "1.7.21"
coroutines = "1.6.4"
serialization = "1.4.0"
[libraries]
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.10.1" }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" }
gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" }
commons-text = { module = "org.apache.commons:commons-text", version.ref = "commonsText" }
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.13.0" }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.13.0" }
tachiyomi-lib = { module = "com.github.komikku-app:extensions-lib", version = "1.7.1" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin_version" }
kotlin-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" }
kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization" }
kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "4135455a2a" }
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
jsoup = { module = "org.jsoup:jsoup", version = "1.15.3" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" }
core = { group = "androidx.core", name = "core", version.ref = "coreVersion" }
[bundles]
common = ["kotlin-stdlib", "coroutines-core", "coroutines-android", "injekt-core", "rxjava", "kotlin-protobuf", "kotlin-json", "jsoup", "okhttp", "tachiyomi-lib", "quickjs"]

View File

@@ -1,41 +0,0 @@
# Komga
Table of Content
- [FAQ](#FAQ)
- [Why do I see no manga?](#why-do-i-see-no-manga)
- [Where can I get more information about Komga?](#where-can-i-get-more-information-about-komga)
- [The Komga extension stopped working?](#the-komga-extension-stopped-working)
- [Can I add more than one Komga server or user?](#can-i-add-more-than-one-komga-server-or-user)
- [Can I test the Komga extension before setting up my own server?](#can-i-test-the-komga-extension-before-setting-up-my-own-server)
- [Guides](#Guides)
- [How do I add my Komga server to the app?](#how-do-i-add-my-komga-server-to-the-app)
## FAQ
### Why do I see no manga?
Komga is a self-hosted comic/manga media server.
### Where can I get more information about Komga?
You can visit the [Komga](https://komga.org/) website for for more information.
### The Komga extension stopped working?
Make sure that your Komga server and extension are on the newest version.
### Can I add more than one Komga server or user?
Yes, currently you can add up to 11 different Komga instances to the app. The number of instances
available can be customized in extension settings.
### Can I test the Komga extension before setting up my own server?
Yes, you can try it out with the DEMO server `https://demo.komga.org`, username `demo@komga.org` and
password `komga-demo`.
### Why are EPUB chapters/books missing?
Mihon/Tachiyomi does not support reading text. EPUB files containing only images will be available
if your Komga server version is [1.9.0](https://github.com/gotson/komga/releases/tag/1.9.0)
or newer.
## Guides
### How do I add my Komga server to the app?
Go into the settings of the Komga extension (under Browse -> Extensions) and fill in your server
address and login details.

View File

@@ -1,12 +1,10 @@
# ================ #
# Authentication #
# ================ #
login_errors_failed_login=Authentication failed.\nPlease check your credentials
login_errors_header_token_empty=Authorization token missing\nPlease open the extension first to generate a token
login_errors_invalid_url=Invalid server URL:
login_errors_parse_tokendto=Failed to process authentication token
http_errors_401=Authentication expired\nPlease log in again
http_errors_500=Server error occurred
login_errors_failed_login=Authentication failed.\nPlease check your credentials and server URL
login_errors_header_token_empty=Authentication required\nPlease open this extension once to generate a login token
login_errors_invalid_url=Invalid server URL format:
login_errors_parse_tokendto=Server response invalid\nPlease check your Kavita version (requires v0.8.7+)
# ============== #
# Preferences #
@@ -15,42 +13,58 @@ pref_customsource_title=Source Display Name
pref_edit_customsource_summary=Customize how this Kavita instance appears in your library\nExample: "Home Server" or "Cloud Instance"
pref_group_tags_title=Tag Grouping (fork must support grouping)
pref_group_tags_summary_on=Show tags with category prefixes
pref_group_tags_summary_off=Display tags as flat list
pref_group_tags_summary_on=Show tags with category prefixes (e.g., "Genre: Action")
pref_group_tags_summary_off=Display all tags in a flat list
pref_last_volume_cover_title=Keep Cover Updated
pref_last_volume_cover_summary_off=Keep series cover
pref_last_volume_cover_summary_on=Always show current Volume/Issue cover on refresh
pref_last_volume_cover_title=Dynamic Cover Updates
pref_last_volume_cover_summary_off=Always show the main series cover
pref_last_volume_cover_summary_on=Update cover on refresh to show current volume/issue when reading
pref_show_epub_title=EPUB Visibility
pref_show_epub_summary_off=Hide EPUB entries
pref_show_epub_summary_on=Show EPUB entries (unreadable in Mihon)
pref_show_epub_summary_off=Hide EPUB files
pref_show_epub_summary_on=Show EPUB entries (not supported by Mihon)
pref_release_date_title=Chapters: Display Release Dates
pref_release_date_summary_off=Show when chapters were added to Kavita
pref_release_date_summary_on=Show original publication dates\n?? May affect chapter sorting if dates are missing
pref_rd_date_title=Reading List: Display Release Date
pref_rd_date_summary_off=Hide publication dates
pref_rd_date_summary_off=Hide publication dates from reading lists
pref_rd_date_summary_on=Show publication dates\nNote: Once you toggle it on, if you toggle it off again, it will stays as N/A.
pref_reset_title=Reset Settings
pref_reset_title=Reset All Settings
pref_reset_summary=Clear all preferences for this Kavita instance
pref_filters_title=Default Filters
pref_filters_summary=Select which filters appear by default
pref_allowed_libraries_feed_title=Allowed Libraries on Latest/Popular Feeds & Suggestions
pref_allowed_libraries_feed_summary=Select which libraries appear in the Latest/Popular Feeds & Suggestions (Komikku only)
pref_allowed_libraries_search_title=Allowed Libraries in Search
pref_allowed_libraries_search_summary=Select which libraries appear in search results
pref_chapter_title_format_title=Chapter Title Format
pref_chapter_title_format_summary=Customize chapter titles using variables\nCurrent: %s
pref_chapter_title_format_dialog=Available variables: (Make sure to remove the {} around like the default)\n\n\n\${Type} - Chapter/Episode/Volume/Issue/Special\n\${No} - Chapter number\n\${Title} - Original chapter title\n\${CleanTitle} - Custom-cleaned chapter title (default)\n\${Pages} - Number of pages\n\${Size} - File size\n\${Volume} - Volume number (If available)\n\${SeriesName} - Series name\n\${LibraryName} - Library name\n\${Format} - File format (CBZ, PDF, etc.)\n\${Created} - Creation date\n\${ReleaseDate} - Release date
pref_scanlator_format_title=Scanlator Format
pref_scanlator_format_summary=Customize scanlator field using variables\nCurrent: %s
pref_scanlator_format_dialog=Available variables: (? remove the {} around like the default)\n\n\n\${Type} - Chapter/Episode/Volume/Issue/Special (default)\n\${No} - Chapter number\n\${Title} - Original chapter title\n\${CleanTitle} - Custom-cleaned chapter title\n\${Pages} - Number of pages\n\${Size} - File size\n\${Volume} - Volume number (If available)\n\${SeriesName} - Series name\n\${LibraryName} - Library name\n\${Format} - File format (CBZ, PDF, etc.)\n\${Created} - Creation date\n\${ReleaseDate} - Release date
# ============== #
# OPDS Setup #
# ============== #
pref_opds_must_setup_address=OPDS URL required for connection
pref_opds_badformed_url=Invalid OPDS address\nCopy from: User Settings > API Key / OPDS > OPDS URL (settings#clients)
pref_opds_duplicated_source_url=URL already used in another source:
pref_opds_must_setup_address=OPDS URL required\nPlease configure your Kavita server connection
pref_opds_badformed_url=nvalid OPDS address format\nGet this from: User Settings ? API Key/OPDS ? OPDS URL (settings#clients)
pref_opds_duplicated_source_url=URL already used in another source:
pref_opds_summary=Complete OPDS URL including API key\nFormat: http://address:port/opds?apiKey=XXX
# ============== #
# System Messages #
# ============== #
restartapp_settings=Restart Mihon to apply changes
check_version=Requires Kavita v0.8.7+\nIf issues persist, contact support with logs
version_exceptions_chapters_parse=Chapter parsing error\nPlease report with logs
version_exceptions_smart_filter=Smart Filter error\nRequires Kavita v0.7.11+
check_version=Requires Kavita v0.8.7 or newer\nIf issues persist, open an issue on GitHub
version_exceptions_chapters_parse=Failed to parse chapter information\nPlease report this with error logs on Github
version_exceptions_smart_filter=Smart filter not supported\nRequires Kavita v0.7.11 or newer
# ============== #
@@ -102,6 +116,17 @@ filters_sort_release_date=Release Date
filters_sort_read_progress=Read Progress
# Error messages
http_errors_401=Authentication expired\nPlease log in again
http_errors_500=Server error occurred
http_errors_400=Bad request - Invalid parameters
http_errors_403=Access forbidden - Insufficient permissions
http_errors_404=Resource not found - The requested item doesn't exist
http_errors_429=Too many requests - Please try again later
http_errors_502=Bad gateway - Server communication error
http_errors_503=Service unavailable - Server is temporarily down
filters_error_loading=Failed to load filters
filters_empty_list=No filters available
error_chapter_not_found=Chapter not found in reading list
error_no_chapters_found=No chapters found
error_invalid_reading_list_item=Invalid reading list item
error_failed_parse_chapter=Failed to parse chapter details

View File

@@ -2,13 +2,16 @@ ext {
kmkVersionCode = 1
extName = 'Kavita'
extClass = '.KavitaFactory'
extVersionCode = 20
extVersionCode = 21
isNsfw = false
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation("org.apache.commons:commons-text:1.11.0")
// implementation("org.apache.commons:commons-text:1.14.0")
// implementation(libs.lib.i18n)
implementation project(':lib:i18n')
implementation(libs.commons.text)
}

View File

@@ -6,67 +6,16 @@ object KavitaConstants {
const val UNNUMBERED_VOLUME_STR = "-100000"
const val SPECIAL_NUMBER = 100_000
const val VOLUME_NUMBER_OFFSET = 10000f
val PERSON_ROLES = listOf(
"Writer", "Penciller", "Inker", "Colorist",
"Letterer", "CoverArtist", "Editor",
"Publisher", "Character", "Translator",
)
// toggle filters
const val toggledFiltersPref = "toggledFilters"
val filterPrefEntries = arrayOf(
"Sort Options",
"Special Lists",
"Format",
"Libraries",
"Read Status",
"Genres",
"Tags",
"Collections",
"Languages",
"Publication Status",
"Rating",
"Age Rating",
"ReleaseYearRange",
"Random",
"Read Progress",
)
val filterPrefEntriesValue = arrayOf(
"Sort Options",
"Special Lists",
"Format",
"Libraries",
"Read Status",
"Genres",
"Tags",
"Collections",
"Languages",
"Publication Status",
"Rating",
"Age Rating",
"ReleaseYearRange",
"Random",
"Read Progress",
)
val defaultFilterPrefEntries = setOf(
"Sort Options",
"Special Lists",
"Format",
"Libraries",
"Read Status",
"Genres",
"Tags",
"Collections",
"Languages",
"Publication Status",
"Rating",
"Age Rating",
"ReleaseYearRange",
"Random",
"Read Progress",
)
const val SPECIAL_NUMBER_OFFSET = 10000000000f
const val customSourceNamePref = "customSourceName"
const val noSmartFilterSelected = "No smart filter loaded"
// Template formatting preferences
const val CHAPTER_TITLE_FORMAT_DEFAULT = "\$CleanTitle"
const val SCANLATOR_FORMAT_DEFAULT = "\$Type"
// Library filtering preferences
const val allowedLibrariesFeedPref = "allowedLibrariesFeed"
const val allowedLibrariesSearchPref = "allowedLibrariesSearch"
}

View File

@@ -111,6 +111,7 @@ enum class FilterField(val type: Int) {
Team(30),
Location(31),
ReadLast(32),
People(33),
;
companion object {

View File

@@ -88,11 +88,6 @@ data class SeriesDetailPlusDto(
seriesDto?.libraryName
}
}
// Helper function to get the library ID from SeriesDto if needed
fun getLibraryId(seriesDto: SeriesDto?): Int? {
return libraryId ?: seriesDto?.libraryId
}
}
@Serializable
@@ -253,6 +248,7 @@ data class ChapterDto(
val volumeId: Int,
val created: String,
val lastModifiedUtc: String,
val releaseDate: String,
val files: List<FileDto>? = null,
) {
val fileCount: Int
@@ -262,6 +258,12 @@ data class ChapterDto(
@Serializable
data class FileDto(
val id: Int,
val bytes: Long = 0,
val pages: Int = 0,
val filePath: String = "",
val format: Int = 0,
val created: String = "",
val extension: String = "",
)
@Serializable