mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-05 11:54:38 -05:00
Compare commits
16 Commits
v0.6.1
...
token-auth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53c3ac5676 | ||
|
|
a26b8ecca0 | ||
|
|
5a32ccfa7a | ||
|
|
f51818b157 | ||
|
|
31a624db51 | ||
|
|
f045b18762 | ||
|
|
f5006cac7d | ||
|
|
152b193ad5 | ||
|
|
a27af0b642 | ||
|
|
44ffed3f7c | ||
|
|
fa035ad9be | ||
|
|
186ace4343 | ||
|
|
8fb1a0bb1f | ||
|
|
05513bf8b9 | ||
|
|
858784857e | ||
|
|
291a23949a |
75
CHANGELOG.md
75
CHANGELOG.md
@@ -1,3 +1,78 @@
|
||||
# Server: v0.6.3 + WebUI: r942
|
||||
## TL;DR
|
||||
- Changes in Server
|
||||
- Support for array search filter changes list
|
||||
- Support for Tachiyomi extensions lib 1.3
|
||||
- Changes in WebUI
|
||||
- Better search filter support
|
||||
- Fluid manga grid
|
||||
- Library comfortable grid
|
||||
- Sources view layouts
|
||||
- Various other changes...
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- (r1074) v0.6.2 (by @AriaMoradi)
|
||||
- (r1075) support array filter changes ([#304](https://github.com/Suwayomi/Tachidesk-Server/pull/304) by @AriaMoradi)
|
||||
- (r1076) fix filterlist bugs ([#306](https://github.com/Suwayomi/Tachidesk-Server/pull/306) by @AriaMoradi)
|
||||
- (r1077) Update README.md ([#305](https://github.com/Suwayomi/Tachidesk-Server/pull/305) by @mahor1221)
|
||||
- (r1078) fix meta update changing all keys ([#314](https://github.com/Suwayomi/Tachidesk-Server/pull/314) by @AriaMoradi)
|
||||
- (r1079) add support for tachiyomi extensions Lib 1.3 ([#316](https://github.com/Suwayomi/Tachidesk-Server/pull/316) by @AriaMoradi)
|
||||
- (r1080) Fix sources list of one source throws an exception ([#308](https://github.com/Suwayomi/Tachidesk-Server/pull/308) by @Syer10)
|
||||
- (r1081) Improve source handling, fix errors with uninitialized mangas in broken sources ([#319](https://github.com/Suwayomi/Tachidesk-Server/pull/319) by @Syer10)
|
||||
- (r1082) Add thumbnail support for stub sources ([#320](https://github.com/Suwayomi/Tachidesk-Server/pull/320) by @Syer10)
|
||||
- (r1083) update description for Tachidesk-Sorayomi ([#326](https://github.com/Suwayomi/Tachidesk-Server/pull/326) by @DattatreyaReddy)
|
||||
- (r1084) Add last bit of code needed for Extensions Lib 1.3 ([#330](https://github.com/Suwayomi/Tachidesk-Server/pull/330) by @Syer10)
|
||||
- (r1085) Add QuickJS, replaces Duktape for Extensions Lib 1.3 ([#331](https://github.com/Suwayomi/Tachidesk-Server/pull/331) by @Syer10)
|
||||
- (r1086) fix auth not actually blocking requests ([#333](https://github.com/Suwayomi/Tachidesk-Server/pull/333) by @AriaMoradi)
|
||||
|
||||
## Tachidesk-WebUI Changelog
|
||||
- (r930) Source filter scroll fix (array of filters on submit [#149](https://github.com/Suwayomi/Tachidesk-WebUI/pull/149) by @Robonau)
|
||||
- (r931) fix manga badges setting menu that turns the update/download badges on and off ([#150](https://github.com/Suwayomi/Tachidesk-WebUI/pull/150) by @Robonau)
|
||||
- (r932) move sorts to copy tachiyomi ([#151](https://github.com/Suwayomi/Tachidesk-WebUI/pull/151) by @Robonau)
|
||||
- (r933) add comfortable grid option ([#152](https://github.com/Suwayomi/Tachidesk-WebUI/pull/152) by @Robonau)
|
||||
- (r934) source layouts ([#153](https://github.com/Suwayomi/Tachidesk-WebUI/pull/153) by @Robonau)
|
||||
- (r935) List layout ([#154](https://github.com/Suwayomi/Tachidesk-WebUI/pull/154) by @Robonau)
|
||||
- (r936) in library badge to manga in sources ([#156](https://github.com/Suwayomi/Tachidesk-WebUI/pull/156) by @Robonau)
|
||||
- (r937) mass search ([#157](https://github.com/Suwayomi/Tachidesk-WebUI/pull/157) by @Robonau)
|
||||
- (r938) 18+ tag on source/extension cards ([#160](https://github.com/Suwayomi/Tachidesk-WebUI/pull/160) by @Robonau)
|
||||
- (r939) fix search source click ([#164](https://github.com/Suwayomi/Tachidesk-WebUI/pull/164) by @Robonau)
|
||||
- (r940) items per row setting ([#165](https://github.com/Suwayomi/Tachidesk-WebUI/pull/165) by @Robonau)
|
||||
- (r941) fix the grid width thing ([#169](https://github.com/Suwayomi/Tachidesk-WebUI/pull/169) by @Robonau)
|
||||
- (r942) unified library options ([#168](https://github.com/Suwayomi/Tachidesk-WebUI/pull/168) by @infix)
|
||||
|
||||
# Server: v0.6.2 + WebUI: r929
|
||||
## TL;DR
|
||||
- Changes in WebUI
|
||||
- Moved search to Browse
|
||||
- Support for Source Filters
|
||||
- Better visuals for Download Queue
|
||||
- A live version of WebUI is now available [at this link](https://tachidesk-webui-preview.github.io/).
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- (r1073) Refactor debian-packager.sh, rename launcher scripts ([#303](https://github.com/Suwayomi/Tachidesk-Server/pull/303) by @mahor1221)
|
||||
|
||||
## Tachidesk-WebUI Changelog
|
||||
- (r912) show locale date, less confusing ([#131](https://github.com/Suwayomi/Tachidesk-WebUI/pull/131) by @AriaMoradi)
|
||||
- (r913) fix links to work on a bare host ([#132](https://github.com/Suwayomi/Tachidesk-WebUI/pull/132) by @AriaMoradi)
|
||||
- (r914) fix direct links ([#133](https://github.com/Suwayomi/Tachidesk-WebUI/pull/133) by @AriaMoradi)
|
||||
- (r915) deploy to github pages (by @AriaMoradi)
|
||||
- (r916) fix typo (by @AriaMoradi)
|
||||
- (r917) better naming (by @AriaMoradi)
|
||||
- (r918) update notice about github pages (by @AriaMoradi)
|
||||
- (r919) move text (by @AriaMoradi)
|
||||
- (r920) make all links work by catching 404 (by @AriaMoradi)
|
||||
- (r921) fix scrolling 8px ([#135](https://github.com/Suwayomi/Tachidesk-WebUI/pull/135) by @Robonau)
|
||||
- (r922) sorting ([#136](https://github.com/Suwayomi/Tachidesk-WebUI/pull/136) by @Robonau)
|
||||
- (r923) Close button fix ([#141](https://github.com/Suwayomi/Tachidesk-WebUI/pull/141)) z14942744@gmail.com
|
||||
- (r924) add NavBarContextProvider ([#128](https://github.com/Suwayomi/Tachidesk-WebUI/pull/128) by @abhijeetChawla)
|
||||
- (r925) Resolved Merged Conflicts ([#127](https://github.com/Suwayomi/Tachidesk-WebUI/pull/127) by @abhijeetChawla)
|
||||
- (r926) more Download Queue info ([#138](https://github.com/Suwayomi/Tachidesk-WebUI/pull/138) by @Robonau)
|
||||
- (r927) Source filters, move search to SourceMangas ([#142](https://github.com/Suwayomi/Tachidesk-WebUI/pull/142) by @Robonau)
|
||||
- (r928) Source genre sorts design ([#147](https://github.com/Suwayomi/Tachidesk-WebUI/pull/147) by @Robonau)
|
||||
- (r929) Update LibraryOptions.tsx ([#146](https://github.com/Suwayomi/Tachidesk-WebUI/pull/146) by @Robonau)
|
||||
|
||||
|
||||
|
||||
# Server: v0.6.1 + WebUI: r911
|
||||
## TL;DR
|
||||
- msi and deb packages thanks to @mahor1221
|
||||
|
||||
25
README.md
25
README.md
@@ -49,7 +49,7 @@ Here's a list of known clients/user interfaces for Tachidesk-Server:
|
||||
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server is traditionally shipped with. Usually gets new features faster.
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Tachidesk-Server. Currently the most advanced.
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
|
||||
- [Tachidesk-Flutter](https://github.com/Suwayomi/Tachidesk-Flutter): A Flutter front-end for Desktop(Linux, windows, etc.), in early stage of development.
|
||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android. UI and UX similar to Tachiyomi.
|
||||
##### Inctive/Abandoned Cients
|
||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
|
||||
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client, in super early stage of development.
|
||||
@@ -80,33 +80,40 @@ If a bundle for your operating system or cpu architecture is not provided then r
|
||||
**Node:** Linux launcher scripts are named a bit differently but work the same.
|
||||
|
||||
### Windows
|
||||
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
|
||||
|
||||
Unzip the downloaded file and double click on one of the launcher scripts.
|
||||
|
||||
### macOS
|
||||
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
|
||||
|
||||
Unzip the downloaded file and double click on one of the launcher scripts.
|
||||
|
||||
### GNU/Linux
|
||||
Download the latest `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Tachidesk/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
|
||||
Download the latest `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
|
||||
|
||||
`tar xvf` the downloaded file and double click on one of the launcher scripts or run them using the terminal.
|
||||
|
||||
## Other methods of getting Tachidesk
|
||||
### Arch Linux
|
||||
You can install Tachidesk from the AUR
|
||||
You can install Tachidesk from the AUR:
|
||||
```
|
||||
yay -S tachidesk
|
||||
```
|
||||
|
||||
### Ubuntu-based distributions
|
||||
More information can be found on the [PPA's page](https://launchpad.net/~suwayomi/+archive/ubuntu/tachidesk).
|
||||
### Debian/Ubuntu
|
||||
Download the latest deb package from the release section or Install from the MPR
|
||||
```
|
||||
sudo add-apt-repository ppa:suwayomi/tachidesk
|
||||
git clone https://mpr.makedeb.org/tachidesk-server.git
|
||||
cd tachidesk-server
|
||||
makedeb -si
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
```
|
||||
sudo add-apt-repository ppa:suwayomi/tachidesk-server
|
||||
sudo apt update
|
||||
sudo apt install tachidesk
|
||||
sudo apt install tachidesk-server
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
@@ -12,9 +12,9 @@ const val kotlinVersion = "1.6.10"
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.6.1"
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.6.3"
|
||||
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r911"
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r942"
|
||||
|
||||
// counts commits on the master branch
|
||||
val tachideskRevision = runCatching {
|
||||
|
||||
@@ -6,43 +6,46 @@
|
||||
# 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/.
|
||||
|
||||
echo "creating debian package"
|
||||
echo "Creating DEB Package"
|
||||
pkgname="tachidesk-server"
|
||||
PkgName="Tachidesk-Server"
|
||||
jar=$(ls ../server/build/*.jar | tail -n1)
|
||||
release_ver=$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v)
|
||||
orig_dir="tachidesk-$release_ver" # dir uses hyphen "-"
|
||||
orig_tar_gz="tachidesk_$release_ver.orig.tar.gz" # orig file uses underscore "_"
|
||||
package_name="tachidesk_$release_ver-1_all.deb"
|
||||
pkgver="$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v)"
|
||||
pkgrel=1
|
||||
|
||||
# copy artifacts
|
||||
mkdir "$orig_dir"
|
||||
cp "$jar" "$orig_dir/Tachidesk.jar"
|
||||
cp -r "resources/debian" "$orig_dir"
|
||||
cp "resources/tachidesk-browser-launcher-standalone.sh" "$orig_dir/debian"
|
||||
cp "resources/tachidesk-debug-launcher-standalone.sh" "$orig_dir/debian"
|
||||
cp "resources/tachidesk-electron-launcher-standalone.sh" "$orig_dir/debian"
|
||||
cp "resources/tachidesk.desktop" "$orig_dir/debian"
|
||||
cp "../server/src/main/resources/icon/faviconlogo.png" "$orig_dir/debian/tachidesk.png"
|
||||
srcdir="$pkgname-$pkgver" # uses hyphen "-"
|
||||
srctgz="${pkgname}_$pkgver.orig.tar.gz" # uses underscore "_"
|
||||
deb="${pkgname}_$pkgver-${pkgrel}_all.deb"
|
||||
Deb="${PkgName}_$pkgver-${pkgrel}_all.deb"
|
||||
|
||||
# prepare
|
||||
tar cvzf "$orig_tar_gz" "$orig_dir/Tachidesk.jar"
|
||||
sed -i "s/\${version}/$release_ver/" "$orig_dir/debian/changelog"
|
||||
# Prepare
|
||||
mkdir "$srcdir/"
|
||||
cp "$jar" "$srcdir/$pkgname.jar"
|
||||
cp "resources/$pkgname-browser-launcher.sh" "$srcdir/"
|
||||
cp "resources/$pkgname-debug-launcher.sh" "$srcdir/"
|
||||
cp "resources/$pkgname-electron-launcher-debian.sh" "$srcdir/$pkgname-electron-launcher.sh"
|
||||
cp "resources/$pkgname.desktop" "$srcdir/"
|
||||
cp "../server/src/main/resources/icon/faviconlogo.png" "$srcdir/$pkgname.png"
|
||||
|
||||
# build
|
||||
mkdir "build"
|
||||
mv $orig_dir $orig_tar_gz "build/"
|
||||
cd "build/$orig_dir/debian"
|
||||
GZIP=-9 tar -cvzf "$srctgz" "$srcdir/"
|
||||
|
||||
cp -r "resources/debian" "$srcdir/"
|
||||
sed -i "s/\${pkgver}/$pkgver/" "$srcdir/debian/changelog"
|
||||
sed -i "s/\${pkgrel}/$pkgrel/" "$srcdir/debian/changelog"
|
||||
|
||||
# Build
|
||||
mkdir "debuild/"
|
||||
mv "$srctgz" "$srcdir/" "debuild/"
|
||||
sudo apt install devscripts build-essential dh-exec
|
||||
# --lintian-opts --profile debian: build Debian packages on Ubuntu
|
||||
# --lintian-opts --profile are for building Debian packages on Ubuntu
|
||||
cd "debuild/$srcdir/debian"
|
||||
debuild -uc -us --lintian-opts --profile debian
|
||||
cd -
|
||||
|
||||
# clean build directory
|
||||
mv "build/$package_name" "./"
|
||||
rm -rf "build"
|
||||
|
||||
# clean up from possible previous runs
|
||||
if [ -f "../server/build/$package_name" ]; then
|
||||
rm "../server/build/$package_name"
|
||||
if [ -f "../server/build/$Deb" ]; then
|
||||
rm "../server/build/$Deb"
|
||||
fi
|
||||
|
||||
mv "$package_name" "../server/build/"
|
||||
mv "debuild/$deb" "../server/build/$Deb"
|
||||
rm -rf "debuild/"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tachidesk (${version}-1) unstable; urgency=medium
|
||||
tachidesk-server (${pkgver}-${pkgrel}) unstable; urgency=medium
|
||||
|
||||
* See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Source: tachidesk
|
||||
Source: tachidesk-server
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Mahor Foruzesh <mahorforuzesh@pm.me>
|
||||
@@ -6,7 +6,7 @@ Build-Depends: debhelper-compat (= 12), dh-exec
|
||||
Standards-Version: 4.5.1
|
||||
Homepage: https://github.com/Suwayomi/Tachidesk-Server
|
||||
|
||||
Package: tachidesk
|
||||
Package: tachidesk-server
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, default-jre-headless (>= 8)
|
||||
Description: Manga Reader
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: tachidesk
|
||||
Upstream-Name: tachidesk-server
|
||||
Upstream-Contact: https://discord.gg/DDZdqZWaHA
|
||||
Source: https://github.com/Suwayomi/Tachidesk-Server
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/dh-exec
|
||||
|
||||
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk
|
||||
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk-browser
|
||||
debian/tachidesk-debug-launcher-standalone.sh => usr/bin/tachidesk-debug
|
||||
debian/tachidesk-electron-launcher-standalone.sh => usr/bin/tachidesk-electron
|
||||
Tachidesk.jar => usr/share/java/tachidesk/tachidesk.jar
|
||||
debian/tachidesk.png usr/share/pixmaps/
|
||||
debian/tachidesk.desktop usr/share/applications/
|
||||
tachidesk-server-browser-launcher.sh => usr/bin/tachidesk-server-browser
|
||||
tachidesk-server-debug-launcher.sh => usr/bin/tachidesk-server-debug
|
||||
tachidesk-server-electron-launcher.sh => usr/bin/tachidesk-server-electron
|
||||
tachidesk-server.jar usr/share/java/tachidesk-server/
|
||||
tachidesk-server.png usr/share/pixmaps/
|
||||
tachidesk-server.desktop usr/share/applications/
|
||||
|
||||
@@ -1 +1 @@
|
||||
debian/tachidesk.png
|
||||
tachidesk-server.png
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
exec /usr/bin/java -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
./jre/bin/java -jar Tachidesk.jar
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
./jre/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk.jar
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
./jre/bin/java "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron" -jar Tachidesk.jar
|
||||
2
scripts/resources/tachidesk-server-browser-launcher.sh
Normal file
2
scripts/resources/tachidesk-server-browser-launcher.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec /usr/bin/java -jar /usr/share/java/tachidesk-server/tachidesk-server.jar
|
||||
2
scripts/resources/tachidesk-debug-launcher-standalone.sh → scripts/resources/tachidesk-server-debug-launcher.sh
Executable file → Normal file
2
scripts/resources/tachidesk-debug-launcher-standalone.sh → scripts/resources/tachidesk-server-debug-launcher.sh
Executable file → Normal file
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar /usr/share/java/tachidesk-server/tachidesk-server.jar
|
||||
10
scripts/resources/tachidesk-electron-launcher-standalone.sh → scripts/resources/tachidesk-server-electron-launcher-debian.sh
Executable file → Normal file
10
scripts/resources/tachidesk-electron-launcher-standalone.sh → scripts/resources/tachidesk-server-electron-launcher-debian.sh
Executable file → Normal file
@@ -1,14 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ ! -f /usr/bin/electron ]; then
|
||||
echo "Electron executable was not found!
|
||||
echo "Electron executable was not found
|
||||
In order to run this launcher, you need Electron installed.
|
||||
|
||||
You can install it with these commands:
|
||||
sudo apt install npm
|
||||
sudo npm install electron -g
|
||||
"
|
||||
exit 1
|
||||
sudo npm install electron -g"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk/tachidesk.jar
|
||||
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk-server/tachidesk-server.jar
|
||||
3
scripts/resources/tachidesk-server-electron-launcher.sh
Normal file
3
scripts/resources/tachidesk-server-electron-launcher.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec /usr/bin/java -Dsuwayomi.tachidesk.config.server.webUIInterface=electron -Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron -jar /usr/share/java/tachidesk-server/tachidesk-server.jar
|
||||
8
scripts/resources/tachidesk-server.desktop
Normal file
8
scripts/resources/tachidesk-server.desktop
Normal file
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Tachidesk-Server
|
||||
Comment=Manga Reader
|
||||
Exec=/usr/bin/java -jar /usr/share/java/tachidesk-server/tachidesk-server.jar "\\$@"
|
||||
Icon=tachidesk-server
|
||||
Terminal=false
|
||||
Categories=Network;
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Tachidesk
|
||||
Comment=Manga Reader
|
||||
Exec=/usr/bin/java -jar /usr/share/java/tachidesk/tachidesk.jar "\\$@"
|
||||
Icon=tachidesk
|
||||
Terminal=false
|
||||
Categories=Network;
|
||||
@@ -57,11 +57,11 @@ fi
|
||||
unzip $electron -d $release_name/electron
|
||||
|
||||
# copy artifacts
|
||||
cp $jar $release_name/Tachidesk.jar
|
||||
cp $jar $release_name/Tachidesk-Server.jar
|
||||
if [ $os = linux ]; then
|
||||
cp "resources/tachidesk-browser-launcher.sh" $release_name
|
||||
cp "resources/tachidesk-debug-launcher.sh" $release_name
|
||||
cp "resources/tachidesk-electron-launcher.sh" $release_name
|
||||
cp "resources/tachidesk-server-browser-launcher.sh" $release_name
|
||||
cp "resources/tachidesk-server-debug-launcher.sh" $release_name
|
||||
cp "resources/tachidesk-server-electron-launcher.sh" $release_name
|
||||
elif [ $os = macOS ]; then
|
||||
cp "resources/Tachidesk Browser Launcher.command" $release_name
|
||||
cp "resources/Tachidesk Debug Launcher.command" $release_name
|
||||
|
||||
@@ -44,6 +44,7 @@ dependencies {
|
||||
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||
implementation("io.reactivex:rxjava:1.3.8")
|
||||
implementation("org.jsoup:jsoup:1.14.3")
|
||||
implementation("app.cash.quickjs:quickjs-jvm:0.9.2")
|
||||
|
||||
// Sort
|
||||
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package eu.kanade.tachiyomi;
|
||||
|
||||
public class BuildConfig {
|
||||
/** should be something like 74 */
|
||||
public static final int VERSION_CODE = Integer.parseInt(suwayomi.tachidesk.server.BuildConfig.REVISION.substring(1));
|
||||
|
||||
/** should be something like "0.13.1" */
|
||||
public static final String VERSION_NAME = suwayomi.tachidesk.server.BuildConfig.VERSION.substring(1);
|
||||
}
|
||||
11
server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt
Normal file
11
server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package eu.kanade.tachiyomi
|
||||
|
||||
/**
|
||||
* Used by extensions.
|
||||
*
|
||||
* @since extension-lib 1.3
|
||||
*/
|
||||
object AppInfo {
|
||||
fun getVersionCode() = BuildConfig.VERSION_CODE
|
||||
fun getVersionName() = BuildConfig.VERSION_NAME
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package eu.kanade.tachiyomi;
|
||||
|
||||
public class BuildConfig {
|
||||
public static final int VERSION_CODE = -1;
|
||||
public static final String VERSION_NAME = "stub";
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package eu.kanade.tachiyomi.network.interceptor
|
||||
|
||||
import android.os.SystemClock
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor that handles rate limiting.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* permits = 5, period = 1, unit = seconds => 5 requests per second
|
||||
* permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes
|
||||
*
|
||||
* @since extension-lib 1.3
|
||||
*
|
||||
* @param permits {Int} Number of requests allowed within a period of units.
|
||||
* @param period {Long} The limiting duration. Defaults to 1.
|
||||
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
|
||||
*/
|
||||
fun OkHttpClient.Builder.rateLimit(
|
||||
permits: Int,
|
||||
period: Long = 1,
|
||||
unit: TimeUnit = TimeUnit.SECONDS,
|
||||
) = addInterceptor(RateLimitInterceptor(permits, period, unit))
|
||||
|
||||
private class RateLimitInterceptor(
|
||||
private val permits: Int,
|
||||
period: Long,
|
||||
unit: TimeUnit,
|
||||
) : Interceptor {
|
||||
|
||||
private val requestQueue = ArrayList<Long>(permits)
|
||||
private val rateLimitMillis = unit.toMillis(period)
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
synchronized(requestQueue) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val waitTime = if (requestQueue.size < permits) {
|
||||
0
|
||||
} else {
|
||||
val oldestReq = requestQueue[0]
|
||||
val newestReq = requestQueue[permits - 1]
|
||||
|
||||
if (newestReq - oldestReq > rateLimitMillis) {
|
||||
0
|
||||
} else {
|
||||
oldestReq + rateLimitMillis - now // Remaining time
|
||||
}
|
||||
}
|
||||
|
||||
if (requestQueue.size == permits) {
|
||||
requestQueue.removeAt(0)
|
||||
}
|
||||
if (waitTime > 0) {
|
||||
requestQueue.add(now + waitTime)
|
||||
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
|
||||
} else {
|
||||
requestQueue.add(now)
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(chain.request())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package eu.kanade.tachiyomi.network.interceptor
|
||||
|
||||
import android.os.SystemClock
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor that handles given url host's rate limiting.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
|
||||
* httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com
|
||||
*
|
||||
* @since extension-lib 1.3
|
||||
*
|
||||
* @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
|
||||
* @param permits {Int} Number of requests allowed within a period of units.
|
||||
* @param period {Long} The limiting duration. Defaults to 1.
|
||||
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
|
||||
*/
|
||||
fun OkHttpClient.Builder.rateLimitHost(
|
||||
httpUrl: HttpUrl,
|
||||
permits: Int,
|
||||
period: Long = 1,
|
||||
unit: TimeUnit = TimeUnit.SECONDS,
|
||||
) = addInterceptor(SpecificHostRateLimitInterceptor(httpUrl, permits, period, unit))
|
||||
|
||||
class SpecificHostRateLimitInterceptor(
|
||||
httpUrl: HttpUrl,
|
||||
private val permits: Int,
|
||||
period: Long,
|
||||
unit: TimeUnit,
|
||||
) : Interceptor {
|
||||
|
||||
private val requestQueue = ArrayList<Long>(permits)
|
||||
private val rateLimitMillis = unit.toMillis(period)
|
||||
private val host = httpUrl.host
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (chain.request().url.host != host) {
|
||||
return chain.proceed(chain.request())
|
||||
}
|
||||
synchronized(requestQueue) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val waitTime = if (requestQueue.size < permits) {
|
||||
0
|
||||
} else {
|
||||
val oldestReq = requestQueue[0]
|
||||
val newestReq = requestQueue[permits - 1]
|
||||
|
||||
if (newestReq - oldestReq > rateLimitMillis) {
|
||||
0
|
||||
} else {
|
||||
oldestReq + rateLimitMillis - now // Remaining time
|
||||
}
|
||||
}
|
||||
|
||||
if (requestQueue.size == permits) {
|
||||
requestQueue.removeAt(0)
|
||||
}
|
||||
if (waitTime > 0) {
|
||||
requestQueue.add(now + waitTime)
|
||||
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
|
||||
} else {
|
||||
requestQueue.add(now)
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(chain.request())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi.source
|
||||
|
||||
/**
|
||||
* A source that explicitly doesn't require traffic considerations.
|
||||
*
|
||||
* This typically applies for self-hosted sources.
|
||||
*/
|
||||
interface UnmeteredSource
|
||||
@@ -55,6 +55,9 @@ interface SManga : Serializable {
|
||||
const val ONGOING = 1
|
||||
const val COMPLETED = 2
|
||||
const val LICENSED = 3
|
||||
const val PUBLISHING_FINISHED = 4
|
||||
const val CANCELLED = 5
|
||||
const val ON_HIATUS = 6
|
||||
|
||||
fun create(): SManga {
|
||||
return SMangaImpl()
|
||||
|
||||
@@ -45,7 +45,7 @@ object MangaAPI {
|
||||
post("{sourceId}/preferences", SourceController::setPreference)
|
||||
|
||||
get("{sourceId}/filters", SourceController::getFilters)
|
||||
post("{sourceId}/filters", SourceController::setFilter)
|
||||
post("{sourceId}/filters", SourceController::setFilters)
|
||||
|
||||
get("{sourceId}/search", SourceController::searchSingle)
|
||||
// get("all/search", SourceController::searchGlobal) // TODO
|
||||
|
||||
@@ -8,6 +8,11 @@ package suwayomi.tachidesk.manga.controller
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import io.javalin.http.Context
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.MangaList
|
||||
import suwayomi.tachidesk.manga.impl.Search
|
||||
import suwayomi.tachidesk.manga.impl.Search.FilterChange
|
||||
@@ -24,7 +29,7 @@ object SourceController {
|
||||
/** fetch source with id `sourceId` */
|
||||
fun retrieve(ctx: Context) {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
ctx.json(Source.getSource(sourceId))
|
||||
ctx.json(Source.getSource(sourceId)!!)
|
||||
}
|
||||
|
||||
/** popular mangas from source with id `sourceId` */
|
||||
@@ -69,10 +74,16 @@ object SourceController {
|
||||
ctx.json(Search.getFilterList(sourceId, reset))
|
||||
}
|
||||
|
||||
/** set one filter of source with id `sourceId` */
|
||||
fun setFilter(ctx: Context) {
|
||||
private val json by DI.global.instance<Json>()
|
||||
|
||||
/** change filters of source with id `sourceId` */
|
||||
fun setFilters(ctx: Context) {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val filterChange = ctx.bodyAsClass(FilterChange::class.java)
|
||||
val filterChange = try {
|
||||
json.decodeFromString<List<FilterChange>>(ctx.body())
|
||||
} catch (e: Exception) {
|
||||
listOf(json.decodeFromString<FilterChange>(ctx.body()))
|
||||
}
|
||||
|
||||
ctx.json(Search.setFilter(sourceId, filterChange))
|
||||
}
|
||||
|
||||
@@ -201,8 +201,10 @@ object Chapter {
|
||||
val chapterId =
|
||||
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
|
||||
.first()[ChapterTable.id].value
|
||||
val meta =
|
||||
transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } }.firstOrNull()
|
||||
val meta = transaction {
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
}.firstOrNull()
|
||||
|
||||
if (meta == null) {
|
||||
ChapterMetaTable.insert {
|
||||
it[ChapterMetaTable.key] = key
|
||||
@@ -210,7 +212,7 @@ object Chapter {
|
||||
it[ChapterMetaTable.ref] = chapterId
|
||||
}
|
||||
} else {
|
||||
ChapterMetaTable.update {
|
||||
ChapterMetaTable.update({ (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }) {
|
||||
it[ChapterMetaTable.value] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ package suwayomi.tachidesk.manga.impl
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.select
|
||||
@@ -23,7 +25,9 @@ import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||
import suwayomi.tachidesk.manga.impl.Source.getSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.source.StubSource
|
||||
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.ImageUtil
|
||||
@@ -34,6 +38,7 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -50,30 +55,10 @@ object Manga {
|
||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
return if (mangaEntry[MangaTable.initialized] && !onlineFetch) {
|
||||
MangaDataClass(
|
||||
mangaId,
|
||||
mangaEntry[MangaTable.sourceReference].toString(),
|
||||
|
||||
mangaEntry[MangaTable.url],
|
||||
mangaEntry[MangaTable.title],
|
||||
proxyThumbnailUrl(mangaId),
|
||||
|
||||
true,
|
||||
|
||||
mangaEntry[MangaTable.artist],
|
||||
mangaEntry[MangaTable.author],
|
||||
mangaEntry[MangaTable.description],
|
||||
mangaEntry[MangaTable.genre].toGenreList(),
|
||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
mangaEntry[MangaTable.inLibrary],
|
||||
mangaEntry[MangaTable.inLibraryAt],
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
getMangaMetaMap(mangaId),
|
||||
mangaEntry[MangaTable.realUrl],
|
||||
false
|
||||
)
|
||||
getMangaDataClass(mangaId, mangaEntry)
|
||||
} else { // initialize manga
|
||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
|
||||
?: return getMangaDataClass(mangaId, mangaEntry)
|
||||
val sManga = SManga.create().apply {
|
||||
url = mangaEntry[MangaTable.url]
|
||||
title = mangaEntry[MangaTable.title]
|
||||
@@ -135,6 +120,29 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass(
|
||||
mangaId,
|
||||
mangaEntry[MangaTable.sourceReference].toString(),
|
||||
|
||||
mangaEntry[MangaTable.url],
|
||||
mangaEntry[MangaTable.title],
|
||||
proxyThumbnailUrl(mangaId),
|
||||
|
||||
true,
|
||||
|
||||
mangaEntry[MangaTable.artist],
|
||||
mangaEntry[MangaTable.author],
|
||||
mangaEntry[MangaTable.description],
|
||||
mangaEntry[MangaTable.genre].toGenreList(),
|
||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
mangaEntry[MangaTable.inLibrary],
|
||||
mangaEntry[MangaTable.inLibraryAt],
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
getMangaMetaMap(mangaId),
|
||||
mangaEntry[MangaTable.realUrl],
|
||||
false
|
||||
)
|
||||
|
||||
fun getMangaMetaMap(manga: Int): Map<String, String> {
|
||||
return transaction {
|
||||
MangaMetaTable.select { MangaMetaTable.ref eq manga }
|
||||
@@ -146,8 +154,10 @@ object Manga {
|
||||
transaction {
|
||||
val manga = MangaTable.select { MangaTable.id eq mangaId }
|
||||
.first()[MangaTable.id]
|
||||
val meta =
|
||||
transaction { MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) } }.firstOrNull()
|
||||
val meta = transaction {
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) }
|
||||
}.firstOrNull()
|
||||
|
||||
if (meta == null) {
|
||||
MangaMetaTable.insert {
|
||||
it[MangaMetaTable.key] = key
|
||||
@@ -155,7 +165,7 @@ object Manga {
|
||||
it[MangaMetaTable.ref] = manga
|
||||
}
|
||||
} else {
|
||||
MangaMetaTable.update {
|
||||
MangaMetaTable.update({ (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) }) {
|
||||
it[MangaMetaTable.value] = value
|
||||
}
|
||||
}
|
||||
@@ -163,6 +173,7 @@ object Manga {
|
||||
}
|
||||
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
suspend fun getMangaThumbnail(mangaId: Int, useCache: Boolean): Pair<InputStream, String> {
|
||||
val saveDir = applicationDirs.thumbnailsRoot
|
||||
val fileName = mangaId.toString()
|
||||
@@ -176,10 +187,12 @@ object Manga {
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!!
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
}[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException()
|
||||
throw NullPointerException("No thumbnail found")
|
||||
}
|
||||
|
||||
source.client.newCall(
|
||||
@@ -199,6 +212,13 @@ object Manga {
|
||||
?: "image/jpeg"
|
||||
imageFile.inputStream() to contentType
|
||||
}
|
||||
is StubSource -> getImageResponse(saveDir, fileName, useCache) {
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
?: throw NullPointerException("No thumbnail found")
|
||||
network.client.newCall(
|
||||
GET(thumbnailUrl)
|
||||
).await()
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown source")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import io.javalin.plugin.json.JsonMapper
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
@@ -80,37 +81,42 @@ object Search {
|
||||
val filter: Filter<*>,
|
||||
)
|
||||
|
||||
fun setFilter(sourceId: Long, change: FilterChange) {
|
||||
fun setFilter(sourceId: Long, changes: List<FilterChange>) {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val filterList = getFilterListOf(source, false)
|
||||
|
||||
when (val filter = filterList[change.position]) {
|
||||
is Filter.Header -> {
|
||||
// NOOP
|
||||
}
|
||||
is Filter.Separator -> {
|
||||
// NOOP
|
||||
}
|
||||
is Filter.Select<*> -> filter.state = change.state.toInt()
|
||||
is Filter.Text -> filter.state = change.state
|
||||
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict()
|
||||
is Filter.TriState -> filter.state = change.state.toInt()
|
||||
is Filter.Group<*> -> {
|
||||
val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java)
|
||||
changes.forEach { change ->
|
||||
when (val filter = filterList[change.position]) {
|
||||
is Filter.Header -> {
|
||||
// NOOP
|
||||
}
|
||||
is Filter.Separator -> {
|
||||
// NOOP
|
||||
}
|
||||
is Filter.Select<*> -> filter.state = change.state.toInt()
|
||||
is Filter.Text -> filter.state = change.state
|
||||
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict()
|
||||
is Filter.TriState -> filter.state = change.state.toInt()
|
||||
is Filter.Group<*> -> {
|
||||
val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java)
|
||||
|
||||
when (val groupFilter = filter.state[groupChange.position]) {
|
||||
is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict()
|
||||
is Filter.TriState -> groupFilter.state = groupChange.state.toInt()
|
||||
is Filter.Text -> groupFilter.state = groupChange.state
|
||||
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
|
||||
when (val groupFilter = filter.state[groupChange.position]) {
|
||||
is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict()
|
||||
is Filter.TriState -> groupFilter.state = groupChange.state.toInt()
|
||||
is Filter.Text -> groupFilter.state = groupChange.state
|
||||
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
|
||||
}
|
||||
}
|
||||
is Filter.Sort -> {
|
||||
filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
|
||||
}
|
||||
}
|
||||
is Filter.Sort -> filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private val jsonMapper by DI.global.instance<JsonMapper>()
|
||||
|
||||
@Serializable
|
||||
data class FilterChange(
|
||||
val position: Int,
|
||||
val state: String
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
|
||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||
@@ -36,8 +36,8 @@ object Source {
|
||||
|
||||
fun getSourceList(): List<SourceDataClass> {
|
||||
return transaction {
|
||||
SourceTable.selectAll().map {
|
||||
val catalogueSource = getCatalogueSourceOrStub(it[SourceTable.id].value)
|
||||
SourceTable.selectAll().mapNotNull {
|
||||
val catalogueSource = getCatalogueSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null
|
||||
val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()
|
||||
|
||||
SourceDataClass(
|
||||
@@ -54,27 +54,23 @@ object Source {
|
||||
}
|
||||
}
|
||||
|
||||
fun getSource(sourceId: Long): SourceDataClass { // all the data extracted fresh form the source instance
|
||||
fun getSource(sourceId: Long): SourceDataClass? { // all the data extracted fresh form the source instance
|
||||
return transaction {
|
||||
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||
val catalogueSource = source?.let { getCatalogueSource(sourceId) }
|
||||
val extension = source?.let {
|
||||
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
|
||||
}
|
||||
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null
|
||||
val catalogueSource = getCatalogueSourceOrNull(sourceId) ?: return@transaction null
|
||||
val extension = ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
|
||||
|
||||
SourceDataClass(
|
||||
sourceId.toString(),
|
||||
source?.get(SourceTable.name),
|
||||
source?.get(SourceTable.lang),
|
||||
source?.let {
|
||||
getExtensionIconUrl(
|
||||
extension!![ExtensionTable.apkName]
|
||||
)
|
||||
},
|
||||
catalogueSource?.supportsLatest,
|
||||
catalogueSource?.let { it is ConfigurableSource },
|
||||
source?.get(SourceTable.isNsfw),
|
||||
catalogueSource?.toString()
|
||||
source[SourceTable.name],
|
||||
source[SourceTable.lang],
|
||||
getExtensionIconUrl(
|
||||
extension[ExtensionTable.apkName]
|
||||
),
|
||||
catalogueSource.supportsLatest,
|
||||
catalogueSource is ConfigurableSource,
|
||||
source[SourceTable.isNsfw],
|
||||
catalogueSource.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ object PackageTools {
|
||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
|
||||
const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
||||
const val LIB_VERSION_MIN = 1.2
|
||||
const val LIB_VERSION_MAX = 1.2
|
||||
const val LIB_VERSION_MAX = 1.3
|
||||
|
||||
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
|
||||
private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key
|
||||
|
||||
@@ -26,7 +26,7 @@ object GetCatalogueSource {
|
||||
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>()
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
fun getCatalogueSource(sourceId: Long): CatalogueSource? {
|
||||
private fun getCatalogueSource(sourceId: Long): CatalogueSource? {
|
||||
val cachedResult: CatalogueSource? = sourceCache[sourceId]
|
||||
if (cachedResult != null) {
|
||||
return cachedResult
|
||||
@@ -56,8 +56,12 @@ object GetCatalogueSource {
|
||||
return sourceCache[sourceId]!!
|
||||
}
|
||||
|
||||
fun getCatalogueSourceOrNull(sourceId: Long): CatalogueSource? {
|
||||
return runCatching { getCatalogueSource(sourceId) }.getOrNull()
|
||||
}
|
||||
|
||||
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource {
|
||||
return getCatalogueSource(sourceId) ?: StubSource(sourceId)
|
||||
return getCatalogueSourceOrNull(sourceId) ?: StubSource(sourceId)
|
||||
}
|
||||
|
||||
fun registerCatalogueSource(sourcePair: Pair<Long, CatalogueSource>) {
|
||||
|
||||
@@ -11,19 +11,19 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
|
||||
data class SourceDataClass(
|
||||
val id: String,
|
||||
val name: String?,
|
||||
val lang: String?,
|
||||
val iconUrl: String?,
|
||||
val name: String,
|
||||
val lang: String,
|
||||
val iconUrl: String,
|
||||
|
||||
/** The Source provides a latest listing */
|
||||
val supportsLatest: Boolean?,
|
||||
val supportsLatest: Boolean,
|
||||
|
||||
/** The Source implements [ConfigurableSource] */
|
||||
val isConfigurable: Boolean?,
|
||||
val isConfigurable: Boolean,
|
||||
|
||||
/** The Source class has a @Nsfw annotation */
|
||||
val isNsfw: Boolean?,
|
||||
val isNsfw: Boolean,
|
||||
|
||||
/** A nicer version of [name] */
|
||||
val displayName: String?,
|
||||
val displayName: String,
|
||||
)
|
||||
|
||||
@@ -66,7 +66,10 @@ enum class MangaStatus(val value: Int) {
|
||||
UNKNOWN(0),
|
||||
ONGOING(1),
|
||||
COMPLETED(2),
|
||||
LICENSED(3);
|
||||
LICENSED(3),
|
||||
PUBLISHING_FINISHED(4),
|
||||
CANCELLED(5),
|
||||
ON_HIATUS(6);
|
||||
|
||||
companion object {
|
||||
fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN
|
||||
|
||||
@@ -54,6 +54,22 @@ object JavalinSetup {
|
||||
}
|
||||
|
||||
config.enableCorsForAllOrigins()
|
||||
|
||||
config.accessManager { handler, ctx, _ ->
|
||||
fun basicAuthCredentialsValid(): Boolean {
|
||||
val (username, password) = ctx.basicAuthCredentials()
|
||||
return username == serverConfig.basicAuthUsername && password == serverConfig.basicAuthPassword
|
||||
}
|
||||
|
||||
if (serverConfig.authType != "none") {
|
||||
if (serverConfig.authType == "basicAuth" && !(ctx.basicAuthCredentialsExist() && basicAuthCredentialsValid())) {
|
||||
ctx.header("WWW-Authenticate", "Basic")
|
||||
ctx.status(401).json("Unauthorized")
|
||||
}
|
||||
} else {
|
||||
handler.handle(ctx)
|
||||
}
|
||||
}
|
||||
}.events { event ->
|
||||
event.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled) {
|
||||
@@ -83,18 +99,6 @@ object JavalinSetup {
|
||||
ctx.result(e.message ?: "Internal Server Error")
|
||||
}
|
||||
|
||||
app.before { ctx ->
|
||||
fun credentialsValid(): Boolean {
|
||||
val (username, password) = ctx.basicAuthCredentials()
|
||||
return username == serverConfig.basicAuthUsername && password == serverConfig.basicAuthPassword
|
||||
}
|
||||
|
||||
if (serverConfig.basicAuthEnabled && !(ctx.basicAuthCredentialsExist() && credentialsValid())) {
|
||||
ctx.header("WWW-Authenticate", "Basic")
|
||||
ctx.status(401).json("Unauthorized")
|
||||
}
|
||||
}
|
||||
|
||||
app.routes {
|
||||
path("api/v1/") {
|
||||
GlobalAPI.defineEndpoints()
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.typesafe.config.Config
|
||||
import xyz.nulldev.ts.config.GlobalConfigManager
|
||||
import xyz.nulldev.ts.config.SystemPropertyOverridableConfigModule
|
||||
import xyz.nulldev.ts.config.debugLogsEnabled
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
private const val MODULE_NAME = "server"
|
||||
class ServerConfig(config: Config, moduleName: String = MODULE_NAME) : SystemPropertyOverridableConfigModule(config, moduleName) {
|
||||
@@ -34,6 +35,15 @@ class ServerConfig(config: Config, moduleName: String = MODULE_NAME) : SystemPro
|
||||
val electronPath: String by overridableConfig
|
||||
|
||||
// Authentication
|
||||
val authType: String by object {
|
||||
operator fun <R> getValue(thisRef: R, property: KProperty<*>): String {
|
||||
val propValue: String = overridableConfig.getValue(thisRef, property)
|
||||
if (basicAuthEnabled) {
|
||||
return "basicAuth"
|
||||
}
|
||||
return propValue
|
||||
}
|
||||
}
|
||||
val basicAuthEnabled: Boolean by overridableConfig
|
||||
val basicAuthUsername: String by overridableConfig
|
||||
val basicAuthPassword: String by overridableConfig
|
||||
|
||||
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import io.javalin.plugin.json.JavalinJackson
|
||||
import io.javalin.plugin.json.JsonMapper
|
||||
import kotlinx.serialization.json.Json
|
||||
import mu.KotlinLogging
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.bind
|
||||
@@ -59,6 +60,7 @@ fun applicationSetup() {
|
||||
bind<ApplicationDirs>() with singleton { applicationDirs }
|
||||
bind<IUpdater>() with singleton { Updater() }
|
||||
bind<JsonMapper>() with singleton { JavalinJackson() }
|
||||
bind<Json>() with singleton { Json { ignoreUnknownKeys = true } }
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ server.webUIInterface = "browser" # "browser" or "electron"
|
||||
server.electronPath = ""
|
||||
|
||||
# Authentication
|
||||
server.basicAuthEnabled = false
|
||||
server.authType = "none" # "none" or "basicAuth" or "token"
|
||||
server.basicAuthEnabled = false # This is deprecated, use server.authType
|
||||
server.basicAuthUsername = ""
|
||||
server.basicAuthPassword = ""
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||
import suwayomi.tachidesk.server.applicationSetup
|
||||
import suwayomi.tachidesk.test.BASE_PATH
|
||||
@@ -72,7 +72,7 @@ class TestExtensionCompatibility {
|
||||
}
|
||||
}
|
||||
}
|
||||
sources = getSourceList().map { getCatalogueSource(it.id.toLong())!! as HttpSource }
|
||||
sources = getSourceList().map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource }
|
||||
}
|
||||
setLoggingEnabled(true)
|
||||
File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
||||
|
||||
Reference in New Issue
Block a user