Compare commits
67 Commits
fe5534b006
...
7f98a7fb5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f98a7fb5c | ||
|
|
a39721eaf7 | ||
|
|
65c61c0c01 | ||
|
|
ac977a6cbb | ||
|
|
8908031eee | ||
|
|
4141a8f429 | ||
|
|
ad7f1eddca | ||
|
|
472ddab27c | ||
|
|
560cf63a5e | ||
|
|
d4e1acd515 | ||
|
|
f66b97edc1 | ||
|
|
a084909507 | ||
|
|
58357a3745 | ||
|
|
8a6a529023 | ||
|
|
1b9ff47ab3 | ||
|
|
229f321299 | ||
|
|
a5233ede48 | ||
|
|
d9025dbf81 | ||
|
|
2cc0af17d5 | ||
|
|
a3f384e3b3 | ||
|
|
5882a5d54d | ||
|
f62809407b
|
|||
|
|
14c0eaf273 | ||
|
|
0817a82f36 | ||
|
|
727e326379 | ||
|
|
ccaf52b74e | ||
|
|
fc988cdd1e | ||
|
|
cb2e9841ae | ||
|
|
775063d36f | ||
|
|
86b8642456 | ||
|
|
185d9b6e6e | ||
|
|
e3495fc41f | ||
|
|
d8e47b2b52 | ||
|
|
be64bf2328 | ||
|
|
d85bb64b1c | ||
|
|
de2289eb75 | ||
|
|
7cb069d108 | ||
|
|
8a18df148d | ||
|
|
9007852d31 | ||
|
|
60e451303e | ||
|
|
170a11ae26 | ||
|
|
642ac08338 | ||
|
|
ff256cb3ec | ||
|
|
08d46988a3 | ||
|
|
a3abc81dfc | ||
|
|
41a4c90e75 | ||
|
|
23fdfbbef9 | ||
|
|
a4f41e41f8 | ||
|
|
db586045d7 | ||
|
|
e636eda840 | ||
|
|
406992a6c9 | ||
|
30f97c5c82
|
|||
|
b9ecdb2db6
|
|||
|
e044463f91
|
|||
|
a93c760133
|
|||
|
|
f2b6cab251 | ||
|
|
07225568ee | ||
|
|
860dd32a53 | ||
|
|
74f5891ffe | ||
|
|
7bae2602c7 | ||
|
|
4c697b6406 | ||
|
|
b61698d405 | ||
|
|
260e5acd5c | ||
| e599f431c5 | |||
|
|
0ec4fe42c1 | ||
|
|
c9e4c7273e | ||
|
|
3e2515ac6a |
2
.github/summary.yaml
vendored
2
.github/summary.yaml
vendored
@@ -1 +1 @@
|
||||
total: 1251
|
||||
total: 1255
|
||||
8
.github/workflows/test-branch.yml
vendored
8
.github/workflows/test-branch.yml
vendored
@@ -13,15 +13,13 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up enviroment 🔧
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up Gradle 📦
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
cache-read-only: true
|
||||
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
|
||||
|
||||
- name: Compile parsers 🚀
|
||||
run: ./gradlew compileKotlin
|
||||
|
||||
10
.github/workflows/test-parsers.yml
vendored
10
.github/workflows/test-parsers.yml
vendored
@@ -17,15 +17,13 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up enviroment 🔧
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up Gradle 📦
|
||||
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
with:
|
||||
cache-read-only: true
|
||||
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
|
||||
|
||||
- name: Compile parsers 🚀
|
||||
run: ./gradlew compileKotlin
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -92,3 +92,7 @@ local.properties
|
||||
.idea/**/runConfigurations.xml
|
||||
.idea/**/AndroidProjectSystem.xml
|
||||
.idea/caches/deviceStreaming.xml
|
||||
/.idea/copilot.data.migration.agent.xml
|
||||
/.idea/copilot.data.migration.ask.xml
|
||||
/.idea/copilot.data.migration.ask2agent.xml
|
||||
/.idea/copilot.data.migration.edit.xml
|
||||
|
||||
@@ -56,6 +56,7 @@ dependencies {
|
||||
testImplementation(libs.junit.api)
|
||||
testImplementation(libs.junit.engine)
|
||||
testImplementation(libs.junit.params)
|
||||
testRuntimeOnly(libs.junit.launcher)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
testImplementation(libs.quickjs)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "2.2.10"
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -11,8 +11,7 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(gradleApi())
|
||||
implementation("org.simpleframework:simple-xml:2.7.1")
|
||||
implementation("com.soywiz.korlibs.korte:korte-jvm:4.0.10")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation(libs.korte)
|
||||
implementation(libs.simplexml)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
|
||||
BIN
buildSrc/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
buildSrc/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
47
buildSrc/gradlew
vendored
47
buildSrc/gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +82,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -133,22 +133,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
41
buildSrc/gradlew.bat
vendored
41
buildSrc/gradlew.bat
vendored
@@ -13,8 +13,10 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +27,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
7
buildSrc/settings.gradle.kts
Normal file
7
buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
# Following this blog:
|
||||
## Following this blog:
|
||||
# https://proandroiddev.com/how-we-reduced-our-gradle-build-times-by-over-80-51f2b6d6b05b
|
||||
kotlin.code.style=official
|
||||
systemProp.org.gradle.unsafe.configuration-cache=false
|
||||
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=4096m -XX:+UseParallelGC
|
||||
org.gradle.vfs.watch=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.unsafe.configuration-cache=true
|
||||
org.gradle.configuration-cache.problems=warn
|
||||
|
||||
## Use these flags on local machine for faster build time
|
||||
# org.gradle.caching=true
|
||||
# org.gradle.configuration-cache=true
|
||||
# org.gradle.vfs.watch=true
|
||||
# org.gradle.parallel=true
|
||||
# org.gradle.workers.max=8
|
||||
# org.gradle.configuration-cache.max-problems=8
|
||||
|
||||
@@ -9,6 +9,8 @@ json = "20240303"
|
||||
androidx-collection = "1.5.0"
|
||||
jsoup = "1.21.2"
|
||||
quickjs = "1.1.0"
|
||||
korte = "4.0.10"
|
||||
simplexml = "2.7.1"
|
||||
|
||||
[plugins]
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
@@ -18,6 +20,7 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
ksp-symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||
junit-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" }
|
||||
junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
|
||||
junit-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" }
|
||||
junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" }
|
||||
@@ -27,3 +30,5 @@ json = { module = "org.json:json", version.ref = "json" }
|
||||
androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
|
||||
quickjs = { module = "io.webfolder:quickjs", version.ref = "quickjs" }
|
||||
korte = { module = "com.soywiz.korlibs.korte:korte-jvm", version.ref = "korte" }
|
||||
simplexml = { module = "org.simpleframework:simple-xml", version.ref = "simplexml" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,7 @@
|
||||
#Wed Aug 27 01:56:37 ICT 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
49
gradlew
vendored
49
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +82,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -133,22 +133,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
41
gradlew.bat
vendored
41
gradlew.bat
vendored
@@ -13,8 +13,10 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +27,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
2
jitpack.yml
Normal file
2
jitpack.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
jdk:
|
||||
- openjdk17
|
||||
@@ -5,6 +5,7 @@ import androidx.collection.SparseArrayCompat
|
||||
import okhttp3.HttpUrl
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
@@ -18,6 +19,7 @@ import java.util.*
|
||||
|
||||
private const val CHAPTERS_LIMIT = 99999
|
||||
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("COMICK_FUN", "ComicK")
|
||||
internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
|
||||
@@ -145,8 +147,13 @@ internal class ComickFunParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
val ja = webClient.httpGet(url.build()).parseJsonArray()
|
||||
val tagsMap = tagsArray.get()
|
||||
val ja = try {
|
||||
webClient.httpGet(url.build()).parseJsonArray()
|
||||
} catch (_: Exception) {
|
||||
throw IllegalArgumentException("ComicK is down!")
|
||||
}
|
||||
|
||||
val tagsMap = tagsArray.get()
|
||||
return ja.mapJSON { jo ->
|
||||
val slug = jo.getString("slug")
|
||||
Manga(
|
||||
|
||||
@@ -19,9 +19,7 @@ import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import java.net.HttpURLConnection
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken("Need to fix getPages, most manga don't have chapter images due to faulty fetch logic")
|
||||
@MangaSourceParser("KOHARU", "Schale.network", type = ContentType.HENTAI)
|
||||
internal class Koharu(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.KOHARU, 24) {
|
||||
|
||||
@@ -14,11 +14,40 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.bitmap.Rect
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.getCookies
|
||||
import org.koitharu.kotatsu.parsers.util.mapChapters
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.util.ownTextOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.parseFailed
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.parseJson
|
||||
import org.koitharu.kotatsu.parsers.util.parseSafe
|
||||
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
||||
import org.koitharu.kotatsu.parsers.util.splitByWhitespace
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Base64
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import kotlin.math.min
|
||||
|
||||
private const val PIECE_SIZE = 200
|
||||
@@ -41,6 +70,10 @@ internal abstract class MangaFireParser(
|
||||
SortOrder.RELEVANCE,
|
||||
)
|
||||
|
||||
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
|
||||
.add("Referer", "https://$domain/")
|
||||
.build()
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
@@ -95,25 +128,30 @@ internal abstract class MangaFireParser(
|
||||
addQueryParameter("page", page.toString())
|
||||
addQueryParameter("language[]", siteLang)
|
||||
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
|
||||
part.urlEncoded()
|
||||
}
|
||||
addEncodedQueryParameter("keyword", encodedQuery)
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (order) {
|
||||
SortOrder.UPDATED -> "recently_updated"
|
||||
SortOrder.POPULARITY -> "most_viewed"
|
||||
SortOrder.RATING -> "scores"
|
||||
SortOrder.NEWEST -> "release_date"
|
||||
SortOrder.ALPHABETICAL -> "title_az"
|
||||
SortOrder.RELEVANCE -> "most_relevance"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
|
||||
part.urlEncoded()
|
||||
}
|
||||
addEncodedQueryParameter("keyword", encodedQuery)
|
||||
|
||||
// Generate VRF for search query
|
||||
val searchVrf = VrfGenerator.generate(filter.query.trim())
|
||||
addQueryParameter("vrf", searchVrf)
|
||||
|
||||
addQueryParameter(
|
||||
name = "sort",
|
||||
value = when (order) {
|
||||
SortOrder.UPDATED -> "recently_updated"
|
||||
SortOrder.POPULARITY -> "most_viewed"
|
||||
SortOrder.RATING -> "scores"
|
||||
SortOrder.NEWEST -> "release_date"
|
||||
SortOrder.ALPHABETICAL -> "title_az"
|
||||
SortOrder.RELEVANCE -> "most_relevance"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
filter.tagsExclude.forEach { tag ->
|
||||
@@ -260,14 +298,18 @@ internal abstract class MangaFireParser(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> {
|
||||
val chapterElements = webClient
|
||||
.httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}")
|
||||
.parseJson()
|
||||
.getJSONObject("result")
|
||||
.getString("html")
|
||||
.let(Jsoup::parseBodyFragment)
|
||||
.select("ul li a")
|
||||
private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> {
|
||||
val readVrfInput = "$mangaId@${branch.type}@${branch.langCode}"
|
||||
val readVrf = VrfGenerator.generate(readVrfInput)
|
||||
|
||||
val response = webClient
|
||||
.httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}?vrf=$readVrf")
|
||||
|
||||
val chapterElements = response.parseJson()
|
||||
.getJSONObject("result")
|
||||
.getString("html")
|
||||
.let(Jsoup::parseBodyFragment)
|
||||
.select("ul li a")
|
||||
|
||||
if (branch.type == "chapter") {
|
||||
val doc = webClient
|
||||
@@ -276,31 +318,32 @@ internal abstract class MangaFireParser(
|
||||
.getString("result")
|
||||
.let(Jsoup::parseBodyFragment)
|
||||
|
||||
doc.select("ul li a").withIndex().forEach { (i, it) ->
|
||||
val date = it.select("span")[1].ownText()
|
||||
chapterElements[i].attr("upload-date", date)
|
||||
chapterElements[i].attr("other-title", it.attr("title"))
|
||||
}
|
||||
}
|
||||
doc.select("ul li a").withIndex().forEach { (i, it) ->
|
||||
val date = it.select("span").getOrNull(1)?.ownText() ?: ""
|
||||
chapterElements[i].attr("upload-date", date)
|
||||
chapterElements[i].attr("other-title", it.attr("title"))
|
||||
}
|
||||
}
|
||||
|
||||
return chapterElements.mapChapters(reversed = true) { _, it ->
|
||||
MangaChapter(
|
||||
id = generateUid(it.attr("href")),
|
||||
title = it.attr("title").ifBlank {
|
||||
"${branch.type.toTitleCase()} ${it.attr("data-number")}"
|
||||
},
|
||||
number = it.attr("data-number").toFloat(),
|
||||
volume = it.attr("other-title").let {
|
||||
volumeNumRegex.find(it)?.groupValues?.getOrNull(2)?.toInt() ?: 0
|
||||
},
|
||||
url = "${branch.type}/${it.attr("data-id")}",
|
||||
scanlator = null,
|
||||
uploadDate = dateFormat.parseSafe(it.attr("upload-date")),
|
||||
branch = "${branch.langTitle} ${branch.type.toTitleCase()}",
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
return chapterElements.mapChapters(reversed = true) { _, it ->
|
||||
val chapterId = it.attr("data-id")
|
||||
MangaChapter(
|
||||
id = generateUid(it.attr("href")),
|
||||
title = it.attr("title").ifBlank {
|
||||
"${branch.type.toTitleCase()} ${it.attr("data-number")}"
|
||||
},
|
||||
number = it.attr("data-number").toFloatOrNull() ?: -1f,
|
||||
volume = it.attr("other-title").let { title ->
|
||||
volumeNumRegex.find(title)?.groupValues?.getOrNull(2)?.toInt() ?: 0
|
||||
},
|
||||
url = "$mangaId/${branch.type}/${branch.langCode}/$chapterId",
|
||||
scanlator = null,
|
||||
uploadDate = dateFormat.parseSafe(it.attr("upload-date")),
|
||||
branch = "${branch.langTitle} ${branch.type.toTitleCase()}",
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)
|
||||
private val volumeNumRegex = Regex("""vol(ume)?\s*(\d+)""", RegexOption.IGNORE_CASE)
|
||||
@@ -387,12 +430,15 @@ internal abstract class MangaFireParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val images = webClient
|
||||
.httpGet("https://$domain/ajax/read/${chapter.url}")
|
||||
.parseJson()
|
||||
.getJSONObject("result")
|
||||
.getJSONArray("images")
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val chapterId = chapter.url.substringAfterLast('/')
|
||||
val vrf = VrfGenerator.generate("chapter@$chapterId")
|
||||
|
||||
val images = webClient
|
||||
.httpGet("https://$domain/ajax/read/chapter/$chapterId?vrf=$vrf")
|
||||
.parseJson()
|
||||
.getJSONObject("result")
|
||||
.getJSONArray("images")
|
||||
|
||||
val pages = ArrayList<MangaPage>(images.length())
|
||||
|
||||
@@ -491,3 +537,186 @@ internal abstract class MangaFireParser(
|
||||
class PortugueseBR(context: MangaLoaderContext) :
|
||||
MangaFireParser(context, MangaParserSource.MANGAFIRE_PTBR, "pt-br")
|
||||
}
|
||||
|
||||
|
||||
private object VrfGenerator {
|
||||
private fun atob(data: String): ByteArray = Base64.getDecoder().decode(data)
|
||||
|
||||
private fun btoa(data: ByteArray): String = Base64.getEncoder().encodeToString(data)
|
||||
|
||||
private fun rc4(key: ByteArray, input: ByteArray): ByteArray {
|
||||
val s = IntArray(256) { it }
|
||||
var j = 0
|
||||
|
||||
// KSA
|
||||
for (i in 0..255) {
|
||||
j = (j + s[i] + key[i % key.size].toInt().and(0xFF)) and 0xFF
|
||||
val temp = s[i]
|
||||
s[i] = s[j]
|
||||
s[j] = temp
|
||||
}
|
||||
|
||||
// PRGA
|
||||
val output = ByteArray(input.size)
|
||||
var i = 0
|
||||
j = 0
|
||||
for (y in input.indices) {
|
||||
i = (i + 1) and 0xFF
|
||||
j = (j + s[i]) and 0xFF
|
||||
val temp = s[i]
|
||||
s[i] = s[j]
|
||||
s[j] = temp
|
||||
val k = s[(s[i] + s[j]) and 0xFF]
|
||||
output[y] = (input[y].toInt() xor k).toByte()
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun transform(
|
||||
input: ByteArray,
|
||||
initSeedBytes: ByteArray,
|
||||
prefixKeyBytes: ByteArray,
|
||||
prefixLen: Int,
|
||||
schedule: List<(Int) -> Int>,
|
||||
): ByteArray {
|
||||
val out = mutableListOf<Byte>()
|
||||
for (i in input.indices) {
|
||||
if (i < prefixLen) {
|
||||
out.add(prefixKeyBytes[i])
|
||||
}
|
||||
val transformed = schedule[i % 10](
|
||||
(input[i].toInt() xor initSeedBytes[i % 32].toInt()) and 0xFF,
|
||||
) and 0xFF
|
||||
out.add(transformed.toByte())
|
||||
}
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
private val scheduleC = listOf<(Int) -> Int>(
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c xor 241) and 0xFF },
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c xor 8) and 0xFF },
|
||||
)
|
||||
|
||||
private val scheduleY = listOf<(Int) -> Int>(
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
{ c -> (c xor 163) and 0xFF },
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c xor 83) and 0xFF },
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
)
|
||||
|
||||
private val scheduleB = listOf<(Int) -> Int>(
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
{ c -> (c - 48 + 256) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> (c xor 8) and 0xFF },
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c xor 163) and 0xFF },
|
||||
)
|
||||
|
||||
private val scheduleJ = listOf<(Int) -> Int>(
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> (c xor 83) and 0xFF },
|
||||
{ c -> (c - 19 + 256) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> (c + 223) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> (c xor 83) and 0xFF },
|
||||
)
|
||||
|
||||
private val scheduleE = listOf<(Int) -> Int>(
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c xor 83) and 0xFF },
|
||||
{ c -> (c xor 163) and 0xFF },
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c - 170 + 256) and 0xFF },
|
||||
{ c -> (c xor 8) and 0xFF },
|
||||
{ c -> (c xor 241) and 0xFF },
|
||||
{ c -> (c + 82) and 0xFF },
|
||||
{ c -> (c + 176) and 0xFF },
|
||||
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
||||
)
|
||||
|
||||
private val rc4Keys = mapOf(
|
||||
"l" to "u8cBwTi1CM4XE3BkwG5Ble3AxWgnhKiXD9Cr279yNW0=",
|
||||
"g" to "t00NOJ/Fl3wZtez1xU6/YvcWDoXzjrDHJLL2r/IWgcY=",
|
||||
"B" to "S7I+968ZY4Fo3sLVNH/ExCNq7gjuOHjSRgSqh6SsPJc=",
|
||||
"m" to "7D4Q8i8dApRj6UWxXbIBEa1UqvjI+8W0UvPH9talJK8=",
|
||||
"F" to "0JsmfWZA1kwZeWLk5gfV5g41lwLL72wHbam5ZPfnOVE=",
|
||||
)
|
||||
|
||||
private val seeds32 = mapOf(
|
||||
"A" to "pGjzSCtS4izckNAOhrY5unJnO2E1VbrU+tXRYG24vTo=",
|
||||
"V" to "dFcKX9Qpu7mt/AD6mb1QF4w+KqHTKmdiqp7penubAKI=",
|
||||
"N" to "owp1QIY/kBiRWrRn9TLN2CdZsLeejzHhfJwdiQMjg3w=",
|
||||
"P" to "H1XbRvXOvZAhyyPaO68vgIUgdAHn68Y6mrwkpIpEue8=",
|
||||
"k" to "2Nmobf/mpQ7+Dxq1/olPSDj3xV8PZkPbKaucJvVckL0=",
|
||||
)
|
||||
|
||||
private val prefixKeys = mapOf(
|
||||
"O" to "Rowe+rg/0g==",
|
||||
"v" to "8cULcnOMJVY8AA==",
|
||||
"L" to "n2+Og2Gth8Hh",
|
||||
"p" to "aRpvzH+yoA==",
|
||||
"W" to "ZB4oBi0=",
|
||||
)
|
||||
|
||||
fun generate(input: String): String {
|
||||
var bytes = input.toByteArray()
|
||||
// RC4 1
|
||||
bytes = rc4(atob(rc4Keys["l"]!!), bytes)
|
||||
|
||||
// Step C1
|
||||
bytes = transform(bytes, atob(seeds32["A"]!!), atob(prefixKeys["O"]!!), 7, scheduleC)
|
||||
|
||||
// RC4 2
|
||||
bytes = rc4(atob(rc4Keys["g"]!!), bytes)
|
||||
|
||||
// Step Y
|
||||
bytes = transform(bytes, atob(seeds32["V"]!!), atob(prefixKeys["v"]!!), 10, scheduleY)
|
||||
|
||||
// RC4 3
|
||||
bytes = rc4(atob(rc4Keys["B"]!!), bytes)
|
||||
|
||||
// Step B
|
||||
bytes = transform(bytes, atob(seeds32["N"]!!), atob(prefixKeys["L"]!!), 9, scheduleB)
|
||||
|
||||
// RC4 4
|
||||
bytes = rc4(atob(rc4Keys["m"]!!), bytes)
|
||||
|
||||
// Step J
|
||||
bytes = transform(bytes, atob(seeds32["P"]!!), atob(prefixKeys["p"]!!), 7, scheduleJ)
|
||||
|
||||
// RC4 5
|
||||
bytes = rc4(atob(rc4Keys["F"]!!), bytes)
|
||||
|
||||
// Step E
|
||||
bytes = transform(bytes, atob(seeds32["k"]!!), atob(prefixKeys["W"]!!), 5, scheduleE)
|
||||
|
||||
// Base64URL encode
|
||||
return btoa(bytes)
|
||||
.replace("+", "-")
|
||||
.replace("/", "_")
|
||||
.replace("=", "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers.site.all
|
||||
|
||||
import okhttp3.Headers
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
@@ -22,13 +23,15 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.splitByWhitespace
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.collections.joinToString
|
||||
|
||||
@MangaSourceParser("MYREADINGMANGA", "MyReadingManga", type = ContentType.HENTAI)
|
||||
internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
@@ -36,19 +39,22 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("myreadingmanga.info")
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
override fun getRequestHeaders(): Headers = Headers.Builder()
|
||||
.add("User-Agent", "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36")
|
||||
.add("X-Requested-With", randomString((1..20).random()))
|
||||
.build()
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
isOriginalLocaleSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
isMultipleTagsSupported = true,
|
||||
)
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.RELEVANCE,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.NEWEST_ASC,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
@@ -56,6 +62,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
availableStates = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.PAUSED,
|
||||
MangaState.ABANDONED,
|
||||
),
|
||||
availableContentRating = EnumSet.of(ContentRating.ADULT),
|
||||
availableLocales = setOf(
|
||||
@@ -100,6 +108,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
|
||||
private fun getLanguageSlug(locale: Locale?): String? {
|
||||
return when {
|
||||
locale?.language == "en" -> "english"
|
||||
locale?.language == "ja" -> "japanese"
|
||||
locale?.language == "fr" -> "french"
|
||||
locale?.language == "ja" -> "jp"
|
||||
locale?.language == "zh" && locale.country == "TW" -> "traditional-chinese"
|
||||
@@ -139,75 +149,56 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
// Add language path if specified
|
||||
val langSlug = getLanguageSlug(filter.locale)
|
||||
if (langSlug != null) {
|
||||
append("/lang/")
|
||||
append(langSlug)
|
||||
}
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
|
||||
when {
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
// Search with language: /lang/french/page/2/?s=example
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
// order
|
||||
append("/?ep_sort=")
|
||||
when (order) {
|
||||
SortOrder.NEWEST -> append("date")
|
||||
SortOrder.NEWEST_ASC -> append("date_asc")
|
||||
else -> append("")
|
||||
}
|
||||
|
||||
filter.tags.isNotEmpty() -> {
|
||||
// Genre filtering doesn't work with language, so we ignore language for genre
|
||||
if (langSlug == null) {
|
||||
append("/genre/")
|
||||
append(filter.tags.first().key)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
} else {
|
||||
// If both language and genre are selected, just use language
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.isNotEmpty() -> {
|
||||
// Status filtering doesn't work with language either
|
||||
if (langSlug == null) {
|
||||
append("/status/")
|
||||
append(
|
||||
when (filter.states.first()) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "ongoing"
|
||||
},
|
||||
)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
} else {
|
||||
// If both language and status are selected, just use language
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
// fix order
|
||||
append("&s=")
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append(filter.query.splitByWhitespace().joinToString(separator = "+"))
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Regular browsing with or without language
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
val langSlug = getLanguageSlug(filter.locale)
|
||||
if (langSlug != null) {
|
||||
append("&ep_filter_lang=")
|
||||
append(langSlug)
|
||||
}
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
append("&ep_filter_status=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
MangaState.PAUSED -> "hiatus"
|
||||
MangaState.ABANDONED -> "dropped"
|
||||
else -> ""
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&ep_filter_genre=")
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -287,10 +278,10 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
|
||||
val genres = mutableSetOf<MangaTag>()
|
||||
|
||||
doc.select("span.entry-terms:has(span:contains(Genres)) a").forEach {
|
||||
doc.select("span.entry-terms:has(span:contains(Genre:)) a").forEach {
|
||||
genres.add(
|
||||
MangaTag(
|
||||
title = it.text(),
|
||||
title = it.text().removeSuffix(",").trim(),
|
||||
key = it.attr("href").substringAfterLast("/genre/").substringBefore("/"),
|
||||
source = source,
|
||||
),
|
||||
@@ -300,6 +291,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
val state = when (doc.select("a[href*=status]").firstOrNull()?.text()) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -356,15 +349,15 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
|
||||
private fun findImageSrc(element: Element?): String? {
|
||||
element ?: return null
|
||||
|
||||
|
||||
return when {
|
||||
element.hasAttr("data-src") && imgRegex.matcher(element.attr("data-src")).find() ->
|
||||
element.hasAttr("data-src") && imgRegex.matcher(element.attr("data-src")).find() ->
|
||||
element.absUrl("data-src")
|
||||
element.hasAttr("data-cfsrc") && imgRegex.matcher(element.attr("data-cfsrc")).find() ->
|
||||
element.hasAttr("data-cfsrc") && imgRegex.matcher(element.attr("data-cfsrc")).find() ->
|
||||
element.absUrl("data-cfsrc")
|
||||
element.hasAttr("src") && imgRegex.matcher(element.attr("src")).find() ->
|
||||
element.hasAttr("src") && imgRegex.matcher(element.attr("src")).find() ->
|
||||
element.absUrl("src")
|
||||
element.hasAttr("data-lazy-src") ->
|
||||
element.hasAttr("data-lazy-src") ->
|
||||
element.absUrl("data-lazy-src")
|
||||
else -> null
|
||||
}
|
||||
@@ -447,5 +440,10 @@ internal class MyReadingManga(context: MangaLoaderContext) :
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
private fun randomString(length: Int): String {
|
||||
val charPool = ('a'..'z') + ('A'..'Z')
|
||||
return List(length) { charPool.random() }.joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ internal class BatCave(context: MangaLoaderContext) :
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("batcave.biz")
|
||||
|
||||
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
|
||||
.add("Referer", "https://$domain/")
|
||||
.build()
|
||||
|
||||
private val availableTags = suspendLazy(initializer = ::fetchTags)
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.Broken
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Broken // The website is broken, it seems to be closed already.
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS)
|
||||
internal class ComicExtra(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {
|
||||
|
||||
@@ -156,18 +156,21 @@ internal class MangaGeko(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
return doc.requireElementById("chapter-reader").select("img").map { img ->
|
||||
val url = img.requireSrc().toRelativeUrl(domain)
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
return doc.select("center img")
|
||||
.mapNotNull { it.attr("src").takeIf { src -> src.isNotBlank() } }
|
||||
// remove all invaild images + credits
|
||||
.filterNot { it.startsWith("data:image") || it.contains("credits-mgeko.png") }
|
||||
.distinct().map { url ->
|
||||
val finalUrl = url.toRelativeUrl(domain)
|
||||
MangaPage(
|
||||
id = generateUid(finalUrl),
|
||||
url = finalUrl,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,8 @@ import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.FlexiblePagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
|
||||
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchCapability
|
||||
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
|
||||
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchableField
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchableField.*
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
||||
@@ -23,7 +16,12 @@ import java.util.Locale
|
||||
|
||||
@MangaSourceParser("VIOLETSCANS", "VioletScans", "en")
|
||||
internal class VioletScans(context: MangaLoaderContext) :
|
||||
FlexiblePagedMangaParser(context, MangaParserSource.VIOLETSCANS, 12) {
|
||||
PagedMangaParser(context, MangaParserSource.VIOLETSCANS, 12) {
|
||||
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("violetscans.com")
|
||||
|
||||
@@ -34,37 +32,14 @@ internal class VioletScans(context: MangaLoaderContext) :
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)
|
||||
|
||||
override val searchQueryCapabilities: MangaSearchQueryCapabilities
|
||||
get() = MangaSearchQueryCapabilities(
|
||||
SearchCapability(
|
||||
field = TITLE_NAME,
|
||||
criteriaTypes = setOf(Match::class),
|
||||
isMultiple = false,
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
|
||||
|
||||
override suspend fun getListPage(query: MangaSearchQuery, page: Int): List<Manga> {
|
||||
var searchParameter = ""
|
||||
query.criteria.forEach { criterion ->
|
||||
when (criterion) {
|
||||
is QueryCriteria.Match<*> -> {
|
||||
if (criterion.field == SearchableField.TITLE_NAME) {
|
||||
searchParameter = criterion.value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
is QueryCriteria.Exclude<*> -> null
|
||||
is QueryCriteria.Range<*> -> null
|
||||
is QueryCriteria.Include<*> -> null
|
||||
}
|
||||
}
|
||||
// scrapeNonSearchList has considerable less payload as response so this is a optimization
|
||||
return when {
|
||||
!searchParameter.isNullOrEmpty() -> scrapeSearchList(searchParameter, page)
|
||||
else -> scrapeNonSearchList(page)
|
||||
}
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
return if (!filter.query.isNullOrEmpty()) {
|
||||
scrapeSearchList(filter.query, page)
|
||||
} else {
|
||||
scrapeNonSearchList(page)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun scrapeNonSearchList(page: Int): List<Manga> {
|
||||
@@ -199,7 +174,7 @@ internal class VioletScans(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
val dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)
|
||||
val date = dateFormat.parseSafe(dateString) ?: 0L
|
||||
val date = dateFormat.parseSafe(dateString)
|
||||
|
||||
val chaptersList = root.selectFirstOrThrow("#chapterlist ul")
|
||||
val chapters = chaptersList.select("li")
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en.MTL
|
||||
package org.koitharu.kotatsu.parsers.site.en.mtl
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
|
||||
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchCapability
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchableField
|
||||
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.core.FlexiblePagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -18,7 +13,7 @@ internal abstract class MTLParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaParserSource,
|
||||
domain: String,
|
||||
) : FlexiblePagedMangaParser(context, source, 24) {
|
||||
) : PagedMangaParser(context, source, 24) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
@@ -30,21 +25,18 @@ internal abstract class MTLParser(
|
||||
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
|
||||
|
||||
override val searchQueryCapabilities = MangaSearchQueryCapabilities(
|
||||
SearchCapability(
|
||||
field = SearchableField.TITLE_NAME,
|
||||
criteriaTypes = setOf(Match::class),
|
||||
isMultiple = false,
|
||||
),
|
||||
)
|
||||
override val filterCapabilities: MangaListFilterCapabilities
|
||||
get() = MangaListFilterCapabilities(
|
||||
isSearchSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(query: MangaSearchQuery, page: Int): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search")
|
||||
append("?")
|
||||
when (query.order) {
|
||||
when (order) {
|
||||
SortOrder.POPULARITY -> append("sort_by=views")
|
||||
SortOrder.UPDATED -> append("sort_by=recent")
|
||||
else -> append("sort_by=recent")
|
||||
@@ -53,19 +45,10 @@ internal abstract class MTLParser(
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
query.criteria.find { it.field == SearchableField.TITLE_NAME }?.let { criteria ->
|
||||
when (criteria) {
|
||||
is Match -> {
|
||||
append("&q=")
|
||||
append(criteria.value.toString())
|
||||
}
|
||||
|
||||
is Include,
|
||||
is Exclude,
|
||||
is Range,
|
||||
-> Unit // Not supported for this field
|
||||
}
|
||||
}
|
||||
append("&q=")
|
||||
if (!filter.query.isNullOrEmpty()) {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -190,7 +173,7 @@ internal abstract class MTLParser(
|
||||
val sdf = SimpleDateFormat("dd MMMM yyyy", Locale.ENGLISH)
|
||||
sdf.timeZone = TimeZone.getTimeZone("UTC")
|
||||
sdf.parse(dateString)?.time ?: 0L
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en.MTL
|
||||
package org.koitharu.kotatsu.parsers.site.en.mtl
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.site.en.MTL.MTLParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("SNOWMTL", "SnowMTL", "en", type = ContentType.OTHER)
|
||||
internal class SnowMTL(context: MangaLoaderContext):
|
||||
MTLParser(context, source = MangaParserSource.SNOWMTL, "snowmtl.ru")
|
||||
MTLParser(context, source = MangaParserSource.SNOWMTL, "snowmtl.ru")
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en.MTL
|
||||
package org.koitharu.kotatsu.parsers.site.en.mtl
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.site.en.MTL.MTLParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("SOLARMTL", "SolarMTL", "en", type = ContentType.MANGA)
|
||||
internal class SolarMTL(context: MangaLoaderContext):
|
||||
MTLParser(context, source = MangaParserSource.SOLARMTL, "solarmtl.com")
|
||||
MTLParser(context, source = MangaParserSource.SOLARMTL, "solarmtl.com")
|
||||
@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Redirect to DragonTranslation.org source")
|
||||
@MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es")
|
||||
internal class DragonTranslationParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.DRAGONTRANSLATION, 30) {
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.SinglePageMangaParser
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("MANGAMOINS", "MangaMoins", "fr")
|
||||
internal class MangaMoins(context: MangaLoaderContext) :
|
||||
SinglePageMangaParser(context, MangaParserSource.MANGAMOINS) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangamoins.com")
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val filterCapabilities = MangaListFilterCapabilities()
|
||||
|
||||
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
|
||||
|
||||
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
return listOf(
|
||||
Manga(
|
||||
id = generateUid("OP"),
|
||||
title = "One Piece",
|
||||
altTitles = emptySet(),
|
||||
url = "OP",
|
||||
publicUrl = "https://mangamoins.com/",
|
||||
rating = RATING_UNKNOWN,
|
||||
contentRating = null,
|
||||
coverUrl = "https://mangamoins.com/files/scans/OP1161/thumbnail.png",
|
||||
tags = emptySet(),
|
||||
state = MangaState.ONGOING,
|
||||
authors = setOf("Eiichiro Oda"),
|
||||
source = source,
|
||||
),
|
||||
Manga(
|
||||
id = generateUid("LCDL"),
|
||||
title = "Les Carnets de l'Apothicaire",
|
||||
altTitles = emptySet(),
|
||||
url = "LCDL",
|
||||
publicUrl = "https://mangamoins.com/",
|
||||
rating = RATING_UNKNOWN,
|
||||
contentRating = null,
|
||||
coverUrl = "https://mangamoins.com/files/scans/LCDL76.2/thumbnail.png",
|
||||
tags = emptySet(),
|
||||
state = MangaState.ONGOING,
|
||||
authors = setOf("Itsuki Nanao", "Nekokurage"),
|
||||
source = source,
|
||||
),
|
||||
Manga(
|
||||
id = generateUid("JKM"),
|
||||
title = "Jujutsu Kaisen Modulo",
|
||||
altTitles = emptySet(),
|
||||
url = "JKM",
|
||||
publicUrl = "https://mangamoins.com/",
|
||||
rating = RATING_UNKNOWN,
|
||||
contentRating = null,
|
||||
coverUrl = "https://mangamoins.com/files/scans/JKM1/thumbnail.png",
|
||||
tags = emptySet(),
|
||||
state = MangaState.ONGOING,
|
||||
authors = setOf("Gege Akutami", "Yuji Iwasaki"),
|
||||
source = source,
|
||||
),
|
||||
Manga(
|
||||
id = generateUid("OPC"),
|
||||
title = "One Piece Colo",
|
||||
altTitles = emptySet(),
|
||||
url = "OPC",
|
||||
publicUrl = "https://mangamoins.com/",
|
||||
rating = RATING_UNKNOWN,
|
||||
contentRating = null,
|
||||
coverUrl = "https://mangamoins.com/files/scans/OPC1160/thumbnail.png",
|
||||
tags = emptySet(),
|
||||
state = MangaState.ONGOING,
|
||||
authors = setOf("Eiichiro Oda"),
|
||||
source = source,
|
||||
),
|
||||
Manga(
|
||||
id = generateUid("LDS"),
|
||||
title = "L'Atelier des Sorciers",
|
||||
altTitles = emptySet(),
|
||||
url = "LDS",
|
||||
publicUrl = "https://mangamoins.com/",
|
||||
rating = RATING_UNKNOWN,
|
||||
contentRating = null,
|
||||
coverUrl = "https://sceneario.com/wp-content/uploads/2023/05/9782811641344-1.jpg",
|
||||
tags = emptySet(),
|
||||
state = MangaState.ONGOING,
|
||||
authors = setOf("Kamome Shirahama"),
|
||||
source = source,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet("https://$domain").parseHtml()
|
||||
val prefix = manga.url
|
||||
val latestSiteChapterNumber = doc.select("div.sortie a").mapNotNull { a ->
|
||||
val href = a.attr("href")
|
||||
if (href.startsWith("?scan=$prefix")) {
|
||||
href.substringAfter(prefix).toFloatOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.maxOrNull()
|
||||
|
||||
if (latestSiteChapterNumber == null) return manga
|
||||
|
||||
val cachedChapters = manga.chapters
|
||||
val lastKnownChapterNumber = cachedChapters?.maxByOrNull { it.number }?.number
|
||||
|
||||
if (lastKnownChapterNumber != null && cachedChapters.isNotEmpty()) {
|
||||
// INCREMENTAL SCAN (CACHE EXISTS)
|
||||
if (latestSiteChapterNumber <= lastKnownChapterNumber) {
|
||||
return manga.copy(chapters = cachedChapters.sortedBy { it.number })
|
||||
}
|
||||
|
||||
val newChapters = mutableListOf<MangaChapter>()
|
||||
var currentChapterInt = latestSiteChapterNumber.toInt()
|
||||
|
||||
while (currentChapterInt > lastKnownChapterNumber.toInt()) {
|
||||
val chaptersForGroup = doChecks(prefix, currentChapterInt)
|
||||
newChapters.addAll(chaptersForGroup)
|
||||
currentChapterInt--
|
||||
}
|
||||
|
||||
val combinedChapters = (cachedChapters + newChapters).distinctBy { it.number }
|
||||
return manga.copy(chapters = combinedChapters.sortedBy { it.number })
|
||||
|
||||
} else {
|
||||
// FULL SCAN (NO CACHE)
|
||||
val allChapters = mutableListOf<MangaChapter>()
|
||||
var currentChapterInt = latestSiteChapterNumber.toInt()
|
||||
var misses = 0
|
||||
|
||||
while (currentChapterInt >= 1 && misses < 4) {
|
||||
val chaptersForGroup = doChecks(prefix, currentChapterInt)
|
||||
if (chaptersForGroup.isNotEmpty()) {
|
||||
misses = 0
|
||||
allChapters.addAll(chaptersForGroup)
|
||||
} else {
|
||||
misses++
|
||||
}
|
||||
currentChapterInt--
|
||||
}
|
||||
return manga.copy(chapters = allChapters.sortedBy { it.number })
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doChecks(prefix: String, chapterInt: Int): List<MangaChapter> {
|
||||
val foundChapters = mutableListOf<MangaChapter>()
|
||||
|
||||
// Check for integer chapter
|
||||
val integerChapter = checkChapter(prefix, chapterInt.toString(), chapterInt.toFloat())
|
||||
if (integerChapter != null) {
|
||||
foundChapters.add(integerChapter)
|
||||
}
|
||||
|
||||
// Conditionally and sequentially check for decimal chapters
|
||||
if (prefix == "LCDL") {
|
||||
// Start from 2 because .1 never exists
|
||||
for (i in 2..9) {
|
||||
val decimalNum = chapterInt + (i / 10.0f)
|
||||
val decimalNumStr = String.format(Locale.US, "%.1f", decimalNum)
|
||||
val decimalChapter = checkChapter(prefix, decimalNumStr, decimalNum)
|
||||
if (decimalChapter != null) {
|
||||
foundChapters.add(decimalChapter)
|
||||
} else {
|
||||
break // Stop if there's a gap
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundChapters
|
||||
}
|
||||
|
||||
private suspend fun checkChapter(prefix: String, chapterNumStr: String, chapterNumFloat: Float): MangaChapter? {
|
||||
val thumbUrl = "https://mangamoins.com/files/scans/$prefix$chapterNumStr/thumbnail.png"
|
||||
return try {
|
||||
val response = webClient.httpHead(thumbUrl)
|
||||
if (response.isSuccessful) {
|
||||
response.close()
|
||||
val chapterUrl = "https://mangamoins.com/?scan=$prefix$chapterNumStr"
|
||||
MangaChapter(
|
||||
id = generateUid(chapterUrl),
|
||||
title = null,
|
||||
number = chapterNumFloat,
|
||||
volume = 0,
|
||||
url = chapterUrl,
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
branch = null,
|
||||
source = source
|
||||
)
|
||||
} else {
|
||||
response.close()
|
||||
null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = webClient.httpGet(chapter.url).parseHtml()
|
||||
return doc.select("link[rel=preload][as=image]").map { element ->
|
||||
val url = element.attr("href").toAbsoluteUrl(domain)
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,12 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
|
||||
@Broken // Not dead, changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("TEMPLESCAN", "TempleScan", "en")
|
||||
internal class TempleScan(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.TEMPLESCAN, "templetoons.com") {
|
||||
override val pathManga = "comic"
|
||||
|
||||
override val apiPath: String
|
||||
get() = "$domain/apiv1"
|
||||
get() = "$domain/api"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
|
||||
@Broken // Not dead but changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("YUGEN_MANGAS_ES", "YugenMangas.lat", "es", ContentType.HENTAI)
|
||||
internal class YugenMangasEs(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.YUGEN_MANGAS_ES, "lectorikigai.acamu.net")
|
||||
|
||||
@@ -8,7 +8,9 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("BRAKEOUT", "Brakeout", "es")
|
||||
internal class Brakeout(context: MangaLoaderContext) :
|
||||
HeanCmsAlt(context, MangaParserSource.BRAKEOUT, "brakeout.xyz", 10) {
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("LEGIONSCANS", "CerberusSeries", "es")
|
||||
internal class CerberuSeries(context: MangaLoaderContext) :
|
||||
HeanCmsAlt(context, MangaParserSource.LEGIONSCANS, "cerberuseries.xyz")
|
||||
HeanCmsAlt(context, MangaParserSource.LEGIONSCANS, "legionscans.com")
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt
|
||||
|
||||
@Broken // Not dead but changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("MANGAESP", "MangaEsp", "es")
|
||||
internal class MangaEsp(context: MangaLoaderContext) :
|
||||
HeanCmsAlt(context, MangaParserSource.MANGAESP, "mangaesp.topmanhuas.org", 15) {
|
||||
|
||||
@@ -30,6 +30,7 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
isMultipleTagsSupported = true,
|
||||
isSearchSupported = true,
|
||||
isSearchWithFiltersSupported = true,
|
||||
isAuthorSearchSupported = true,
|
||||
)
|
||||
|
||||
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||
@@ -49,17 +50,25 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
|
||||
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||
val url = urlBuilder().apply {
|
||||
addPathSegment("manga")
|
||||
addPathSegment("page")
|
||||
addPathSegment("$page/")
|
||||
when {
|
||||
page > 1 -> addPathSegments("manga/page/$page/")
|
||||
else -> addPathSegment("manga/")
|
||||
}
|
||||
|
||||
addQueryParameter(
|
||||
addQueryParameter(
|
||||
"title",
|
||||
filter.query?.let {
|
||||
filter.query
|
||||
},
|
||||
)
|
||||
|
||||
addQueryParameter(
|
||||
name = "author",
|
||||
value = filter.author?.let { it
|
||||
space2plus(it).lowercase()
|
||||
}
|
||||
)
|
||||
|
||||
addQueryParameter(
|
||||
"order",
|
||||
when (order) {
|
||||
@@ -97,14 +106,6 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Author
|
||||
// addQueryParameter("author",
|
||||
// filter.author?.let {
|
||||
// filter.author
|
||||
// }
|
||||
// )
|
||||
|
||||
}.build()
|
||||
|
||||
return webClient.httpGet(url).parseHtml()
|
||||
@@ -204,4 +205,6 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun space2plus(input: String): String = input.replace(' ', '+')
|
||||
}
|
||||
|
||||
@@ -192,8 +192,8 @@ internal abstract class IkenParser(
|
||||
val key = it.attrOrNull("value") ?: return@mapNotNullToSet null
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = (it.text() ?: key).toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
title = it.text().ifBlank { key }.toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ internal abstract class IkenParser(
|
||||
protected fun Document.getNextJson(key: String): String {
|
||||
val scripts = select("script")
|
||||
val scriptData = scripts.find { script ->
|
||||
script.data()?.contains(key) == true
|
||||
script.data().contains(key)
|
||||
}?.data() ?: throw Exception("Unable to retrieve NEXT data")
|
||||
|
||||
val keyIndex = scriptData.indexOf(key)
|
||||
|
||||
@@ -4,9 +4,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.iken.IkenParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken("Need to fix getPages")
|
||||
@MangaSourceParser("HIVECOMIC", "HiveComic", "en")
|
||||
internal class HiveComic(context: MangaLoaderContext) :
|
||||
IkenParser(context, MangaParserSource.HIVECOMIC, "hivecomic.com", 18)
|
||||
IkenParser(context, MangaParserSource.HIVECOMIC, "hivetoons.org", 18, true)
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.iken.IkenParser
|
||||
|
||||
@Broken // Redirect to @VORTEXSCANS
|
||||
@Broken("Redirect to VortexScans")
|
||||
@MangaSourceParser("MANGAGALAXY", "MangaGalaxy", "en")
|
||||
internal class MangaGalaxyParser(context: MangaLoaderContext) :
|
||||
IkenParser(context, MangaParserSource.MANGAGALAXY, "vortexscans.org", 18)
|
||||
IkenParser(context, MangaParserSource.MANGAGALAXY, "vortexscans.org", 18)
|
||||
|
||||
@@ -134,6 +134,7 @@ internal abstract class MadaraParser(
|
||||
"en curso",
|
||||
"ongoing",
|
||||
"on going",
|
||||
"OnGoing",
|
||||
"ativo",
|
||||
"en cours",
|
||||
"en cours \uD83D\uDFE2",
|
||||
@@ -478,7 +479,7 @@ internal abstract class MadaraParser(
|
||||
return elements.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
|
||||
val author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText()
|
||||
val author = summary?.selectFirst(".mg_author, .mg_artists")?.selectFirst("a")?.ownText()
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
@@ -549,7 +550,7 @@ internal abstract class MadaraParser(
|
||||
"div.post-content_item:contains(Status), div.post-content_item:contains(Statut), " +
|
||||
"div.post-content_item:contains(État), div.post-content_item:contains(حالة العمل), div.post-content_item:contains(Estado), div.post-content_item:contains(สถานะ)," +
|
||||
"div.post-content_item:contains(Stato), div.post-content_item:contains(Durum), div.post-content_item:contains(Statüsü), div.post-content_item:contains(Статус)," +
|
||||
"div.post-content_item:contains(状态), div.post-content_item:contains(الحالة)"
|
||||
"div.post-content_item:contains(状态), div.post-content_item:contains(الحالة), div.post-content_item:contains(Tình trạng)"
|
||||
protected open val selectAlt =
|
||||
".post-content_item:contains(Alt) .summary-content, .post-content_item:contains(Nomes alternativos: ) .summary-content"
|
||||
|
||||
@@ -567,7 +568,7 @@ internal abstract class MadaraParser(
|
||||
|
||||
val href = doc.selectFirst("head meta[property='og:url']")?.attr("content")?.toRelativeUrl(domain) ?: manga.url
|
||||
val testCheckAsync = doc.select(selectTestAsync)
|
||||
val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) {
|
||||
val chaptersDeferred = if (testCheckAsync.isEmpty()) {
|
||||
async { loadChapters(href, doc) }
|
||||
} else {
|
||||
async { getChapters(manga, doc) }
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("COMICARAB", "ComicArab", "ar")
|
||||
internal class ComicArab(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.COMICARAB, "comicarab.com", pageSize = 24) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("GATEMANGA", "GateManga", "ar")
|
||||
internal class GateManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.GATEMANGA, "gatemanga.com") {
|
||||
|
||||
@@ -9,4 +9,8 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
internal class FreeMangaTop(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.FREEMANGATOP, "freemangatop.com") {
|
||||
override val datePattern = "MM/dd/yyyy"
|
||||
|
||||
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
|
||||
.add("Referer", "https://$domain/")
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @hentai20
|
||||
@Broken("Redirect to @hentai20")
|
||||
@MangaSourceParser("HENTAI3Z", "Hentai3z", "en", ContentType.HENTAI)
|
||||
internal class Hentai3z(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.HENTAI3Z, "manga18h.xyz", pageSize = 20) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @MortalsGroove
|
||||
@Broken("Redirect to @MortalsGroove")
|
||||
@MangaSourceParser("IMMORTALUPDATES", "ImmortalUpdates", "en")
|
||||
internal class ImmortalUpdates(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.IMMORTALUPDATES, "immortalupdates.com") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import java.util.*
|
||||
|
||||
@Broken // Redirect to @XMANHWA
|
||||
@Broken("Redirect to @XMANHWA")
|
||||
@MangaSourceParser("INSTAMANHWA", "InstaManhwa", "en", ContentType.HENTAI)
|
||||
internal class InstaManhwa(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.INSTAMANHWA, "www.manhwaden.com", 15) {
|
||||
|
||||
@@ -1,15 +1,90 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.en
|
||||
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
private const val F_URL = "fullUrl="
|
||||
|
||||
@MangaSourceParser("MADARADEX", "MadaraDex", "en", ContentType.HENTAI)
|
||||
internal class MadaraDex(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MADARADEX, "madaradex.org") {
|
||||
override val listUrl = "title/"
|
||||
override val tagPrefix = "genre/"
|
||||
override val postReq = true
|
||||
MadaraParser(context, MangaParserSource.MADARADEX, "madaradex.org") {
|
||||
|
||||
init {
|
||||
context.cookieJar.insertCookies(domain, "wpmanga-adault=1")
|
||||
}
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.remove(userAgentKey)
|
||||
}
|
||||
|
||||
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
|
||||
.set("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override val authUrl: String
|
||||
get() = "https://${domain}"
|
||||
|
||||
override suspend fun isAuthorized(): Boolean {
|
||||
return context.cookieJar.getCookies(domain).any {
|
||||
it.name.contains("cm_uaid")
|
||||
}
|
||||
}
|
||||
|
||||
override val listUrl = "title/"
|
||||
override val tagPrefix = "genre/"
|
||||
override val postReq = true
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val root = doc.body().selectFirst(selectBodyPage)
|
||||
?: throw ParseException("No image found, try to log in", fullUrl)
|
||||
|
||||
return root.select(selectPage).flatMap { div ->
|
||||
div.selectOrThrow("img").map { img ->
|
||||
val fragUrl = img.requireSrc().toRelativeUrl(domain).toHttpUrl().newBuilder()
|
||||
.fragment(F_URL + fullUrl)
|
||||
.build()
|
||||
val cleanUrl = fragUrl.newBuilder().fragment(null).build()
|
||||
MangaPage(
|
||||
id = generateUid(cleanUrl.toString()),
|
||||
url = fragUrl.toString(),
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val url = request.url
|
||||
val fullUrl = url.fragment?.substringAfter(F_URL, "")
|
||||
return if (!fullUrl.isNullOrEmpty()) {
|
||||
copyCookies()
|
||||
val cleanUrl = url.newBuilder().fragment(null).toString()
|
||||
val newReq = request.newBuilder()
|
||||
.header("Referer", fullUrl)
|
||||
.url(cleanUrl)
|
||||
.build()
|
||||
chain.proceed(newReq)
|
||||
} else {
|
||||
super.intercept(chain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyCookies() = context.cookieJar.copyCookies(domain, "cdn.$domain")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @hentai20
|
||||
@Broken("Redirect to @hentai20")
|
||||
@MangaSourceParser("MANGA18H", "Manga18h", "en", ContentType.HENTAI)
|
||||
internal class Manga18h(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MANGA18H, "manga18h.xyz", 20)
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @MANGAREAD
|
||||
@Broken("Redirect to @MANGAREAD")
|
||||
@MangaSourceParser("MANGAEFFECT", "MangaEffect", "en")
|
||||
internal class MangaEffect(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MANGAEFFECT, "www.mangaread.org") {
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("RESETSCANS", "ReadManhua", "en")
|
||||
@MangaSourceParser("RESETSCANS", "Resetscans", "en")
|
||||
internal class ResetScans(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.RESETSCANS, "reset-scans.co", 20)
|
||||
MadaraParser(context, MangaParserSource.RESETSCANS, "reset-scans.org", 20)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("SCANSRAW", "AquaScans.com", "en")
|
||||
internal class Scansraw(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.SCANSRAW, "aquascans.com", 10)
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("STKISSMANGABLOG", "1StKissManga.net", "en")
|
||||
internal class StkissMangaBlog(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.STKISSMANGABLOG, "1stkissmanga.org", 20) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("STKISSMANGA_COM", "1stKissManga.com", "en")
|
||||
internal class StkissMangaCom(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.STKISSMANGA_COM, "1stkissmanga.mom", 10) {
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("COCORIP", "Cocorip", "es")
|
||||
internal class Cocorip(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.COCORIP, "cocorip.net", 16) {
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("DAPROB", "Daprob", "es")
|
||||
internal class Daprob(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.DAPROB, "daprob.com") {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("DARKNEBULUS", "Darknebulus", "es")
|
||||
internal class Darknebulus(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.DARKNEBULUS, "www.darknebulus.com")
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("DRAGONTRANSLATIONORG", "DragonTranslation.org", "es")
|
||||
internal class DragonTranslationOrg(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.DRAGONTRANSLATIONORG, "dragontranslation.org", 16) {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
}
|
||||
@@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("EMPERORSCAN", "EmperorScan", "es")
|
||||
internal class EmperorScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.EMPERORSCAN, "zonaemperor.com")
|
||||
MadaraParser(context, MangaParserSource.EMPERORSCAN, "emperorscan.mundoalterno.org")
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("HAREMSCANN", "HaremScann", "es")
|
||||
internal class HaremScann(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.HAREMSCANN, "haremscann.es") {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("HOUSEMANGAS", "HouseMangas", "es")
|
||||
internal class HouseMangas(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.HOUSEMANGAS, "housemangas.com")
|
||||
MadaraParser(context, MangaParserSource.HOUSEMANGAS, "visormanga.com")
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("INMORALNOFANSUB", "InmoralNoFansub", "es")
|
||||
internal class InmoralNoFansub(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.INMORALNOFANSUB, "inmoralnofansub.xyz") {
|
||||
|
||||
@@ -5,8 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("JEAZTWOBLUESCANS", "Marcialhub", "es")
|
||||
@MangaSourceParser("JEAZTWOBLUESCANS", "Lector HUB", "es")
|
||||
internal class JeazTwoBlueScans(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.JEAZTWOBLUESCANS, "marcialhub.xyz") {
|
||||
MadaraParser(context, MangaParserSource.JEAZTWOBLUESCANS, "lectorhub.j5z.xyz") {
|
||||
override val datePattern = "d MMMM, yyyy"
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("KNIGHTNOSCANLATION", "TwoBlueScans", "es")
|
||||
@MangaSourceParser("KNIGHTNOSCANLATION", "Lector KNS", "es")
|
||||
internal class KnightnoScanlation(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.KNIGHTNOSCANLATION, "kns.cookni.net") {
|
||||
MadaraParser(context, MangaParserSource.KNIGHTNOSCANLATION, "lectorknight.com") {
|
||||
override val listUrl = "sr/"
|
||||
override val tagPrefix = "generos/"
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Not dead but changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("KOINOBORISCAN", "KoinoboriScan", "es")
|
||||
internal class KoinoboriScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.KOINOBORISCAN, "koinoboriscan.com") {
|
||||
MadaraParser(context, MangaParserSource.KOINOBORISCAN, "visorkoi.com") {
|
||||
override val postReq = true
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("LEGENDSCANLATIONS", "LegendScanlations", "es")
|
||||
internal class LegendScanlations(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.LEGENDSCANLATIONS, "legendscanlations.com", 10) {
|
||||
MadaraParser(context, MangaParserSource.LEGENDSCANLATIONS, "escaneodeleyendas.com", 10) {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("JOBSIBE", "Jobsibe", "es")
|
||||
internal class Jobsibe(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.JOBSIBE, "jobsibe.com") {
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("LMTOS", "Lmtos", "es")
|
||||
internal class Lmtos(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.LMTOS, "lmtos.com") {
|
||||
override val datePattern = "dd/MM"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("MANGA_CRAB", "MangaCrab", "es")
|
||||
internal class MangaCrab(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MANGA_CRAB, "mangacrab.topmanhuas.org") {
|
||||
MadaraParser(context, MangaParserSource.MANGA_CRAB, "mangacrab.org") {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
override val tagPrefix = "manga-genero/"
|
||||
override val listUrl = "series/"
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("MANTRAZSCAN", "MantrazScan", "es")
|
||||
internal class MantrazScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MANTRAZSCAN, "artessupremas.com") {
|
||||
MadaraParser(context, MangaParserSource.MANTRAZSCAN, "mantrazscan.org") {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
override val tagPrefix = "generos-de-manga/"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("MHSCANS", "MhScans", "es")
|
||||
internal class MhScans(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MHSCANS, "twobluescans.com") {
|
||||
MadaraParser(context, MangaParserSource.MHSCANS, "mhscans.mundoalterno.org") {
|
||||
override val datePattern = "d 'de' MMMMM 'de' yyyy"
|
||||
override val listUrl = "series/"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("MMDAOS", "Mmdaos", "es")
|
||||
internal class Mmdaos(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MMDAOS, "mmdaos.com")
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("MONARCAMANGA", "MonarcaManga", "es")
|
||||
internal class MonarcaManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.MONARCAMANGA, "visormonarca.com") {
|
||||
|
||||
@@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("NOBLESSETRANSLATIONS", "NoblesseTranslations", "es")
|
||||
internal class NoblesseTranslations(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.NOBLESSETRANSLATIONS, "swordalada.org")
|
||||
MadaraParser(context, MangaParserSource.NOBLESSETRANSLATIONS, "nobledicion.yoveo.xyz")
|
||||
|
||||
@@ -9,8 +9,8 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
@Broken
|
||||
@MangaSourceParser("RAGNAROKSCAN", "RagnarokScan", "es")
|
||||
internal class RagnarokScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.RAGNAROKSCAN, "ragnarokscan.com") {
|
||||
override val stylePage = ""
|
||||
override val listUrl = "series/"
|
||||
override val tagPrefix = "genero/"
|
||||
MadaraParser(context, MangaParserSource.RAGNAROKSCAN, "ragnarokscan.com") {
|
||||
override val stylePage = ""
|
||||
override val listUrl = "series/"
|
||||
override val tagPrefix = "genero/"
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("RAGNAROKSCANLATION", "RagnarokScanlation", "es")
|
||||
internal class RagnarokScanlation(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.RAGNAROKSCANLATION, "ragnarokscanlation.net")
|
||||
MadaraParser(context, MangaParserSource.RAGNAROKSCANLATION, "ragnarokscanlation.org")
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("RICHTOSCAN", "RichtoScan", "es")
|
||||
internal class RichtoScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.RICHTOSCAN, "richtoscan.com") {
|
||||
MadaraParser(context, MangaParserSource.RICHTOSCAN, "r1.richtoon.top") {
|
||||
override val tagPrefix = "manga-generos/"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("SAMURAISCAN", "SamuraiScan", "es")
|
||||
internal class SamuraiScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.SAMURAISCAN, "latan.visorsmr.com", 10) {
|
||||
MadaraParser(context, MangaParserSource.SAMURAISCAN, "samuraiscan.com", 10) {
|
||||
override val listUrl = "read/"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("TAURUSMANGA", "TaurusManga", "es")
|
||||
internal class TaurusManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.TAURUSMANGA, "taurusmanga.com") {
|
||||
MadaraParser(context, MangaParserSource.TAURUSMANGA, "taurus.topmanhuas.org") {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("TECNOPROJECTS", "TecnoProjects", "es")
|
||||
internal class TecnoProjects(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.TECNOPROJECTS, "tecnoprojects.com") {
|
||||
MadaraParser(context, MangaParserSource.TECNOPROJECTS, "tecnoprojects.xyz") {
|
||||
override val datePattern = "dd 'de' MMMM 'de' yyyy"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
@@ -7,6 +8,7 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("TMOMANGA", "TmoManga", "es")
|
||||
internal class TmoManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.TMOMANGA, "tmomanga.com") {
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@MangaSourceParser("ZEVEP", "Zevep", "es")
|
||||
internal class Zevep(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.ZEVEP, "zevep.com", 16)
|
||||
|
||||
@@ -8,4 +8,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("SCANHENTAIMENU", "ScanHentai.Menu", "fr", ContentType.HENTAI)
|
||||
internal class ScanHentaiMenu(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.SCANHENTAIMENU, "x-manga.net")
|
||||
MadaraParser(context, MangaParserSource.SCANHENTAIMENU, "x-manga.org")
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Not dead, changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("CERISE_SCANS", "CeriseScans", "pt")
|
||||
internal class CeriseScans(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.CERISE_SCANS, "cerise.leitorweb.com") {
|
||||
|
||||
@@ -8,4 +8,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("CVNSCAN", "CvnScan", "pt", ContentType.HENTAI)
|
||||
internal class CvnScan(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.CVNSCAN, "cvnscan.com")
|
||||
MadaraParser(context, MangaParserSource.CVNSCAN, "covendasbruxonas.com")
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("GALINHASAMURAI", "GalinhaSamurai", "pt")
|
||||
internal class GalinhaSamurai(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.GALINHASAMURAI, "galinhasamurai.com") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken // Website closed and the domain has been sold
|
||||
@Broken("Original site closed")
|
||||
@MangaSourceParser("GOOFFANSUB", "GoofFansub", "pt", ContentType.HENTAI)
|
||||
internal class GoofFansub(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.GOOFFANSUB, "gooffansub.com") {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Not dead, changed template
|
||||
@Broken("Not dead, changed template")
|
||||
@MangaSourceParser("SINENSISSCANS", "SinensisScans", "pt")
|
||||
internal class SinensisScans(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.SINENSISSCANS, "sinensis.leitorweb.com") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @KINGS_MANGA
|
||||
@Broken("Redirect to @KINGS_MANGA")
|
||||
@MangaSourceParser("NEKOPOST", "NekoPost", "th", ContentType.HENTAI)
|
||||
internal class NekoPost(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.NEKOPOST, "www.superdoujin.org") {
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @WEBTOONHATTI
|
||||
@Broken("Redirect to @WEBTOONHATTI")
|
||||
@MangaSourceParser("CLOVERMANGA", "CloverManga", "tr")
|
||||
internal class CloverManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.CLOVERMANGA, "webtoonhatti.me", 20)
|
||||
|
||||
@@ -6,8 +6,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @GRIMELEK
|
||||
@Broken("Redirect to @GRIMELEK")
|
||||
@MangaSourceParser("GHOSTFANSUB", "GhostFansub", "tr")
|
||||
internal class GhostFansub(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.GHOSTFANSUB, "ghostfansub.co", 18)
|
||||
// you now need to log in to access content
|
||||
// you now need to log in to access content
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@Broken // Redirect to @MANGAGEZGINI
|
||||
@Broken("Redirect to @MANGAGEZGINI")
|
||||
@MangaSourceParser("GLORYMANGA", "GloryManga", "tr")
|
||||
internal class GloryManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.GLORYMANGA, "mangagezgini.site", 18) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
|
||||
@MangaSourceParser("HENTAIVNPLUS", "HentaiVN.plus", "vi", ContentType.HENTAI)
|
||||
internal class HentaiVnPlus(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.HENTAIVNPLUS, "hentaivn.cx", 24) {
|
||||
MadaraParser(context, MangaParserSource.HENTAIVNPLUS, "hentaivn.party", 24) {
|
||||
override val listUrl = "truyen-hentai/"
|
||||
override val tagPrefix = "the-loai/"
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
|
||||
@Broken
|
||||
@@ -26,7 +25,7 @@ internal class HentaiZ(context: MangaLoaderContext) :
|
||||
|
||||
val href = doc.selectFirst("head meta[property='og:url']")?.attr("content")?.toRelativeUrl(domain) ?: manga.url
|
||||
val testCheckAsync = doc.select(selectTestAsync)
|
||||
val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) {
|
||||
val chaptersDeferred = if (testCheckAsync.isEmpty()) {
|
||||
async { loadChapters(href, doc) }
|
||||
} else {
|
||||
async { getChapters(manga, doc) }
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("QUAANHDAOCUTEO", "Quả Anh Đào Cuteo", "vi", ContentType.HENTAI)
|
||||
internal class Quaanhdaocuteo(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.QUAANHDAOCUTEO, "qadcuteo.org") {
|
||||
MadaraParser(context, MangaParserSource.QUAANHDAOCUTEO, "qadcuteo.cc") {
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
override val selectPage = "p img"
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.koitharu.kotatsu.parsers.site.madara.vi
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
|
||||
@MangaSourceParser("TRUYENTRANHFULL", "Truyện Tranh Full", "vi")
|
||||
internal class TruyenTranhFull(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaParserSource.TRUYENTRANHFULL, "truyentranhfull.net", 20) {
|
||||
override val listUrl = "truyen-tranh/"
|
||||
override val tagPrefix = "the-loai/"
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -141,7 +142,7 @@ internal abstract class MadthemeParser(
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox ->
|
||||
val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null
|
||||
val key = checkbox.selectFirstOrThrow("input").attr("value")
|
||||
val name = checkbox.selectFirst("span.radio__label")?.text() ?: key
|
||||
MangaTag(
|
||||
key = key,
|
||||
@@ -190,7 +191,7 @@ internal abstract class MadthemeParser(
|
||||
altTitles = setOfNotNull(alt.nullIfEmpty()),
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
contentRating = if (nsfw || manga.isNsfw) {
|
||||
contentRating = if (nsfw || manga.contentRating == ContentRating.ADULT) {
|
||||
ContentRating.ADULT
|
||||
} else {
|
||||
ContentRating.SAFE
|
||||
|
||||
@@ -271,7 +271,7 @@ internal abstract class MangaboxParser(
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
if (doc.select(selectPage).isNullOrEmpty()) {
|
||||
if (doc.select(selectPage).isEmpty()) {
|
||||
val fullUrl2 = chapter.url.toAbsoluteUrl(domain).replace(domain, otherDomain)
|
||||
val doc2 = webClient.httpGet(fullUrl2).parseHtml()
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ import java.util.*
|
||||
|
||||
@MangaSourceParser("MANGAKAKALOT", "Mangakakalot.gg", "en")
|
||||
internal class Mangakakalot(context: MangaLoaderContext) : MangaboxParser(context, MangaParserSource.MANGAKAKALOT) {
|
||||
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(
|
||||
"www.mangakakalot.gg",
|
||||
"mangakakalot.gg",
|
||||
)
|
||||
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
@@ -132,7 +132,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : MangaboxParser(contex
|
||||
|
||||
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
|
||||
|
||||
return doc.select("div.list-truyen-item-wrap").ifEmpty {
|
||||
return doc.select("div.list-comic-item-wrap").ifEmpty {
|
||||
doc.select("div.story_item")
|
||||
}.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.mangabox.en
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
@@ -17,6 +18,7 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@Broken("Connection refused")
|
||||
@MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en")
|
||||
internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
MangaboxParser(context, MangaParserSource.MANGAKAKALOTTV) {
|
||||
@@ -119,7 +121,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
|
||||
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml()
|
||||
|
||||
return doc.select("div.list-truyen-item-wrap").ifEmpty {
|
||||
return doc.select("div.list-comic-item-wrap").ifEmpty {
|
||||
doc.select("div.story_item")
|
||||
}.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
@@ -128,7 +130,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirst("img")?.src(),
|
||||
title = div.selectFirstOrThrow("h3").text().orEmpty(),
|
||||
title = div.selectFirstOrThrow("h3").text(),
|
||||
altTitles = emptySet(),
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.ContentRating
|
||||
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -111,7 +112,7 @@ internal class Normoyun(context: MangaLoaderContext) :
|
||||
description = docs.selectFirst("span.desc")?.html(),
|
||||
state = mangaState,
|
||||
authors = setOfNotNull(author),
|
||||
contentRating = if (manga.isNsfw || nsfw) {
|
||||
contentRating = if (manga.contentRating == ContentRating.ADULT || nsfw) {
|
||||
ContentRating.ADULT
|
||||
} else {
|
||||
ContentRating.SAFE
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user