Compare commits

..

16 Commits

Author SHA1 Message Date
Aria Moradi
53c3ac5676 token auth 2022-04-16 18:26:44 +04:30
Aria Moradi
a26b8ecca0 v0.6.3 2022-04-07 15:54:42 +04:30
Aria Moradi
5a32ccfa7a fix auth not actually blocking requests (#333) 2022-04-06 21:30:38 +04:30
Mitchell Syer
f51818b157 Add QuickJS, replaces Duktape for Extensions Lib 1.3 (#331) 2022-04-02 19:43:45 +04:30
Mitchell Syer
31a624db51 Add last bit of code needed for Extensions Lib 1.3 (#330) 2022-04-02 05:02:26 +04:30
DattatreyaReddy Panta
f045b18762 update description for Tachidesk-Sorayomi (#326)
* added Tachidesk-Flutter to readme

* Updated Description for Tachidesk-Sorayomi
2022-03-27 16:41:35 +04:30
Mitchell Syer
f5006cac7d Add thumbnail support for stub sources (#320) 2022-03-22 15:51:58 +04:30
Mitchell Syer
152b193ad5 Improve source handling, fix errors with uninitialized mangas in broken sources (#319) 2022-03-22 15:51:07 +04:30
Mitchell Syer
a27af0b642 Fix sources list of one source throws an exception (#308) 2022-03-20 19:24:09 +03:30
Aria Moradi
44ffed3f7c add support for tachiyomi extensions Lib 1.3 (#316)
* closes #315

* provide real values

* add support for tachiyomi extensions lib 1.3
2022-03-19 02:36:42 +03:30
Aria Moradi
fa035ad9be fix meta update changing all keys (#314) 2022-03-18 00:14:22 +03:30
Mahor
186ace4343 Update README.md (#305)
* Update README.md

* Update README.md again
2022-03-05 09:38:20 +03:30
Aria Moradi
8fb1a0bb1f fix filterlist bugs (#306) 2022-03-05 01:13:48 +03:30
Aria Moradi
05513bf8b9 support array filter changes (#304)
* support array filter changes

* typo

* better formating
2022-03-05 00:06:55 +03:30
Aria Moradi
858784857e v0.6.2 2022-03-04 19:03:23 +03:30
Mahor
291a23949a Refactor debian-packager.sh, rename launcher scripts (#303)
* Improve windows-bundler.sh

* Overhaul debian-packager.sh. Rename base package name to tachidesk-server

* Add -electron-launcher-debian.sh
2022-03-03 13:26:56 +03:30
43 changed files with 493 additions and 190 deletions

View File

@@ -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 # Server: v0.6.1 + WebUI: r911
## TL;DR ## TL;DR
- msi and deb packages thanks to @mahor1221 - msi and deb packages thanks to @mahor1221

View File

@@ -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-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-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-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 ##### Inctive/Abandoned Cients
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development. - [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. - [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. **Node:** Linux launcher scripts are named a bit differently but work the same.
### Windows ### 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. Unzip the downloaded file and double click on one of the launcher scripts.
### macOS ### 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. Unzip the downloaded file and double click on one of the launcher scripts.
### GNU/Linux ### 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. `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 ## Other methods of getting Tachidesk
### Arch Linux ### Arch Linux
You can install Tachidesk from the AUR You can install Tachidesk from the AUR:
``` ```
yay -S tachidesk yay -S tachidesk
``` ```
### Ubuntu-based distributions ### Debian/Ubuntu
More information can be found on the [PPA's page](https://launchpad.net/~suwayomi/+archive/ubuntu/tachidesk). 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 update
sudo apt install tachidesk sudo apt install tachidesk-server
``` ```
### Docker ### Docker

View File

@@ -12,9 +12,9 @@ const val kotlinVersion = "1.6.10"
const val MainClass = "suwayomi.tachidesk.MainKt" const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release // 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 // counts commits on the master branch
val tachideskRevision = runCatching { val tachideskRevision = runCatching {

View File

@@ -6,43 +6,46 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # 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) jar=$(ls ../server/build/*.jar | tail -n1)
release_ver=$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v) pkgver="$(tmp="${jar%-*}" && echo "${tmp##*-}" | tr -d v)"
orig_dir="tachidesk-$release_ver" # dir uses hyphen "-" pkgrel=1
orig_tar_gz="tachidesk_$release_ver.orig.tar.gz" # orig file uses underscore "_"
package_name="tachidesk_$release_ver-1_all.deb"
# copy artifacts srcdir="$pkgname-$pkgver" # uses hyphen "-"
mkdir "$orig_dir" srctgz="${pkgname}_$pkgver.orig.tar.gz" # uses underscore "_"
cp "$jar" "$orig_dir/Tachidesk.jar" deb="${pkgname}_$pkgver-${pkgrel}_all.deb"
cp -r "resources/debian" "$orig_dir" Deb="${PkgName}_$pkgver-${pkgrel}_all.deb"
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"
# prepare # Prepare
tar cvzf "$orig_tar_gz" "$orig_dir/Tachidesk.jar" mkdir "$srcdir/"
sed -i "s/\${version}/$release_ver/" "$orig_dir/debian/changelog" 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 GZIP=-9 tar -cvzf "$srctgz" "$srcdir/"
mkdir "build"
mv $orig_dir $orig_tar_gz "build/" cp -r "resources/debian" "$srcdir/"
cd "build/$orig_dir/debian" 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 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 debuild -uc -us --lintian-opts --profile debian
cd - cd -
# clean build directory
mv "build/$package_name" "./"
rm -rf "build"
# clean up from possible previous runs # clean up from possible previous runs
if [ -f "../server/build/$package_name" ]; then if [ -f "../server/build/$Deb" ]; then
rm "../server/build/$package_name" rm "../server/build/$Deb"
fi fi
mv "$package_name" "../server/build/" mv "debuild/$deb" "../server/build/$Deb"
rm -rf "debuild/"

View File

@@ -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 * See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server

View File

@@ -1,4 +1,4 @@
Source: tachidesk Source: tachidesk-server
Section: web Section: web
Priority: optional Priority: optional
Maintainer: Mahor Foruzesh <mahorforuzesh@pm.me> Maintainer: Mahor Foruzesh <mahorforuzesh@pm.me>
@@ -6,7 +6,7 @@ Build-Depends: debhelper-compat (= 12), dh-exec
Standards-Version: 4.5.1 Standards-Version: 4.5.1
Homepage: https://github.com/Suwayomi/Tachidesk-Server Homepage: https://github.com/Suwayomi/Tachidesk-Server
Package: tachidesk Package: tachidesk-server
Architecture: all Architecture: all
Depends: ${misc:Depends}, default-jre-headless (>= 8) Depends: ${misc:Depends}, default-jre-headless (>= 8)
Description: Manga Reader Description: Manga Reader

View File

@@ -1,5 +1,5 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 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 Upstream-Contact: https://discord.gg/DDZdqZWaHA
Source: https://github.com/Suwayomi/Tachidesk-Server Source: https://github.com/Suwayomi/Tachidesk-Server

View File

@@ -1,9 +1,8 @@
#!/usr/bin/dh-exec #!/usr/bin/dh-exec
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk tachidesk-server-browser-launcher.sh => usr/bin/tachidesk-server-browser
debian/tachidesk-browser-launcher-standalone.sh => usr/bin/tachidesk-browser tachidesk-server-debug-launcher.sh => usr/bin/tachidesk-server-debug
debian/tachidesk-debug-launcher-standalone.sh => usr/bin/tachidesk-debug tachidesk-server-electron-launcher.sh => usr/bin/tachidesk-server-electron
debian/tachidesk-electron-launcher-standalone.sh => usr/bin/tachidesk-electron tachidesk-server.jar usr/share/java/tachidesk-server/
Tachidesk.jar => usr/share/java/tachidesk/tachidesk.jar tachidesk-server.png usr/share/pixmaps/
debian/tachidesk.png usr/share/pixmaps/ tachidesk-server.desktop usr/share/applications/
debian/tachidesk.desktop usr/share/applications/

View File

@@ -1 +1 @@
debian/tachidesk.png tachidesk-server.png

View File

@@ -1,2 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/tachidesk/tachidesk.jar

View File

@@ -1,3 +0,0 @@
#!/bin/sh
./jre/bin/java -jar Tachidesk.jar

View File

@@ -1,3 +0,0 @@
#!/bin/sh
./jre/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk.jar

View File

@@ -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

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/tachidesk-server/tachidesk-server.jar

View File

@@ -1,2 +1,2 @@
#!/bin/sh #!/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

View File

@@ -1,14 +1,12 @@
#!/bin/sh #!/bin/sh
if [ ! -f /usr/bin/electron ]; then 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. In order to run this launcher, you need Electron installed.
You can install it with these commands: You can install it with these commands:
sudo apt install npm sudo apt install npm
sudo npm install electron -g sudo npm install electron -g"
"
exit 1 exit 1
fi 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

View 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

View 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;

View File

@@ -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;

View File

@@ -57,11 +57,11 @@ fi
unzip $electron -d $release_name/electron unzip $electron -d $release_name/electron
# copy artifacts # copy artifacts
cp $jar $release_name/Tachidesk.jar cp $jar $release_name/Tachidesk-Server.jar
if [ $os = linux ]; then if [ $os = linux ]; then
cp "resources/tachidesk-browser-launcher.sh" $release_name cp "resources/tachidesk-server-browser-launcher.sh" $release_name
cp "resources/tachidesk-debug-launcher.sh" $release_name cp "resources/tachidesk-server-debug-launcher.sh" $release_name
cp "resources/tachidesk-electron-launcher.sh" $release_name cp "resources/tachidesk-server-electron-launcher.sh" $release_name
elif [ $os = macOS ]; then elif [ $os = macOS ]; then
cp "resources/Tachidesk Browser Launcher.command" $release_name cp "resources/Tachidesk Browser Launcher.command" $release_name
cp "resources/Tachidesk Debug Launcher.command" $release_name cp "resources/Tachidesk Debug Launcher.command" $release_name

View File

@@ -44,6 +44,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion") implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("io.reactivex:rxjava:1.3.8") implementation("io.reactivex:rxjava:1.3.8")
implementation("org.jsoup:jsoup:1.14.3") implementation("org.jsoup:jsoup:1.14.3")
implementation("app.cash.quickjs:quickjs-jvm:0.9.2")
// Sort // Sort
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1") implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")

View File

@@ -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);
}

View 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
}

View File

@@ -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";
}

View File

@@ -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())
}
}

View File

@@ -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())
}
}

View File

@@ -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

View File

@@ -55,6 +55,9 @@ interface SManga : Serializable {
const val ONGOING = 1 const val ONGOING = 1
const val COMPLETED = 2 const val COMPLETED = 2
const val LICENSED = 3 const val LICENSED = 3
const val PUBLISHING_FINISHED = 4
const val CANCELLED = 5
const val ON_HIATUS = 6
fun create(): SManga { fun create(): SManga {
return SMangaImpl() return SMangaImpl()

View File

@@ -45,7 +45,7 @@ object MangaAPI {
post("{sourceId}/preferences", SourceController::setPreference) post("{sourceId}/preferences", SourceController::setPreference)
get("{sourceId}/filters", SourceController::getFilters) get("{sourceId}/filters", SourceController::getFilters)
post("{sourceId}/filters", SourceController::setFilter) post("{sourceId}/filters", SourceController::setFilters)
get("{sourceId}/search", SourceController::searchSingle) get("{sourceId}/search", SourceController::searchSingle)
// get("all/search", SourceController::searchGlobal) // TODO // get("all/search", SourceController::searchGlobal) // TODO

View File

@@ -8,6 +8,11 @@ package suwayomi.tachidesk.manga.controller
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import io.javalin.http.Context 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.MangaList
import suwayomi.tachidesk.manga.impl.Search import suwayomi.tachidesk.manga.impl.Search
import suwayomi.tachidesk.manga.impl.Search.FilterChange import suwayomi.tachidesk.manga.impl.Search.FilterChange
@@ -24,7 +29,7 @@ object SourceController {
/** fetch source with id `sourceId` */ /** fetch source with id `sourceId` */
fun retrieve(ctx: Context) { fun retrieve(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
ctx.json(Source.getSource(sourceId)) ctx.json(Source.getSource(sourceId)!!)
} }
/** popular mangas from source with id `sourceId` */ /** popular mangas from source with id `sourceId` */
@@ -69,10 +74,16 @@ object SourceController {
ctx.json(Search.getFilterList(sourceId, reset)) ctx.json(Search.getFilterList(sourceId, reset))
} }
/** set one filter of source with id `sourceId` */ private val json by DI.global.instance<Json>()
fun setFilter(ctx: Context) {
/** change filters of source with id `sourceId` */
fun setFilters(ctx: Context) {
val sourceId = ctx.pathParam("sourceId").toLong() 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)) ctx.json(Search.setFilter(sourceId, filterChange))
} }

View File

@@ -201,8 +201,10 @@ object Chapter {
val chapterId = val chapterId =
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) } ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
.first()[ChapterTable.id].value .first()[ChapterTable.id].value
val meta = val meta = transaction {
transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } }.firstOrNull() ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
}.firstOrNull()
if (meta == null) { if (meta == null) {
ChapterMetaTable.insert { ChapterMetaTable.insert {
it[ChapterMetaTable.key] = key it[ChapterMetaTable.key] = key
@@ -210,7 +212,7 @@ object Chapter {
it[ChapterMetaTable.ref] = chapterId it[ChapterMetaTable.ref] = chapterId
} }
} else { } else {
ChapterMetaTable.update { ChapterMetaTable.update({ (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }) {
it[ChapterMetaTable.value] = value it[ChapterMetaTable.value] = value
} }
} }

View File

@@ -8,9 +8,11 @@ package suwayomi.tachidesk.manga.impl
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select 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.Source.getSource
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.network.await 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.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.clearCachedImage
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil 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.MangaStatus
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.ApplicationDirs import suwayomi.tachidesk.server.ApplicationDirs
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -50,30 +55,10 @@ object Manga {
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
return if (mangaEntry[MangaTable.initialized] && !onlineFetch) { return if (mangaEntry[MangaTable.initialized] && !onlineFetch) {
MangaDataClass( getMangaDataClass(mangaId, mangaEntry)
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
)
} else { // initialize manga } else { // initialize manga
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
?: return getMangaDataClass(mangaId, mangaEntry)
val sManga = SManga.create().apply { val sManga = SManga.create().apply {
url = mangaEntry[MangaTable.url] url = mangaEntry[MangaTable.url]
title = mangaEntry[MangaTable.title] 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> { fun getMangaMetaMap(manga: Int): Map<String, String> {
return transaction { return transaction {
MangaMetaTable.select { MangaMetaTable.ref eq manga } MangaMetaTable.select { MangaMetaTable.ref eq manga }
@@ -146,8 +154,10 @@ object Manga {
transaction { transaction {
val manga = MangaTable.select { MangaTable.id eq mangaId } val manga = MangaTable.select { MangaTable.id eq mangaId }
.first()[MangaTable.id] .first()[MangaTable.id]
val meta = val meta = transaction {
transaction { MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) } }.firstOrNull() MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) }
}.firstOrNull()
if (meta == null) { if (meta == null) {
MangaMetaTable.insert { MangaMetaTable.insert {
it[MangaMetaTable.key] = key it[MangaMetaTable.key] = key
@@ -155,7 +165,7 @@ object Manga {
it[MangaMetaTable.ref] = manga it[MangaMetaTable.ref] = manga
} }
} else { } else {
MangaMetaTable.update { MangaMetaTable.update({ (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) }) {
it[MangaMetaTable.value] = value it[MangaMetaTable.value] = value
} }
} }
@@ -163,6 +173,7 @@ object Manga {
} }
private val applicationDirs by DI.global.instance<ApplicationDirs>() private val applicationDirs by DI.global.instance<ApplicationDirs>()
private val network: NetworkHelper by injectLazy()
suspend fun getMangaThumbnail(mangaId: Int, useCache: Boolean): Pair<InputStream, String> { suspend fun getMangaThumbnail(mangaId: Int, useCache: Boolean): Pair<InputStream, String> {
val saveDir = applicationDirs.thumbnailsRoot val saveDir = applicationDirs.thumbnailsRoot
val fileName = mangaId.toString() val fileName = mangaId.toString()
@@ -176,10 +187,12 @@ object Manga {
?: if (!mangaEntry[MangaTable.initialized]) { ?: if (!mangaEntry[MangaTable.initialized]) {
// initialize then try again // initialize then try again
getManga(mangaId) 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 { } else {
// source provides no thumbnail url for this manga // source provides no thumbnail url for this manga
throw NullPointerException() throw NullPointerException("No thumbnail found")
} }
source.client.newCall( source.client.newCall(
@@ -199,6 +212,13 @@ object Manga {
?: "image/jpeg" ?: "image/jpeg"
imageFile.inputStream() to contentType 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") else -> throw IllegalArgumentException("Unknown source")
} }
} }

View File

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import io.javalin.plugin.json.JsonMapper import io.javalin.plugin.json.JsonMapper
import kotlinx.serialization.Serializable
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.conf.global import org.kodein.di.conf.global
import org.kodein.di.instance import org.kodein.di.instance
@@ -80,10 +81,11 @@ object Search {
val filter: Filter<*>, val filter: Filter<*>,
) )
fun setFilter(sourceId: Long, change: FilterChange) { fun setFilter(sourceId: Long, changes: List<FilterChange>) {
val source = getCatalogueSourceOrStub(sourceId) val source = getCatalogueSourceOrStub(sourceId)
val filterList = getFilterListOf(source, false) val filterList = getFilterListOf(source, false)
changes.forEach { change ->
when (val filter = filterList[change.position]) { when (val filter = filterList[change.position]) {
is Filter.Header -> { is Filter.Header -> {
// NOOP // NOOP
@@ -105,12 +107,16 @@ object Search {
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt() 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>() private val jsonMapper by DI.global.instance<JsonMapper>()
@Serializable
data class FilterChange( data class FilterChange(
val position: Int, val position: Int,
val state: String val state: String

View File

@@ -21,7 +21,7 @@ import org.kodein.di.DI
import org.kodein.di.conf.global import org.kodein.di.conf.global
import org.kodein.di.instance import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl 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.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
@@ -36,8 +36,8 @@ object Source {
fun getSourceList(): List<SourceDataClass> { fun getSourceList(): List<SourceDataClass> {
return transaction { return transaction {
SourceTable.selectAll().map { SourceTable.selectAll().mapNotNull {
val catalogueSource = getCatalogueSourceOrStub(it[SourceTable.id].value) val catalogueSource = getCatalogueSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null
val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first() val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()
SourceDataClass( 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 { return transaction {
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null
val catalogueSource = source?.let { getCatalogueSource(sourceId) } val catalogueSource = getCatalogueSourceOrNull(sourceId) ?: return@transaction null
val extension = source?.let { val extension = ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()
}
SourceDataClass( SourceDataClass(
sourceId.toString(), sourceId.toString(),
source?.get(SourceTable.name), source[SourceTable.name],
source?.get(SourceTable.lang), source[SourceTable.lang],
source?.let {
getExtensionIconUrl( getExtensionIconUrl(
extension!![ExtensionTable.apkName] extension[ExtensionTable.apkName]
) ),
}, catalogueSource.supportsLatest,
catalogueSource?.supportsLatest, catalogueSource is ConfigurableSource,
catalogueSource?.let { it is ConfigurableSource }, source[SourceTable.isNsfw],
source?.get(SourceTable.isNsfw), catalogueSource.toString()
catalogueSource?.toString()
) )
} }
} }

View File

@@ -41,7 +41,7 @@ object PackageTools {
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
const val METADATA_NSFW = "tachiyomi.extension.nsfw" const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val LIB_VERSION_MIN = 1.2 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 officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key

View File

@@ -26,7 +26,7 @@ object GetCatalogueSource {
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>() private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>()
private val applicationDirs by DI.global.instance<ApplicationDirs>() private val applicationDirs by DI.global.instance<ApplicationDirs>()
fun getCatalogueSource(sourceId: Long): CatalogueSource? { private fun getCatalogueSource(sourceId: Long): CatalogueSource? {
val cachedResult: CatalogueSource? = sourceCache[sourceId] val cachedResult: CatalogueSource? = sourceCache[sourceId]
if (cachedResult != null) { if (cachedResult != null) {
return cachedResult return cachedResult
@@ -56,8 +56,12 @@ object GetCatalogueSource {
return sourceCache[sourceId]!! return sourceCache[sourceId]!!
} }
fun getCatalogueSourceOrNull(sourceId: Long): CatalogueSource? {
return runCatching { getCatalogueSource(sourceId) }.getOrNull()
}
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource { fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource {
return getCatalogueSource(sourceId) ?: StubSource(sourceId) return getCatalogueSourceOrNull(sourceId) ?: StubSource(sourceId)
} }
fun registerCatalogueSource(sourcePair: Pair<Long, CatalogueSource>) { fun registerCatalogueSource(sourcePair: Pair<Long, CatalogueSource>) {

View File

@@ -11,19 +11,19 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
data class SourceDataClass( data class SourceDataClass(
val id: String, val id: String,
val name: String?, val name: String,
val lang: String?, val lang: String,
val iconUrl: String?, val iconUrl: String,
/** The Source provides a latest listing */ /** The Source provides a latest listing */
val supportsLatest: Boolean?, val supportsLatest: Boolean,
/** The Source implements [ConfigurableSource] */ /** The Source implements [ConfigurableSource] */
val isConfigurable: Boolean?, val isConfigurable: Boolean,
/** The Source class has a @Nsfw annotation */ /** The Source class has a @Nsfw annotation */
val isNsfw: Boolean?, val isNsfw: Boolean,
/** A nicer version of [name] */ /** A nicer version of [name] */
val displayName: String?, val displayName: String,
) )

View File

@@ -66,7 +66,10 @@ enum class MangaStatus(val value: Int) {
UNKNOWN(0), UNKNOWN(0),
ONGOING(1), ONGOING(1),
COMPLETED(2), COMPLETED(2),
LICENSED(3); LICENSED(3),
PUBLISHING_FINISHED(4),
CANCELLED(5),
ON_HIATUS(6);
companion object { companion object {
fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN

View File

@@ -54,6 +54,22 @@ object JavalinSetup {
} }
config.enableCorsForAllOrigins() 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 -> }.events { event ->
event.serverStarted { event.serverStarted {
if (serverConfig.initialOpenInBrowserEnabled) { if (serverConfig.initialOpenInBrowserEnabled) {
@@ -83,18 +99,6 @@ object JavalinSetup {
ctx.result(e.message ?: "Internal Server Error") 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 { app.routes {
path("api/v1/") { path("api/v1/") {
GlobalAPI.defineEndpoints() GlobalAPI.defineEndpoints()

View File

@@ -11,6 +11,7 @@ import com.typesafe.config.Config
import xyz.nulldev.ts.config.GlobalConfigManager import xyz.nulldev.ts.config.GlobalConfigManager
import xyz.nulldev.ts.config.SystemPropertyOverridableConfigModule import xyz.nulldev.ts.config.SystemPropertyOverridableConfigModule
import xyz.nulldev.ts.config.debugLogsEnabled import xyz.nulldev.ts.config.debugLogsEnabled
import kotlin.reflect.KProperty
private const val MODULE_NAME = "server" private const val MODULE_NAME = "server"
class ServerConfig(config: Config, moduleName: String = MODULE_NAME) : SystemPropertyOverridableConfigModule(config, moduleName) { 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 val electronPath: String by overridableConfig
// Authentication // 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 basicAuthEnabled: Boolean by overridableConfig
val basicAuthUsername: String by overridableConfig val basicAuthUsername: String by overridableConfig
val basicAuthPassword: String by overridableConfig val basicAuthPassword: String by overridableConfig

View File

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
import io.javalin.plugin.json.JavalinJackson import io.javalin.plugin.json.JavalinJackson
import io.javalin.plugin.json.JsonMapper import io.javalin.plugin.json.JsonMapper
import kotlinx.serialization.json.Json
import mu.KotlinLogging import mu.KotlinLogging
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.bind import org.kodein.di.bind
@@ -59,6 +60,7 @@ fun applicationSetup() {
bind<ApplicationDirs>() with singleton { applicationDirs } bind<ApplicationDirs>() with singleton { applicationDirs }
bind<IUpdater>() with singleton { Updater() } bind<IUpdater>() with singleton { Updater() }
bind<JsonMapper>() with singleton { JavalinJackson() } bind<JsonMapper>() with singleton { JavalinJackson() }
bind<Json>() with singleton { Json { ignoreUnknownKeys = true } }
} }
) )

View File

@@ -14,7 +14,8 @@ server.webUIInterface = "browser" # "browser" or "electron"
server.electronPath = "" server.electronPath = ""
# Authentication # Authentication
server.basicAuthEnabled = false server.authType = "none" # "none" or "basicAuth" or "token"
server.basicAuthEnabled = false # This is deprecated, use server.authType
server.basicAuthUsername = "" server.basicAuthUsername = ""
server.basicAuthPassword = "" server.basicAuthPassword = ""

View File

@@ -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.Extension.updateExtension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle 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.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.server.applicationSetup import suwayomi.tachidesk.server.applicationSetup
import suwayomi.tachidesk.test.BASE_PATH 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) setLoggingEnabled(true)
File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" }) File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })