Compare commits

..

6 Commits

Author SHA1 Message Date
Mitchell Syer
c79486b8be Manual Extension Fixes (#2139)
* Fix manual extension icons

* Delete extension where APK Url is null
2026-06-28 02:08:53 -04:00
Mitchell Syer
e2fd15158c Fix Backups (#2138) 2026-06-27 14:09:48 -04:00
Bartu Özen
b6de3c3e39 Use stable manga and chapter composite keys for sync matching (#2124) 2026-06-27 13:41:14 -04:00
Weblate (bot)
656d86c6f6 Weblate translations (#2130)
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/de/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/el/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/es/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/fr/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/it/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ja/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/pl/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/pt/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ru/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ta/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/vi/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/zh_Hans/
Translation: Suwayomi/Suwayomi-Server

Co-authored-by: Constantin Piber <cp.piber@gmail.com>
Co-authored-by: Damien O'Neil <maxiburning@gmail.com>
Co-authored-by: zeedif <carlos_antonio-rl@hotmail.com>
2026-06-27 13:40:44 -04:00
schroda
a0fbff5756 Update issue templates (#2137) 2026-06-27 13:39:43 -04:00
Mitchell Syer
2d535b44d8 Extension API 1.6 (#2120)
* Non-Extension Index changes for 1.6

* Changelog

* Minor fixes

* Implement extension store

* Test build fix

* Docs

* Simplify fetching manga and chapters

* Use EMPTY JsonObject

* Update docs/Configuring-Suwayomi‐Server.md

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>

* Improve Fetch Extension Store

* Fixes

* Simplify deprecated isNsfw in SourceQuery

* Simplify ContentRating in Source.kt

* Simplify isNsfw in SourceType

* No magic numbers for ContentRating, improves safety for future versions of extension api

* Fix SearchTest

* Lint

* Lint

* Optimize imports and fix unchecked cast warning

* Proper extension store queries

* Optimize import fixes

* Add ContentRatingFilter

* Improve extension store sync

* fix: re-sync (#2121)

* Lint

* Add ExtenionStores to the fetchExtensions result since its possible for the stores to change.

* Use a single version of ContentRating

* Exclude ServerConfig.extensionStores from GraphQL

* Use syncDbToPrefs in ExtensionStoreMutation

* Optimize Imports

* Update server/server-config/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>

* Remove replaceWith and add specific description for GQL APIs

* Include OkHttp ZSTD

* Update to latest Mihon extension lib

* Fix latest Mihon Extension Lib

* Lint

* Optimize imports

* Lint

* Review fixes

* Add a index to extesnion table store url

* Lint

---------

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>
2026-06-27 13:39:28 -04:00
18 changed files with 46 additions and 15 deletions

View File

@@ -143,11 +143,13 @@ body:
options: options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true required: true
- label: I have checked the ongoing preview changelog of **[Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md)** and **[Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/blob/master/CHANGELOG.md)** and this bug has **NOT** been listed as fixed
required: true
- label: I have written a short but informative title (ideally less than ~100 characters). - label: I have written a short but informative title (ideally less than ~100 characters).
required: true required: true
- label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support) - label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support)
required: true required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**. - label: I have updated the (**[Suwayomi-WebUI](https://github.com/suwayomi/suwayomi-webui/releases/latest)** and **[Suwayomi-Server](https://github.com/suwayomi/suwayomi-server/releases/latest)**) to the latest versions
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have filled out all of the requested information in this form, including specific version numbers.
required: true required: true

View File

@@ -31,7 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title (ideally less than ~100 characters). - label: I have written a short but informative title (ideally less than ~100 characters).
required: true required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**. - label: I have updated the (**[Suwayomi-WebUI](https://github.com/suwayomi/suwayomi-webui/releases/latest)** and **[Suwayomi-Server](https://github.com/suwayomi/suwayomi-server/releases/latest)**) to the latest versions
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have filled out all of the requested information in this form, including specific version numbers.
required: true required: true

View File

@@ -8,7 +8,7 @@
<string name="opds_feeds_root">Suwayomi OPDS Katalog</string> <string name="opds_feeds_root">Suwayomi OPDS Katalog</string>
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string> <string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
<string name="opds_feeds_sources_title">Alle Quellen</string> <string name="opds_feeds_sources_title">Alle Quellen</string>
<string name="opds_feeds_genres_title">Genres</string> <string name="opds_feeds_genres_title">Genren</string>
<string name="opds_feeds_genres_entry_content">Durchsuche Serien nach Genre</string> <string name="opds_feeds_genres_entry_content">Durchsuche Serien nach Genre</string>
<string name="opds_feeds_status_entry_content">Durchsuche Serien nach Publikationsstatus</string> <string name="opds_feeds_status_entry_content">Durchsuche Serien nach Publikationsstatus</string>
<string name="opds_feeds_languages_title">Sprachen</string> <string name="opds_feeds_languages_title">Sprachen</string>
@@ -122,4 +122,7 @@
<string name="webview_label_login_required">Deine Konfiguration erfordert die Anmeldung. Bitte gib Benutzername und Passwort ein.</string> <string name="webview_label_login_required">Deine Konfiguration erfordert die Anmeldung. Bitte gib Benutzername und Passwort ein.</string>
<string name="opds_linktitle_first_page">Erste Seite</string> <string name="opds_linktitle_first_page">Erste Seite</string>
<string name="opds_linktitle_last_page">Letzte Seite</string> <string name="opds_linktitle_last_page">Letzte Seite</string>
<string name="opds_error_chapters_not_found">Keine Kapitel gefunden oder die Quelle ist nicht erreichbar auf Seite %1$d.</string>
<string name="opds_chapter_title_fallback">Kapitel %1$s</string>
<string name="opds_chapter_title_oneshot">Oneshot</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Σύνδεση</string> <string name="login_label_login">Σύνδεση</string>
<string name="login_placeholder_username">Πληκτρολόγησε όνομα χρήστη...</string> <string name="login_placeholder_username">Πληκτρολόγησε όνομα χρήστη...</string>
<string name="login_placeholder_password">Μυστικό...</string> <string name="login_placeholder_password">Μυστικό...</string>
<string name="opds_error_chapters_not_found">Δεν βρέθηκαν κεφάλαια ή η πηγή είναι μη διαθέσιμη στη σελίδα %1$d.</string>
<string name="opds_chapter_title_fallback">Κεφάλαιο %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="webview_label_login_required">Su configuración requiere que inicie sesión. Introduzca su nombre de usuario y contraseña.</string> <string name="webview_label_login_required">Su configuración requiere que inicie sesión. Introduzca su nombre de usuario y contraseña.</string>
<string name="opds_linktitle_first_page">Primera página</string> <string name="opds_linktitle_first_page">Primera página</string>
<string name="opds_linktitle_last_page">Última página</string> <string name="opds_linktitle_last_page">Última página</string>
<string name="opds_error_chapters_not_found">No se encontraron capítulos o la fuente no está disponible en la página %1$d.</string>
<string name="opds_chapter_title_fallback">Capítulo %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="login_label_login">Se connecter</string> <string name="login_label_login">Se connecter</string>
<string name="login_placeholder_username">Tapez le nom d\'utilisateur…</string> <string name="login_placeholder_username">Tapez le nom d\'utilisateur…</string>
<string name="login_placeholder_password">Secret…</string> <string name="login_placeholder_password">Secret…</string>
<string name="opds_error_chapters_not_found">Aucun chapitre trouvé ou la source est inaccessible à la page %1$d.</string>
<string name="opds_chapter_title_fallback">Chapitre %1$s</string>
<string name="opds_chapter_title_oneshot">One shot</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Accedi</string> <string name="login_label_login">Accedi</string>
<string name="login_placeholder_username">Digita il nome utente...</string> <string name="login_placeholder_username">Digita il nome utente...</string>
<string name="login_placeholder_password">Segreto...</string> <string name="login_placeholder_password">Segreto...</string>
<string name="opds_error_chapters_not_found">Nessun capitolo trovato o la fonte non è raggiungibile alla pagina %1$d.</string>
<string name="opds_chapter_title_fallback">Capitolo %1$s</string>
</resources> </resources>

View File

@@ -60,4 +60,7 @@
<string name="opds_feeds_library_sources_title">ソース</string> <string name="opds_feeds_library_sources_title">ソース</string>
<string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string> <string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string>
<string name="opds_feeds_search_results_title">検索結果</string> <string name="opds_feeds_search_results_title">検索結果</string>
<string name="opds_error_chapters_not_found">ページ %1$d で章が見つからないか、ソースに接続できません。</string>
<string name="opds_chapter_title_oneshot">読み切り</string>
<string name="opds_chapter_title_fallback">第 %1$s 話</string>
</resources> </resources>

View File

@@ -76,4 +76,6 @@
<string name="opds_facet_filter_all">Wszystkie</string> <string name="opds_facet_filter_all">Wszystkie</string>
<string name="opds_facet_filter_downloaded">Pobrane</string> <string name="opds_facet_filter_downloaded">Pobrane</string>
<string name="opds_facet_filter_ongoing">Trwające</string> <string name="opds_facet_filter_ongoing">Trwające</string>
<string name="opds_error_chapters_not_found">Nie znaleziono rozdziałów lub źródło jest nieosiągalne na stronie %1$d.</string>
<string name="opds_chapter_title_fallback">Rozdział %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Entrar</string> <string name="login_label_login">Entrar</string>
<string name="login_placeholder_username">Digite o nome de usuário...</string> <string name="login_placeholder_username">Digite o nome de usuário...</string>
<string name="login_placeholder_password">Segredo...</string> <string name="login_placeholder_password">Segredo...</string>
<string name="opds_error_chapters_not_found">Nenhum capítulo encontrado ou a fonte está inacessível na página %1$d.</string>
<string name="opds_chapter_title_fallback">Capítulo %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="opds_search_description">Ищите тайтлы в каталоге.</string> <string name="opds_search_description">Ищите тайтлы в каталоге.</string>
<string name="opds_error_manga_not_found">Тайтл с ID %1$d не найден.</string> <string name="opds_error_manga_not_found">Тайтл с ID %1$d не найден.</string>
<string name="opds_chapter_details_base">Тайтл: %1$s | %2$s</string> <string name="opds_chapter_details_base">Тайтл: %1$s | %2$s</string>
<string name="opds_error_chapters_not_found">Главы не найдены или источник недоступен на странице %1$d.</string>
<string name="opds_chapter_title_fallback">Глава %1$s</string>
<string name="opds_chapter_title_oneshot">Ваншот</string>
</resources> </resources>

View File

@@ -53,4 +53,7 @@
<string name="opds_chapter_status_unread"></string> <string name="opds_chapter_status_unread"></string>
<string name="opds_chapter_details_base">%1$s | %2$s</string> <string name="opds_chapter_details_base">%1$s | %2$s</string>
<string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string> <string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string>
<string name="opds_error_chapters_not_found">பக்கம் %1$d இல் அத்தியாயங்கள் எதுவும் காணப்படவில்லை அல்லது மூலத்தை அணுக முடியவில்லை.</string>
<string name="opds_chapter_title_oneshot">ஒன்-ஷாட்</string>
<string name="opds_chapter_title_fallback">அத்தியாயம் %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="webview_label_login_required">Cấu hình của bạn yêu cầu bạn phải đăng nhập. Vui lòng nhập tên người dùng và mật khẩu.</string> <string name="webview_label_login_required">Cấu hình của bạn yêu cầu bạn phải đăng nhập. Vui lòng nhập tên người dùng và mật khẩu.</string>
<string name="opds_linktitle_first_page">Trang đầu</string> <string name="opds_linktitle_first_page">Trang đầu</string>
<string name="opds_linktitle_last_page">Trang cuối</string> <string name="opds_linktitle_last_page">Trang cuối</string>
<string name="opds_error_chapters_not_found">Không tìm thấy chương nào hoặc nguồn không thể truy cập tại trang %1$d.</string>
<string name="opds_chapter_title_fallback">Chương %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="login_placeholder_username">输入用户名…</string> <string name="login_placeholder_username">输入用户名…</string>
<string name="login_placeholder_password">密匙…</string> <string name="login_placeholder_password">密匙…</string>
<string name="label_error">错误</string> <string name="label_error">错误</string>
<string name="opds_error_chapters_not_found">第 %1$d 页未找到任何章节,或图源无法访问。</string>
<string name="opds_chapter_title_fallback">第 %1$s 章</string>
<string name="opds_chapter_title_oneshot">单篇</string>
</resources> </resources>

View File

@@ -305,8 +305,7 @@ object SyncYomiSyncService {
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" } logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
fun mangaCompositeKey(manga: BackupManga): String = fun mangaCompositeKey(manga: BackupManga): String = "${manga.source}|${manga.url}"
"${manga.source}|${manga.url}|${manga.title.lowercase().trim()}|${manga.author?.lowercase()?.trim()}"
// Create maps using composite keys // Create maps using composite keys
val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) } val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) }
@@ -415,7 +414,7 @@ object SyncYomiSyncService {
return remoteChapters // If not syncing chapters, keep remote untouched return remoteChapters // If not syncing chapters, keep remote untouched
} }
fun chapterCompositeKey(chapter: BackupChapter): String = "${chapter.url}|${chapter.name}|${chapter.chapterNumber}" fun chapterCompositeKey(chapter: BackupChapter): String = chapter.url
val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) } val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) }
val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) } val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) }

View File

@@ -39,7 +39,7 @@ data class BackupManga(
@ProtoNumber(106) var lastModifiedAt: Long = 0, @ProtoNumber(106) var lastModifiedAt: Long = 0,
@ProtoNumber(109) var version: Long = 0, @ProtoNumber(109) var version: Long = 0,
@ProtoNumber(111) var initialized: Boolean = false, @ProtoNumber(111) var initialized: Boolean = false,
@ProtoNumber(13) var memo: ByteArray = JsonObjectEmptyBytes, @ProtoNumber(112) var memo: ByteArray = JsonObjectEmptyBytes,
// suwayomi // suwayomi
@ProtoNumber(9000) var meta: Map<String, String> = emptyMap(), @ProtoNumber(9000) var meta: Map<String, String> = emptyMap(),
) )

View File

@@ -165,7 +165,7 @@ object Extension {
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType) dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
extractAssetsFromApk(apkFilePath, jarFilePath) extractAssetsFromApk(apkFilePath, jarFilePath)
extractAndCacheApkIcon(apkFilePath, apkName) extractAndCacheApkIcon(apkFilePath, packageInfo.packageName)
// clean up // clean up
File(apkFilePath).delete() File(apkFilePath).delete()
@@ -257,7 +257,7 @@ object Extension {
private fun extractAndCacheApkIcon( private fun extractAndCacheApkIcon(
apkFilePath: String, apkFilePath: String,
apkName: String, pkgName: String,
) { ) {
val iconCacheDir = "${applicationDirs.extensionsRoot}/icon" val iconCacheDir = "${applicationDirs.extensionsRoot}/icon"
try { try {
@@ -270,15 +270,15 @@ object Extension {
?.first ?.first
} }
if (iconData == null) { if (iconData == null) {
logger.warn { "No icon found in APK $apkName" } logger.warn { "No icon found in APK $pkgName" }
return return
} }
File(iconCacheDir).mkdirs() File(iconCacheDir).mkdirs()
clearCachedImage(iconCacheDir, apkName) clearCachedImage(iconCacheDir, pkgName)
saveImage("$iconCacheDir/$apkName", iconData.inputStream(), null) saveImage("$iconCacheDir/$pkgName", iconData.inputStream(), null)
} catch (e: Exception) { } catch (e: Exception) {
logger.warn(e) { "Failed to extract icon from APK $apkName" } logger.warn(e) { "Failed to extract icon from APK $pkgName" }
} }
} }
@@ -371,7 +371,7 @@ object Extension {
SourceTable.deleteWhere { SourceTable.extension eq extensionId } SourceTable.deleteWhere { SourceTable.extension eq extensionId }
if (extensionRecord[ExtensionTable.isObsolete]) { if (extensionRecord[ExtensionTable.isObsolete] || extensionRecord[ExtensionTable.apkUrl] == null) {
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
} else { } else {
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {

View File

@@ -23,7 +23,7 @@ object ExtensionTable : IntIdTable() {
val name = varchar("name", 128) val name = varchar("name", 128)
val pkgName = varchar("pkg_name", 128) val pkgName = varchar("pkg_name", 128)
val apkUrl = varchar("apk_url", 2048) val apkUrl = varchar("apk_url", 2048).nullable()
val extensionLib = varchar("extension_lib", 16).nullable() val extensionLib = varchar("extension_lib", 16).nullable()
val versionName = varchar("version_name", 16) val versionName = varchar("version_name", 16)
val versionCode = long("version_code") val versionCode = long("version_code")