mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-01 01:44:34 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9b693b770 |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -42,7 +42,7 @@ body:
|
||||
label: Suwayomi-Server version
|
||||
description: You can find your Suwayomi-Server version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "v2.2.2100"
|
||||
Example: "v2.1.1867"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@@ -1,6 +0,0 @@
|
||||
<!--
|
||||
Pull Request Checklist:
|
||||
- Mention what the pull request does and the reasons behind the changes
|
||||
- Mention all issues the pull request is closing
|
||||
- Make sure to update the CHANGELOG accordingly if necessary based on the LAST stable release
|
||||
-->
|
||||
@@ -345,57 +345,6 @@ public final class Bitmap {
|
||||
return image.getRGB(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Write the specified {@link Color} into the bitmap (assuming it is
|
||||
* mutable) at the x,y coordinate. The color must be a
|
||||
* non-premultiplied ARGB value in the {@link ColorSpace.Named#SRGB sRGB}
|
||||
* color space.</p>
|
||||
*
|
||||
* @param x The x coordinate of the pixel to replace (0...width-1)
|
||||
* @param y The y coordinate of the pixel to replace (0...height-1)
|
||||
* @param color The ARGB color to write into the bitmap
|
||||
*
|
||||
* @throws IllegalStateException if the bitmap is not mutable
|
||||
* @throws IllegalArgumentException if x, y are outside of the bitmap's
|
||||
* bounds.
|
||||
*/
|
||||
public void setPixel(int x, int y, @ColorInt int color) {
|
||||
checkPixelAccess(x, y);
|
||||
image.setRGB(x, y, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Replace pixels in the bitmap with the colors in the array. Each element
|
||||
* in the array is a packed int representing a non-premultiplied ARGB
|
||||
* {@link Color} in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
|
||||
*
|
||||
* @param pixels The colors to write to the bitmap
|
||||
* @param offset The index of the first color to read from pixels[]
|
||||
* @param stride The number of colors in pixels[] to skip between rows.
|
||||
* Normally this value will be the same as the width of
|
||||
* the bitmap, but it can be larger (or negative).
|
||||
* @param x The x coordinate of the first pixel to write to in
|
||||
* the bitmap.
|
||||
* @param y The y coordinate of the first pixel to write to in
|
||||
* the bitmap.
|
||||
* @param width The number of colors to copy from pixels[] per row
|
||||
* @param height The number of rows to write to the bitmap
|
||||
*
|
||||
* @throws IllegalStateException if the bitmap is not mutable
|
||||
* @throws IllegalArgumentException if x, y, width, height are outside of
|
||||
* the bitmap's bounds.
|
||||
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
|
||||
* to receive the specified number of pixels.
|
||||
*/
|
||||
public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
|
||||
int x, int y, int width, int height) {
|
||||
if (width == 0 || height == 0) {
|
||||
return; // nothing to do
|
||||
}
|
||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||
image.setRGB(x, y, width, height, pixels, offset, stride);
|
||||
}
|
||||
|
||||
public void eraseColor(int c) {
|
||||
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
|
||||
@@ -37,27 +37,13 @@ public final class Rect {
|
||||
this.right = 0;
|
||||
this.bottom = 0;
|
||||
} else {
|
||||
this.left = r.left;
|
||||
this.top = r.top;
|
||||
this.right = r.right;
|
||||
this.bottom = r.bottom;
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int left, int top, int right, int bottom) {
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
|
||||
public void set(Rect r) {
|
||||
this.left = r.left;
|
||||
this.top = r.top;
|
||||
this.right = r.right;
|
||||
this.bottom = r.bottom;
|
||||
}
|
||||
|
||||
public final int getWidth() {
|
||||
return right - left;
|
||||
}
|
||||
|
||||
@@ -7,26 +7,14 @@ package android.widget;
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
public class EditText extends TextView {
|
||||
public EditText(android.content.Context context) {
|
||||
super(context);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
public class EditText {
|
||||
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }
|
||||
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs) {
|
||||
super(context);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs) { throw new RuntimeException("Stub!"); }
|
||||
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) {
|
||||
super(context);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) { throw new RuntimeException("Stub!"); }
|
||||
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) { throw new RuntimeException("Stub!"); }
|
||||
|
||||
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
||||
|
||||
|
||||
2057
CHANGELOG.md
2057
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
36
README.md
36
README.md
@@ -3,12 +3,13 @@
|
||||
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
|  | [](https://github.com/Suwayomi/Suwayomi-Server/releases) | [](https://github.com/Suwayomi/Suwayomi-Server-preview/releases/latest) | [](https://discord.gg/DDZdqZWaHA) |
|
||||
|
||||
## Table of Contents
|
||||
## Table of Content
|
||||
- [What is Suwayomi?](#what-is-suwayomi)
|
||||
- [Features](#features)
|
||||
- [Suwayomi client projects](#suwayomi-client-projects)
|
||||
- [Integrated clients](#integrated-clients)
|
||||
- [Other clients](#other-clients-potentially-inactive-or-abondend)
|
||||
- [Actively Developed Clients](#actively-developed-clients)
|
||||
- [Inactive Clients (functional but outdated)](#inactive-clients-functional-but-outdated)
|
||||
- [Abandoned Clients (functionality unknown)](#abandoned-clients-functionality-unknown)
|
||||
- [Downloading and Running the app](#downloading-and-running-the-app)
|
||||
- [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||
- [Windows](#windows)
|
||||
@@ -37,7 +38,7 @@
|
||||
# What is Suwayomi?
|
||||
<img src="https://github.com/Suwayomi/Suwayomi-Server/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
|
||||
|
||||
A free and open source manga reader server that runs extensions built for [Mihon (Tachiyomi)](https://mihon.app/).
|
||||
A free and open source manga reader server that runs extensions built for [Mihon (Tachiyomi)](https://mihon.app/).
|
||||
|
||||
Suwayomi is an independent Mihon (Tachiyomi) compatible software and is **not a Fork of** Mihon (Tachiyomi).
|
||||
|
||||
@@ -64,24 +65,21 @@ You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look
|
||||
- Automated WebUI updates (supports the default WebUI and VUI)
|
||||
- OPDS and OPDS-PSE support (endpoint: `/api/opds/v1.2`)
|
||||
|
||||
# Suwayomi Client Projects
|
||||
# Suwayomi client projects
|
||||
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
|
||||
|
||||
Here's a list of known clients/user interfaces for Suwayomi-Server (checkout the respective GitHub repository for their features):
|
||||
|
||||
##### Integrated clients
|
||||
|
||||
These clients are built-in options, and the server can keep them automatically up-to-date.
|
||||
|
||||
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA
|
||||
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA
|
||||
|
||||
##### Other clients (potentially inactive or abondend)
|
||||
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance
|
||||
- [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), requires access to a running server
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance
|
||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server
|
||||
##### Actively Developed Clients
|
||||
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web front-end that Suwayomi-Server ships with by default.
|
||||
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A Suwayomi-Server preview focused web frontend built with svelte
|
||||
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin.
|
||||
##### Inactive Clients (functional but outdated)
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-Server.
|
||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android with a User Interface inspired by Mihon (Tachiyomi).
|
||||
##### Abandoned Clients (functionality unknown)
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
|
||||
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
|
||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
|
||||
|
||||
# Downloading and Running the app
|
||||
## Using Operating System Specific Bundles
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val getTachideskVersion = { "v2.2.${getCommitCount()}" }
|
||||
val getTachideskVersion = { "v2.1.${getCommitCount()}" }
|
||||
|
||||
val webUIRevisionTag = "r3136"
|
||||
val webUIRevisionTag = "r2643"
|
||||
|
||||
private val getCommitCount = {
|
||||
runCatching {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[versions]
|
||||
kotlin = "2.3.10"
|
||||
kotlin = "2.3.0"
|
||||
coroutines = "1.10.2"
|
||||
serialization = "1.10.0"
|
||||
serialization = "1.9.0"
|
||||
jvmTarget = "21"
|
||||
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
||||
javalin = "6.7.0"
|
||||
jte = "3.2.3"
|
||||
jte = "3.2.1"
|
||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||
exposed = "0.61.0"
|
||||
dex2jar = "2.4.34"
|
||||
@@ -16,7 +16,7 @@ graphqlkotlin = "8.8.1"
|
||||
xmlserialization = "0.91.3"
|
||||
ktlint = "1.8.0"
|
||||
koin = "4.1.1"
|
||||
moko = "0.26.0"
|
||||
moko = "0.25.2"
|
||||
|
||||
[libraries]
|
||||
# Kotlin
|
||||
@@ -38,8 +38,8 @@ serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", v
|
||||
|
||||
# Logging
|
||||
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
||||
logback = "ch.qos.logback:logback-classic:1.5.32"
|
||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.01"
|
||||
logback = "ch.qos.logback:logback-classic:1.5.28"
|
||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.0.14"
|
||||
|
||||
# OkHttp
|
||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
@@ -68,7 +68,7 @@ exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "e
|
||||
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
||||
postgres = "org.postgresql:postgresql:42.7.10"
|
||||
postgres = "org.postgresql:postgresql:42.7.9"
|
||||
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
||||
hikaricp = "com.zaxxer:HikariCP:7.0.2"
|
||||
|
||||
@@ -105,7 +105,7 @@ dex2jar-tools = { module = "de.femtopedia.dex2jar:dex-tools", version.ref = "dex
|
||||
|
||||
# APK
|
||||
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
||||
apksig = "com.android.tools.build:apksig:9.0.1"
|
||||
apksig = "com.android.tools.build:apksig:8.13.2"
|
||||
|
||||
# Xml
|
||||
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||
@@ -113,7 +113,7 @@ xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||
# Disk & File
|
||||
appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
|
||||
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
||||
zip4j = "net.lingala.zip4j:zip4j:2.11.6"
|
||||
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
|
||||
commonscompress = "org.apache.commons:commons-compress:1.28.0"
|
||||
junrar = "com.github.junrar:junrar:7.5.7"
|
||||
|
||||
@@ -158,7 +158,7 @@ cronUtils = "com.cronutils:cron-utils:9.2.1"
|
||||
kcef = "dev.datlag:kcef:2024.04.20.4"
|
||||
|
||||
# User
|
||||
jwt = "com.auth0:java-jwt:4.5.1"
|
||||
jwt = "com.auth0:java-jwt:4.5.0"
|
||||
|
||||
# lint - used for renovate to update ktlint version
|
||||
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
||||
@@ -179,7 +179,7 @@ ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.0.1"}
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.7"}
|
||||
|
||||
# Download
|
||||
download = { id = "de.undercouch.download", version = "5.7.0"}
|
||||
download = { id = "de.undercouch.download", version = "5.6.0"}
|
||||
|
||||
# ShadowJar
|
||||
shadowjar = { id = "com.gradleup.shadow", version = "8.3.9"}
|
||||
|
||||
@@ -53,11 +53,4 @@
|
||||
<string name="manga_status_licensed">正式版</string>
|
||||
<string name="manga_status_publishing_finished">連載終了</string>
|
||||
<string name="manga_status_cancelled">打ち切り</string>
|
||||
<string name="opds_feeds_history_title">履歴</string>
|
||||
<string name="opds_feeds_history_entry_content">最近読んだ章</string>
|
||||
<string name="opds_feeds_all_series_in_library_title">すべてのマンガ</string>
|
||||
<string name="opds_feeds_all_series_in_library_entry_content">ライブラリに保存されたマンガを閲覧</string>
|
||||
<string name="opds_feeds_library_sources_title">ソース</string>
|
||||
<string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string>
|
||||
<string name="opds_feeds_search_results_title">検索結果</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_search_description">Wyszukaj serie w katalogu.</string>
|
||||
<string name="opds_search_description">Wyszukiwanie mangi w katalogu</string>
|
||||
<string name="manga_status_on_hiatus">Zawieszone</string>
|
||||
<string name="opds_feeds_genre_specific_title">Gatunek: %1$s</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Szczegóły</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_feeds_library_updates_title">Historia Aktualizacji Biblioteki</string>
|
||||
<string name="opds_feeds_categories_entry_content">Przeglądaj serie uporządkowane według kategorii</string>
|
||||
<string name="opds_feeds_categories_entry_content">Przeglądaj mangi uporządkowane według kategorii</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_linktitle_self_feed">Aktualny Kanał</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
@@ -14,11 +14,11 @@
|
||||
<string name="opds_feeds_manga_chapters">%1$s Rozdziały</string>
|
||||
<string name="opds_search_shortname">Suwayomi Wyszukiwanie OPDS</string>
|
||||
<string name="opds_feeds_root">Suwayomi Katalog OPDS</string>
|
||||
<string name="opds_feeds_sources_title">Wszystkie Źródła</string>
|
||||
<string name="opds_feeds_sources_title">Źródła</string>
|
||||
<string name="opds_feeds_genres_title">Gatunki</string>
|
||||
<string name="opds_feeds_status_title">Status</string>
|
||||
<string name="opds_feeds_languages_title">Języki</string>
|
||||
<string name="opds_feeds_languages_entry_content">Przeglądaj serie według języka treści</string>
|
||||
<string name="opds_feeds_languages_entry_content">Przeglądaj mangi według języka treści</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Ostatnio zaktualizowane rozdziały z biblioteki</string>
|
||||
<string name="opds_feeds_category_specific_title">Kategoria: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||
@@ -32,8 +32,8 @@
|
||||
<string name="opds_facet_sort_date_asc">Data rosnąco</string>
|
||||
<string name="opds_facet_sort_date_desc">Data malejąco</string>
|
||||
<string name="opds_facet_filter_all_chapters">Wszystkie Rozdziały</string>
|
||||
<string name="opds_facet_filter_unread_only">Nieprzeczytane</string>
|
||||
<string name="opds_facet_filter_read_only">Przeczytane</string>
|
||||
<string name="opds_facet_filter_unread_only">Tylko Nieprzeczytane</string>
|
||||
<string name="opds_facet_filter_read_only">Tylko Przeczytane</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Wyświetl Szczegóły Rozdziału i Pobierz Strony</string>
|
||||
<string name="opds_linktitle_download_cbz">Pobierz CBZ</string>
|
||||
<string name="opds_linktitle_chapter_cover">Okładka Rozdziału</string>
|
||||
@@ -51,29 +51,11 @@
|
||||
<string name="manga_status_publishing_finished">Publikacja Zakończona</string>
|
||||
<string name="manga_status_cancelled">Anulowano</string>
|
||||
<string name="opds_feeds_categories_title">Kategorie</string>
|
||||
<string name="opds_feeds_genres_entry_content">Przeglądaj serie według tagów gatunku</string>
|
||||
<string name="opds_feeds_status_entry_content">Przeglądaj serie według statusu publikacji</string>
|
||||
<string name="opds_feeds_genres_entry_content">Przeglądaj mangi według tagów gatunku</string>
|
||||
<string name="opds_feeds_status_entry_content">Przeglądaj mangi według statusu publikacji</string>
|
||||
<string name="opds_feeds_source_specific_popular_title">Źródło: %1$s - Popularne</string>
|
||||
<string name="opds_feeds_library_source_specific_title">Biblioteka - Źródło: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_latest_title">Źródło: %1$s - Ostatnie</string>
|
||||
<string name="opds_feeds_search_results_title">Wyniki Wyszukiwania</string>
|
||||
<string name="opds_feeds_history_title">Historia</string>
|
||||
<string name="opds_feeds_explore_title">Odkrywaj</string>
|
||||
<string name="opds_feeds_explore_entry_content">Odkryj nowe serie ze swoich źródeł</string>
|
||||
<string name="opds_feeds_history_entry_content">Ostatnio przeczytane rozdziały</string>
|
||||
<string name="opds_feeds_all_series_in_library_title">Wszystkie serie</string>
|
||||
<string name="opds_feeds_all_series_in_library_entry_content">Przeglądaj wszystkie serie zapisane w bibliotece</string>
|
||||
<string name="opds_feeds_library_sources_title">Źródła</string>
|
||||
<string name="opds_feeds_library_sources_entry_content">Przeglądaj serie w swojej bibliotece filtrowane według źródła</string>
|
||||
<string name="opds_facet_sort_popular">Popularność</string>
|
||||
<string name="opds_facet_sort_latest">Najnowsze</string>
|
||||
<string name="opds_facet_sort_alpha_asc">Alfabetycznie od A do Z</string>
|
||||
<string name="opds_facet_sort_alpha_desc">Alfabetycznie Z-A</string>
|
||||
<string name="opds_facet_sort_last_read_desc">Ostatnio czytane</string>
|
||||
<string name="opds_facet_sort_latest_chapter_desc">Najnowszy rozdział</string>
|
||||
<string name="opds_facet_sort_date_added_desc">Data dodania</string>
|
||||
<string name="opds_facet_sort_unread_desc">Nieprzeczytane rozdziały</string>
|
||||
<string name="opds_facet_filter_all">Wszystkie</string>
|
||||
<string name="opds_facet_filter_downloaded">Pobrane</string>
|
||||
<string name="opds_facet_filter_ongoing">Trwające</string>
|
||||
</resources>
|
||||
|
||||
@@ -71,7 +71,7 @@ fun createAppModule(app: Application): Module {
|
||||
}
|
||||
}
|
||||
|
||||
single<ProtoBuf> {
|
||||
single {
|
||||
ProtoBuf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@@ -24,7 +24,6 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import okio.Buffer
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
@@ -71,8 +70,7 @@ class CloudflareInterceptor(
|
||||
flareResponse.solution.status in 200..299 &&
|
||||
flareResponse.solution.response != null
|
||||
) {
|
||||
val isImage =
|
||||
flareResponse.solution.response.contains(CHROME_IMAGE_TEMPLATE_REGEX)
|
||||
val isImage = flareResponse.solution.response.contains(CHROME_IMAGE_TEMPLATE_REGEX)
|
||||
if (!isImage) {
|
||||
logger.debug { "Falling back to FlareSolverr response" }
|
||||
|
||||
@@ -89,8 +87,7 @@ class CloudflareInterceptor(
|
||||
}
|
||||
}
|
||||
|
||||
val request =
|
||||
CFClearance.requestWithFlareSolverr(flareResponse, setUserAgent, originalRequest)
|
||||
val request = CFClearance.requestWithFlareSolverr(flareResponse, setUserAgent, originalRequest)
|
||||
|
||||
chain.proceed(request)
|
||||
} catch (e: Exception) {
|
||||
@@ -190,6 +187,7 @@ object CFClearance {
|
||||
onlyCookies: Boolean,
|
||||
): FlareSolverResponse {
|
||||
val timeout = serverConfig.flareSolverrTimeout.value.seconds
|
||||
|
||||
return with(json) {
|
||||
mutex.withLock {
|
||||
client.value
|
||||
@@ -200,7 +198,7 @@ object CFClearance {
|
||||
Json
|
||||
.encodeToString(
|
||||
FlareSolverRequest(
|
||||
"request.${originalRequest.method.lowercase()}",
|
||||
"request.get",
|
||||
originalRequest.url.toString(),
|
||||
session = serverConfig.flareSolverrSessionName.value,
|
||||
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
|
||||
@@ -210,22 +208,6 @@ object CFClearance {
|
||||
},
|
||||
returnOnlyCookies = onlyCookies,
|
||||
maxTimeout = timeout.inWholeMilliseconds.toInt(),
|
||||
postData =
|
||||
if (originalRequest.method == "POST") {
|
||||
when (val body = originalRequest.body) {
|
||||
is FormBody -> {
|
||||
Buffer()
|
||||
.also { body.writeTo(it) }
|
||||
.readUtf8()
|
||||
}
|
||||
|
||||
else -> {
|
||||
""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
),
|
||||
).toRequestBody(jsonMediaType),
|
||||
),
|
||||
@@ -256,9 +238,7 @@ object CFClearance {
|
||||
if (!cookie.path.isNullOrEmpty()) it.path(cookie.path)
|
||||
// We need to convert the expires time to milliseconds for the persistent cookie store
|
||||
if (cookie.expires != null && cookie.expires > 0) it.expiresAt((cookie.expires * 1000).toLong())
|
||||
if (!cookie.domain.startsWith('.')) {
|
||||
it.hostOnlyDomain(cookie.domain.removePrefix("."))
|
||||
}
|
||||
if (!cookie.domain.startsWith('.')) it.hostOnlyDomain(cookie.domain.removePrefix("."))
|
||||
}.build()
|
||||
}.groupBy { it.domain }
|
||||
.flatMap { (domain, cookies) ->
|
||||
|
||||
@@ -14,8 +14,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import net.dongliu.apk.parser.ApkFile
|
||||
import net.dongliu.apk.parser.bean.Icon
|
||||
import okhttp3.CacheControl
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
@@ -39,9 +37,7 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.saveImage
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
@@ -119,6 +115,7 @@ object Extension {
|
||||
|
||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
||||
val jarFilePath = "$dirPathWithoutType.jar"
|
||||
val dexFilePath = "$dirPathWithoutType.dex"
|
||||
|
||||
val packageInfo = getPackageInfo(apkFilePath)
|
||||
val pkgName = packageInfo.packageName
|
||||
@@ -158,115 +155,79 @@ object Extension {
|
||||
|
||||
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
|
||||
extractAssetsFromApk(apkFilePath, jarFilePath)
|
||||
extractAndCacheApkIcon(apkFilePath, apkName)
|
||||
|
||||
// clean up
|
||||
File(apkFilePath).delete()
|
||||
File(dexFilePath).delete()
|
||||
|
||||
try {
|
||||
// collect sources from the extension
|
||||
val extensionMainClassInstance = loadExtensionSources(jarFilePath, className)
|
||||
val sources: List<CatalogueSource> =
|
||||
when (extensionMainClassInstance) {
|
||||
is Source -> listOf(extensionMainClassInstance)
|
||||
is SourceFactory -> extensionMainClassInstance.createSources()
|
||||
else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}")
|
||||
}.map { it as CatalogueSource }
|
||||
// collect sources from the extension
|
||||
val extensionMainClassInstance = loadExtensionSources(jarFilePath, className)
|
||||
val sources: List<CatalogueSource> =
|
||||
when (extensionMainClassInstance) {
|
||||
is Source -> listOf(extensionMainClassInstance)
|
||||
is SourceFactory -> extensionMainClassInstance.createSources()
|
||||
else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}")
|
||||
}.map { it as CatalogueSource }
|
||||
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val extensionLang =
|
||||
when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
else -> "all"
|
||||
}
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val extensionLang =
|
||||
when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
else -> "all"
|
||||
}
|
||||
|
||||
val extensionName =
|
||||
packageInfo.applicationInfo.nonLocalizedLabel
|
||||
.toString()
|
||||
.substringAfter("Tachiyomi: ")
|
||||
val extensionName =
|
||||
packageInfo.applicationInfo.nonLocalizedLabel
|
||||
.toString()
|
||||
.substringAfter("Tachiyomi: ")
|
||||
|
||||
// update extension info
|
||||
transaction {
|
||||
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
|
||||
ExtensionTable.insert {
|
||||
it[this.apkName] = apkName
|
||||
it[name] = extensionName
|
||||
it[this.pkgName] = packageInfo.packageName
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
it[lang] = extensionLang
|
||||
it[this.isNsfw] = isNsfw
|
||||
}
|
||||
}
|
||||
|
||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||
// update extension info
|
||||
transaction {
|
||||
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
|
||||
ExtensionTable.insert {
|
||||
it[this.apkName] = apkName
|
||||
it[this.isInstalled] = true
|
||||
it[this.classFQName] = className
|
||||
it[name] = extensionName
|
||||
it[this.pkgName] = packageInfo.packageName
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
}
|
||||
|
||||
val extensionId =
|
||||
ExtensionTable
|
||||
.selectAll()
|
||||
.where { ExtensionTable.pkgName eq pkgName }
|
||||
.first()[ExtensionTable.id]
|
||||
.value
|
||||
|
||||
sources.forEach { httpSource ->
|
||||
SourceTable.insert {
|
||||
it[id] = httpSource.id
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
it[lang] = extensionLang
|
||||
it[this.isNsfw] = isNsfw
|
||||
}
|
||||
}
|
||||
return 201 // we installed successfully
|
||||
} catch (e: Throwable) {
|
||||
// free up the file descriptor if exists
|
||||
PackageTools.jarLoaderMap.remove(jarFilePath)?.close()
|
||||
File(jarFilePath).delete()
|
||||
|
||||
uninstallExtension(pkgName)
|
||||
throw e
|
||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||
it[this.apkName] = apkName
|
||||
it[this.isInstalled] = true
|
||||
it[this.classFQName] = className
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
}
|
||||
|
||||
val extensionId =
|
||||
ExtensionTable
|
||||
.selectAll()
|
||||
.where { ExtensionTable.pkgName eq pkgName }
|
||||
.first()[ExtensionTable.id]
|
||||
.value
|
||||
|
||||
sources.forEach { httpSource ->
|
||||
SourceTable.insert {
|
||||
it[id] = httpSource.id
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
}
|
||||
}
|
||||
return 201 // we installed successfully
|
||||
} else {
|
||||
return 302 // extension was already installed
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAndCacheApkIcon(
|
||||
apkFilePath: String,
|
||||
apkName: String,
|
||||
) {
|
||||
val iconCacheDir = "${applicationDirs.extensionsRoot}/icon"
|
||||
try {
|
||||
val iconData =
|
||||
ApkFile(File(apkFilePath)).use { apk ->
|
||||
apk.allIcons
|
||||
.filterIsInstance<Icon>()
|
||||
.mapNotNull { it.data?.let { data -> data to it.density } }
|
||||
.maxByOrNull { (_, density) -> density }
|
||||
?.first
|
||||
}
|
||||
if (iconData == null) {
|
||||
logger.warn { "No icon found in APK $apkName" }
|
||||
return
|
||||
}
|
||||
|
||||
File(iconCacheDir).mkdirs()
|
||||
clearCachedImage(iconCacheDir, apkName)
|
||||
saveImage("$iconCacheDir/$apkName", iconData.inputStream(), null)
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Failed to extract icon from APK $apkName" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAssetsFromApk(
|
||||
apkPath: String,
|
||||
jarPath: String,
|
||||
|
||||
@@ -71,9 +71,6 @@ object ImageUtil {
|
||||
if (bytes.compareWith(charByteArrayOf(0xFF, 0x0A))) {
|
||||
return JXL
|
||||
}
|
||||
if (bytes.compareWith(charByteArrayOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A))) {
|
||||
return JXL
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -206,12 +206,20 @@ object WebInterfaceManager {
|
||||
|
||||
if (ServerSubpath.isDefined() && orgIndexHtml.exists()) {
|
||||
val originalIndexHtml = orgIndexHtml.readText()
|
||||
val subpathInjectionBaseTag = "<base href=\"${ServerSubpath.asRootPath()}\">"
|
||||
val subpathInjectionScript =
|
||||
"""
|
||||
<script>
|
||||
// <<suwayomi-subpath-injection>>
|
||||
const baseTag = document.createElement('base');
|
||||
baseTag.href = location.origin + "${ServerSubpath.asRootPath()}";
|
||||
document.head.appendChild(baseTag);
|
||||
</script>
|
||||
""".trimIndent()
|
||||
|
||||
val indexHtmlWithSubpathInjection =
|
||||
originalIndexHtml.replace(
|
||||
"<head>",
|
||||
"<head>$subpathInjectionBaseTag",
|
||||
"<head>$subpathInjectionScript",
|
||||
)
|
||||
|
||||
orgIndexHtml.writeText(indexHtmlWithSubpathInjection)
|
||||
|
||||
Reference in New Issue
Block a user