Merge branch 'master' of https://github.com/Suwayomi/Suwayomi-Server
This commit is contained in:
@@ -42,7 +42,7 @@ body:
|
||||
label: Suwayomi-Server version
|
||||
description: You can find your Suwayomi-Server version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "v2.1.1867"
|
||||
Example: "v2.2.2100"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
@@ -54,14 +54,14 @@ jobs:
|
||||
run: ./gradlew :server:shadowJar --stacktrace
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: jar
|
||||
path: master/server/build/*.jar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload icons
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: icon
|
||||
path: master/server/src/main/resources/icon
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||
|
||||
- name: Upload scripts.tar.gz
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: scripts
|
||||
path: scripts.tar.gz
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||
|
||||
- name: Upload JRE package
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.name }}-jre
|
||||
path: suwa
|
||||
@@ -134,26 +134,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Jar
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: jar
|
||||
path: server/build
|
||||
|
||||
- name: Download JRE
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||
with:
|
||||
name: ${{ matrix.jre }}-jre
|
||||
path: jre
|
||||
|
||||
- name: Download icons
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: icon
|
||||
path: server/src/main/resources/icon
|
||||
|
||||
- name: Download scripts.tar.gz
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: scripts
|
||||
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||
|
||||
- name: Upload ${{ matrix.name }} release
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: upload/*
|
||||
@@ -174,35 +174,35 @@ jobs:
|
||||
needs: bundle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: jar
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: debian-all
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-assets
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: macOS-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: macOS-arm64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: windows-x64
|
||||
path: release
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
git push origin $TAG
|
||||
|
||||
- name: Upload Preview Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||
|
||||
@@ -56,14 +56,14 @@ jobs:
|
||||
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: jar
|
||||
path: master/server/build/*.jar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload icons
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: icon
|
||||
path: master/server/src/main/resources/icon
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||
|
||||
- name: Upload scripts.tar.gz
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: scripts
|
||||
path: scripts.tar.gz
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||
|
||||
- name: Upload JDK package
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.name }}-jre
|
||||
path: suwa
|
||||
@@ -136,26 +136,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Jar
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: jar
|
||||
path: server/build
|
||||
|
||||
- name: Download JRE
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||
with:
|
||||
name: ${{ matrix.jre }}-jre
|
||||
path: jre
|
||||
|
||||
- name: Download icons
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: icon
|
||||
path: server/src/main/resources/icon
|
||||
|
||||
- name: Download scripts.tar.gz
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: scripts
|
||||
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||
|
||||
- name: Upload ${{ matrix.name }} files
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: upload/*
|
||||
@@ -177,35 +177,35 @@ jobs:
|
||||
needs: bundle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: jar
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: debian-all
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-assets
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: macOS-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: macOS-arm64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: windows-x64
|
||||
path: release
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
run: cd release && sha256sum * > Checksums.sha256
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||
draft: true
|
||||
|
||||
@@ -345,6 +345,57 @@ 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();
|
||||
|
||||
@@ -7,14 +7,26 @@ 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 {
|
||||
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }
|
||||
public class EditText extends TextView {
|
||||
public EditText(android.content.Context context) {
|
||||
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) {
|
||||
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) {
|
||||
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 EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
||||
|
||||
|
||||
+301
-1756
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,8 @@
|
||||
- [What is Suwayomi?](#what-is-suwayomi)
|
||||
- [Features](#features)
|
||||
- [Suwayomi client projects](#suwayomi-client-projects)
|
||||
- [Actively Developed Clients](#actively-developed-clients)
|
||||
- [Inactive Clients (functional but outdated)](#inactive-clients-functional-but-outdated)
|
||||
- [Abandoned Clients (functionality unknown)](#abandoned-clients-functionality-unknown)
|
||||
- [Integrated clients](#integrated-clients)
|
||||
- [Other clients](#other-clients-potentially-inactive-or-abondend)
|
||||
- [Downloading and Running the app](#downloading-and-running-the-app)
|
||||
- [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||
- [Windows](#windows)
|
||||
@@ -38,7 +37,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).
|
||||
|
||||
@@ -69,18 +68,20 @@ You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look
|
||||
**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):
|
||||
##### 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.
|
||||
- [Moku](https://github.com/Youwes09/Moku): A fast, minimal Tauri + Svelte desktop client with clean and minimal UI.
|
||||
##### 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.
|
||||
|
||||
##### 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 abandoned)
|
||||
- [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), can manage its own suwayomi server instance
|
||||
- [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
|
||||
|
||||
# Downloading and Running the app
|
||||
## Using Operating System Specific Bundles
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ subprojects {
|
||||
}
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||
freeCompilerArgs.add("-Xcontext-receivers")
|
||||
freeCompilerArgs.add("-Xcontext-parameters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val getTachideskVersion = { "v2.1.${getCommitCount()}" }
|
||||
val getTachideskVersion = { "v2.2.${getCommitCount()}" }
|
||||
|
||||
val webUIRevisionTag = "r2643"
|
||||
val webUIRevisionTag = "r3136"
|
||||
|
||||
private val getCommitCount = {
|
||||
runCatching {
|
||||
|
||||
+30
-30
@@ -1,22 +1,22 @@
|
||||
[versions]
|
||||
kotlin = "2.3.10"
|
||||
coroutines = "1.10.2"
|
||||
serialization = "1.10.0"
|
||||
kotlin = "2.3.21"
|
||||
coroutines = "1.11.0"
|
||||
serialization = "1.11.0"
|
||||
jvmTarget = "21"
|
||||
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
||||
javalin = "6.7.0"
|
||||
jte = "3.2.3"
|
||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||
javalin = "7.2.0"
|
||||
jte = "3.2.4"
|
||||
jackson = "3.1.2" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||
exposed = "0.61.0"
|
||||
dex2jar = "2.4.34"
|
||||
polyglot = "24.2.2"
|
||||
dex2jar = "2.4.36"
|
||||
polyglot = "25.0.3"
|
||||
settings = "1.3.0"
|
||||
twelvemonkeys = "3.13.0"
|
||||
graphqlkotlin = "8.8.1"
|
||||
twelvemonkeys = "3.13.1"
|
||||
graphqlkotlin = "8.9.0"
|
||||
xmlserialization = "0.91.3"
|
||||
ktlint = "1.8.0"
|
||||
koin = "4.1.1"
|
||||
moko = "0.26.0"
|
||||
koin = "4.2.1"
|
||||
moko = "0.26.4"
|
||||
|
||||
[libraries]
|
||||
# Kotlin
|
||||
@@ -39,22 +39,22 @@ 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"
|
||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
|
||||
|
||||
# OkHttp
|
||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
||||
okio = "com.squareup.okio:okio:3.16.4"
|
||||
okio = "com.squareup.okio:okio:3.17.0"
|
||||
|
||||
# Javalin api
|
||||
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
||||
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" }
|
||||
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
|
||||
javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
|
||||
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||
jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.20"
|
||||
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
||||
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||
|
||||
@@ -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.11"
|
||||
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"
|
||||
|
||||
@@ -86,10 +86,10 @@ systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
|
||||
# dependencies of Tachiyomi extensions
|
||||
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||
jsoup = "org.jsoup:jsoup:1.22.1"
|
||||
jsoup = "org.jsoup:jsoup:1.22.2"
|
||||
|
||||
# Config
|
||||
config = "com.typesafe:config:1.4.5"
|
||||
config = "com.typesafe:config:1.4.8"
|
||||
config4k = "io.github.config4k:config4k:0.7.0"
|
||||
|
||||
# Sort
|
||||
@@ -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:9.2.1"
|
||||
|
||||
# Xml
|
||||
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||
@@ -115,13 +115,13 @@ 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"
|
||||
commonscompress = "org.apache.commons:commons-compress:1.28.0"
|
||||
junrar = "com.github.junrar:junrar:7.5.7"
|
||||
junrar = "com.github.junrar:junrar:7.5.10"
|
||||
|
||||
# AES/CBC/PKCS7Padding Cypher provider
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83"
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||
|
||||
# AndroidX annotations
|
||||
android-annotations = "androidx.annotation:annotation:1.9.1"
|
||||
android-annotations = "androidx.annotation:annotation:1.10.0"
|
||||
|
||||
# Substitute for duktape-android
|
||||
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
|
||||
@@ -132,7 +132,7 @@ settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.r
|
||||
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
|
||||
|
||||
# ICU4J
|
||||
icu4j = "com.ibm.icu:icu4j:78.2"
|
||||
icu4j = "com.ibm.icu:icu4j:78.3"
|
||||
|
||||
# Image Decoding implementation provider
|
||||
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
|
||||
@@ -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.2"
|
||||
|
||||
# lint - used for renovate to update ktlint version
|
||||
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
||||
@@ -173,16 +173,16 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
||||
|
||||
# Linter
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.0.1"}
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.2.0"}
|
||||
|
||||
# Build config
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.7"}
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.9"}
|
||||
|
||||
# Download
|
||||
download = { id = "de.undercouch.download", version = "5.7.0"}
|
||||
|
||||
# ShadowJar
|
||||
shadowjar = { id = "com.gradleup.shadow", version = "8.3.9"}
|
||||
shadowjar = { id = "com.gradleup.shadow", version = "8.3.10"}
|
||||
|
||||
# Moko
|
||||
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
Vendored
+10
-21
@@ -23,8 +23,8 @@
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@@ -51,7 +51,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@@ -65,7 +65,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
@@ -73,21 +73,10 @@ goto fail
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
|
||||
@@ -71,7 +71,7 @@ fun createAppModule(app: Application): Module {
|
||||
}
|
||||
}
|
||||
|
||||
single {
|
||||
single<ProtoBuf> {
|
||||
ProtoBuf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,17 +140,16 @@ fun OkHttpClient.newCachelessCallWithProgress(
|
||||
return progressClient.newCall(request)
|
||||
}
|
||||
|
||||
context(Json)
|
||||
context(_: Json)
|
||||
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
context(Json)
|
||||
context(json: Json)
|
||||
fun <T> decodeFromJsonResponse(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
response: Response,
|
||||
): T =
|
||||
response.body.source().use {
|
||||
decodeFromBufferedSource(deserializer, it)
|
||||
json.decodeFromBufferedSource(deserializer, it)
|
||||
}
|
||||
|
||||
class HttpException(
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.javalin.websocket.WsMessageContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.manga.impl.update.Websocket
|
||||
|
||||
object WebView : Websocket<String>() {
|
||||
|
||||
+7
-3
@@ -13,10 +13,14 @@ import com.expediagroup.graphql.server.types.GraphQLRequest
|
||||
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.UploadedFile
|
||||
import io.javalin.json.JavalinJackson
|
||||
import io.javalin.json.fromJsonStream
|
||||
import io.javalin.json.fromJsonString
|
||||
import java.io.IOException
|
||||
|
||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
val jsonMapper = JavalinJackson()
|
||||
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||
return try {
|
||||
@@ -29,17 +33,17 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
context.formParam("operations")
|
||||
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
||||
} else {
|
||||
return context.bodyAsClass(GraphQLServerRequest::class.java)
|
||||
return context.bodyInputStream().use { jsonMapper.fromJsonStream<GraphQLServerRequest>(it) }
|
||||
}
|
||||
|
||||
val request =
|
||||
context.jsonMapper().fromJsonString<GraphQLServerRequest>(formParam)
|
||||
jsonMapper.fromJsonString<GraphQLServerRequest>(formParam)
|
||||
|
||||
val map =
|
||||
context
|
||||
.formParam("map")
|
||||
?.let {
|
||||
context.jsonMapper().fromJsonString<Map<String, List<String>>>(it)
|
||||
jsonMapper.fromJsonString<Map<String, List<String>>>(it)
|
||||
}.orEmpty()
|
||||
|
||||
val mapItems =
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -14,6 +14,8 @@ 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
|
||||
@@ -37,7 +39,9 @@ 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
|
||||
@@ -115,7 +119,6 @@ object Extension {
|
||||
|
||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
||||
val jarFilePath = "$dirPathWithoutType.jar"
|
||||
val dexFilePath = "$dirPathWithoutType.dex"
|
||||
|
||||
val packageInfo = getPackageInfo(apkFilePath)
|
||||
val pkgName = packageInfo.packageName
|
||||
@@ -155,79 +158,115 @@ object Extension {
|
||||
|
||||
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
|
||||
extractAssetsFromApk(apkFilePath, jarFilePath)
|
||||
extractAndCacheApkIcon(apkFilePath, apkName)
|
||||
|
||||
// clean up
|
||||
File(apkFilePath).delete()
|
||||
File(dexFilePath).delete()
|
||||
|
||||
// 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 }
|
||||
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 }
|
||||
|
||||
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 {
|
||||
// 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 }) {
|
||||
it[this.apkName] = apkName
|
||||
it[name] = extensionName
|
||||
it[this.pkgName] = packageInfo.packageName
|
||||
it[this.isInstalled] = true
|
||||
it[this.classFQName] = className
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
it[lang] = extensionLang
|
||||
it[this.isNsfw] = isNsfw
|
||||
}
|
||||
|
||||
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
|
||||
} catch (e: Throwable) {
|
||||
// free up the file descriptor if exists
|
||||
PackageTools.jarLoaderMap.remove(jarFilePath)?.close()
|
||||
File(jarFilePath).delete()
|
||||
|
||||
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}" }
|
||||
}
|
||||
uninstallExtension(pkgName)
|
||||
throw e
|
||||
}
|
||||
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,6 +71,9 @@ 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
|
||||
|
||||
@@ -13,12 +13,14 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.apibuilder.ApiBuilder.after
|
||||
import io.javalin.apibuilder.ApiBuilder.path
|
||||
import io.javalin.config.RoutesConfig
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.HandlerType
|
||||
import io.javalin.http.HttpStatus
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import io.javalin.http.RedirectResponse
|
||||
import io.javalin.http.UnauthorizedResponse
|
||||
import io.javalin.json.JavalinJackson3
|
||||
import io.javalin.rendering.template.JavalinJte
|
||||
import io.javalin.websocket.WsContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -47,6 +49,7 @@ import java.net.URLEncoder
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.text.get
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
object JavalinSetup {
|
||||
@@ -58,10 +61,12 @@ object JavalinSetup {
|
||||
|
||||
fun javalinSetup() {
|
||||
val app =
|
||||
Javalin.create { config ->
|
||||
Javalin.start { config ->
|
||||
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
||||
config.fileRenderer(JavalinJte(templateEngine))
|
||||
|
||||
config.jsonMapper(JavalinJackson3())
|
||||
|
||||
WebInterfaceManager.setup(config)
|
||||
|
||||
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
||||
@@ -104,7 +109,8 @@ object JavalinSetup {
|
||||
}
|
||||
}
|
||||
|
||||
config.router.apiBuilder {
|
||||
config.routes.defineCore()
|
||||
config.routes.apiBuilder {
|
||||
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
||||
path("v1/") {
|
||||
GlobalAPI.defineEndpoints()
|
||||
@@ -117,17 +123,32 @@ object JavalinSetup {
|
||||
after { ctx ->
|
||||
// If not matched, the request was for an invalid endpoint
|
||||
// Return a 404 instead of redirecting to the UI for usability
|
||||
if (ctx.endpointHandlerPath() == "*") {
|
||||
if (ctx.endpoints().lastHttpEndpoint()?.path == "*") {
|
||||
throw NotFoundResponse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.events.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
app.stop()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun RoutesConfig.defineCore() {
|
||||
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
||||
|
||||
app.get(loginPath) { ctx ->
|
||||
get(loginPath) { ctx ->
|
||||
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
||||
ctx.header("content-type", "text/html")
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
@@ -141,7 +162,7 @@ object JavalinSetup {
|
||||
)
|
||||
}
|
||||
|
||||
app.post(loginPath) { ctx ->
|
||||
post(loginPath) { ctx ->
|
||||
val username = ctx.formParam("user")
|
||||
val password = ctx.formParam("pass")
|
||||
val isValid =
|
||||
@@ -174,7 +195,7 @@ object JavalinSetup {
|
||||
)
|
||||
}
|
||||
|
||||
app.beforeMatched { ctx ->
|
||||
beforeMatched { ctx ->
|
||||
val isWebManifest =
|
||||
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
||||
val isPageIcon =
|
||||
@@ -219,60 +240,43 @@ object JavalinSetup {
|
||||
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
||||
}
|
||||
|
||||
app.events { event ->
|
||||
event.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.wsBefore {
|
||||
wsBefore {
|
||||
it.onConnect { ctx ->
|
||||
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
app.stop()
|
||||
},
|
||||
)
|
||||
|
||||
app.exception(NullPointerException::class.java) { e, ctx ->
|
||||
exception(NullPointerException::class.java) { e, ctx ->
|
||||
logger.error(e) { "NullPointerException while handling the request" }
|
||||
ctx.status(404)
|
||||
}
|
||||
app.exception(NoSuchElementException::class.java) { e, ctx ->
|
||||
exception(NoSuchElementException::class.java) { e, ctx ->
|
||||
logger.error(e) { "NoSuchElementException while handling the request" }
|
||||
ctx.status(404)
|
||||
}
|
||||
app.exception(IOException::class.java) { e, ctx ->
|
||||
exception(IOException::class.java) { e, ctx ->
|
||||
logger.error(e) { "IOException while handling the request" }
|
||||
ctx.status(500)
|
||||
ctx.result(e.message ?: "Internal Server Error")
|
||||
}
|
||||
|
||||
app.exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||
exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||
logger.error(e) { "IllegalArgumentException while handling the request" }
|
||||
ctx.status(400)
|
||||
ctx.result(e.message ?: "Bad Request")
|
||||
}
|
||||
|
||||
app.exception(UnauthorizedException::class.java) { e, ctx ->
|
||||
exception(UnauthorizedException::class.java) { e, ctx ->
|
||||
logger.error(e) { "UnauthorizedException while handling the request" }
|
||||
ctx.status(HttpStatus.UNAUTHORIZED)
|
||||
ctx.result(e.message ?: "Unauthorized")
|
||||
}
|
||||
|
||||
app.exception(ForbiddenException::class.java) { e, ctx ->
|
||||
exception(ForbiddenException::class.java) { e, ctx ->
|
||||
logger.error(e) { "ForbiddenException while handling the request" }
|
||||
ctx.status(HttpStatus.FORBIDDEN)
|
||||
ctx.result(e.message ?: "Forbidden")
|
||||
}
|
||||
|
||||
app.start()
|
||||
}
|
||||
|
||||
// private fun getOpenApiOptions(): OpenApiOptions {
|
||||
|
||||
@@ -71,15 +71,14 @@ fun <T> getParam(
|
||||
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
||||
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
||||
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
||||
else -> throw IllegalStateException("Invalid param")
|
||||
}.let {
|
||||
if (param.nullable) {
|
||||
it.allowNullable().get() ?: param.defaultValue
|
||||
it.getOrNull() ?: param.defaultValue
|
||||
} else {
|
||||
if (param.defaultValue != null) {
|
||||
it.getOrDefault(param.defaultValue!!)
|
||||
} else {
|
||||
it.get()
|
||||
it.required().get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.github.reactivecircus.cache4k.Cache
|
||||
import io.javalin.config.JavalinConfig
|
||||
import io.javalin.http.staticfiles.AliasCheck
|
||||
import io.javalin.http.staticfiles.Location
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@@ -39,7 +40,6 @@ import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import org.eclipse.jetty.server.handler.ContextHandler
|
||||
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||
@@ -180,7 +180,7 @@ object WebInterfaceManager {
|
||||
// Use canonical path to avoid Jetty alias issues
|
||||
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
||||
staticFiles.location = Location.EXTERNAL
|
||||
staticFiles.aliasCheck = ContextHandler.ApproveAliases()
|
||||
staticFiles.aliasCheck = AliasCheck { _, _ -> true }
|
||||
}
|
||||
|
||||
serveWebUI = {
|
||||
@@ -206,20 +206,12 @@ object WebInterfaceManager {
|
||||
|
||||
if (ServerSubpath.isDefined() && orgIndexHtml.exists()) {
|
||||
val originalIndexHtml = orgIndexHtml.readText()
|
||||
val subpathInjectionScript =
|
||||
"""
|
||||
<script>
|
||||
// <<suwayomi-subpath-injection>>
|
||||
const baseTag = document.createElement('base');
|
||||
baseTag.href = location.origin + "${ServerSubpath.asRootPath()}";
|
||||
document.head.appendChild(baseTag);
|
||||
</script>
|
||||
""".trimIndent()
|
||||
val subpathInjectionBaseTag = "<base href=\"${ServerSubpath.asRootPath()}\">"
|
||||
|
||||
val indexHtmlWithSubpathInjection =
|
||||
originalIndexHtml.replace(
|
||||
"<head>",
|
||||
"<head>$subpathInjectionScript",
|
||||
"<head>$subpathInjectionBaseTag",
|
||||
)
|
||||
|
||||
orgIndexHtml.writeText(indexHtmlWithSubpathInjection)
|
||||
@@ -312,11 +304,25 @@ object WebInterfaceManager {
|
||||
return
|
||||
}
|
||||
|
||||
val flavor = WebUIFlavor.current
|
||||
val servedFlavor = getServedWebUIFlavor()
|
||||
|
||||
val log =
|
||||
KotlinLogging.logger("${logger.name} setupWebUI(flavor= ${flavor.uiName}, servedFlavor= ${servedFlavor.uiName})")
|
||||
KotlinLogging.logger(
|
||||
"${logger.name} setupWebUI(flavor= ${WebUIFlavor.current.uiName}, servedFlavor= ${servedFlavor.uiName}, channel= ${serverConfig.webUIChannel})",
|
||||
)
|
||||
|
||||
val flavor =
|
||||
if (serverConfig.webUIChannel.value == WebUIChannel.BUNDLED) {
|
||||
if (serverConfig.webUIFlavor.value != WebUIFlavor.default) {
|
||||
log.warn {
|
||||
"Changed flavor to ${WebUIFlavor.default.uiName}. Channel \"${WebUIChannel.BUNDLED}\" only works with the default flavor"
|
||||
}
|
||||
}
|
||||
|
||||
WebUIFlavor.default
|
||||
} else {
|
||||
WebUIFlavor.current
|
||||
}
|
||||
|
||||
if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
|
||||
val currentVersion = getLocalVersion()
|
||||
|
||||
Reference in New Issue
Block a user