Compare commits

...

67 Commits

Author SHA1 Message Date
dragonx943 7f98a7fb5c
chore (site/vi): Update sources domain 7 months ago
Draken a39721eaf7
DoujinDesu.tv: Fix search query, add author search support (#2326) 7 months ago
Draken 65c61c0c01
Mangafire: Get/generate vrf for ajax + Add referer, fix access denied (#2310)
Co-authored-by: Vicente <vicente@per>
Co-authored-by: Draken <131387159+dragonx943@users.noreply.github.com>
Co-authored-by: Koitharu <nvasya95@gmail.com>
Co-authored-by: epikaigle444 <epikaigle444@gmail.com>
Co-authored-by: Max Bethmann <bethibande@gmail.com>
Co-authored-by: vtorres-t <73935362+vtorres-t@users.noreply.github.com>
Co-authored-by: epikaigle444 <epikaigle444@gmail.com>
Co-authored-by: Naga <94557604+NagaYZ@users.noreply.github.com>
7 months ago
dragonx943 ac977a6cbb
HentaiVN.su: Small fixes, remove broken annotation 7 months ago
Draken 8908031eee
Rawkuma: Update domain + Add broken annotation 7 months ago
Draken 4141a8f429
MimiHentai: Update API suffix, fix content not found or removed 7 months ago
Draken ad7f1eddca
Rawkuma: Use old domain, temporary fix nothing found 7 months ago
Draken 472ddab27c
GocTruyenTranhVui: Update token, fix chapters list 7 months ago
Draken 560cf63a5e
CManga: Fix missing content in getPages 7 months ago
dragonx943 d4e1acd515
Cứu Truyện: Allow to choose cover quality in source setting 7 months ago
Draken f66b97edc1
HentaiVN.su: Add source (#2295) 7 months ago
epikaigle444 a084909507
MangaMoins: Add source (#2284)
Co-authored-by: epikaigle444 <epikaigle444@gmail.com>
Co-authored-by: Koitharu <nvasya95@gmail.com>
Co-authored-by: dragonx943 <premieregirl26@gmail.com>
7 months ago
vtorres-t 58357a3745
chore: Add broken annotation for some sources (#2275)
Co-authored-by: Vicente <vicente@per>
Co-authored-by: Draken <131387159+dragonx943@users.noreply.github.com>
Co-authored-by: Koitharu <nvasya95@gmail.com>
7 months ago
Max Bethmann 8a6a529023
Mangakakalot fix getListPage (#2231) (#2285) 7 months ago
UC-01-Zer0-ID 1b9ff47ab3
ScanHentai.Menu: Update domain (#2277) 7 months ago
Draken 229f321299
Cứu Truyện: Remove broken 7 months ago
Draken a5233ede48
MangaGeko: Fix missing images in getPages (#2267) 7 months ago
dragonx943 d9025dbf81
AtsuMoe: Delete source that were never usable 7 months ago
vtorres-t 2cc0af17d5
chore: Update domains (#2264)
Co-authored-by: Vicente <vicente@per>
7 months ago
Hoàng Phi Hùng a3f384e3b3
Ổ Truyện: Fix no value for _id in getListPage (#2259)
Co-authored-by: Draken <131387159+dragonx943@users.noreply.github.com>
7 months ago
nakrovati 5882a5d54d
chore: update domains (#2255)
Close #2249, #2234, #2228, #2127
7 months ago
Koitharu f62809407b
[Madaradex] Fixes 7 months ago
Draken 14c0eaf273
Truyện Tranh Full: Add source (#2252) 7 months ago
Draken 0817a82f36
MadaraDex: Fix access denied errors in getPages
Co-authored-by: Koitharu <nvasya95@gmail.com>
7 months ago
Draken 727e326379
Cứu Truyện: Add broken 7 months ago
Harsh Dev Jha ccaf52b74e
AtsuMoe: Add source (close #2187)
Co-authored-by: Draken <131387159+dragonx943@users.noreply.github.com>
7 months ago
Harsh Dev Jha fc988cdd1e
ReadManhua: Change source name to Resetscans + Update domain
Close #2201
7 months ago
Harsh Dev Jha cb2e9841ae
YuriLab: Update domain (close #2233) 7 months ago
Draken 775063d36f
MimiHentai: Fix intercept, scrambled images in getPages (#2237) 7 months ago
Naga 86b8642456
XoxoComics: Fixes (#2229) 7 months ago
Draken 185d9b6e6e
MyReadingManga: Fix order when query is empty 7 months ago
dragonx943 e3495fc41f
MadaraDex: Add referer 7 months ago
dragonx943 d8e47b2b52
HiveComic: Remove broken 7 months ago
dragonx943 be64bf2328
MyReadingManga: Fix locale query + tags in getDetails 7 months ago
dragonx943 d85bb64b1c
HiveComic: Use API to fix content not found in getListPage 7 months ago
dragonx943 de2289eb75
FreeMangaTop: Fix access denied in getPages 7 months ago
dragonx943 7cb069d108
MyReadingManga: Improve search query with more filters 7 months ago
dragonx943 8a18df148d
ComicK: Add exception + Fix build for ci 7 months ago
dragonx943 9007852d31
MyReadingManga: Add broken 7 months ago
dragonx943 60e451303e
MadaraParser: Add some values + Fix warn 7 months ago
dragonx943 170a11ae26
ci: Update dependencies for actions 7 months ago
dragonx943 642ac08338
MadaraDex: Fix access denied in getPages 7 months ago
Draken ff256cb3ec
CBHentai: Add state to manga details 7 months ago
Hoàng Phi Hùng 08d46988a3
LXManga: Add referer, fix not found error in getPages (#2220) 7 months ago
Draken a3abc81dfc
Dâm Cô Nương: Fix chapter list not found (#2210) 8 months ago
dragonx943 41a4c90e75
ComicK: Add broken 8 months ago
dragonx943 23fdfbbef9
Madtheme: Fix warns 8 months ago
dragonx943 a4f41e41f8
MTLParser: Fix warns 8 months ago
dragonx943 db586045d7
VioletScans: Fix warns 8 months ago
dragonx943 e636eda840
Remove unused import + Fix warns 8 months ago
dragonx943 406992a6c9
DragonTranslation.org: Add source + Fix warns 8 months ago
Koitharu 30f97c5c82
Improve OkHttp coroutines integration 8 months ago
Koitharu b9ecdb2db6
[Desu] Fixes 8 months ago
Koitharu e044463f91
[Grouple] Fixes 8 months ago
Koitharu a93c760133
Fix tests running 8 months ago
dragonx943 f2b6cab251
site/vi: Update sources domain 8 months ago
dragonx943 07225568ee
BatCave: Add referer, close #2176 8 months ago
dragonx943 860dd32a53
Góc Truyện Tranh Vui: Fix warns 8 months ago
dragonx943 74f5891ffe
Góc Truyện Tranh Vui: Update token, fix chapters list 8 months ago
Draken 7bae2602c7
Merge pull request #2174 from nakrovati/build/improve-buildsrc-gradle-config
Use libs.toml.versions and `kotlin-dsl` in buildSrc build.gradle.kts
8 months ago
Draken 4c697b6406
Jitpack: Add configuration file to use JVM 17 8 months ago
Daniil Zhuravlev b61698d405 chore: use libs.toml.versions in buildSrc 8 months ago
Draken 260e5acd5c
Cứu Truyện: Change pageSize to 24, remove unused state 8 months ago
Koitharu e599f431c5
Merge pull request #2172 from nakrovati/build/remove-gradle-from-buildsrc 8 months ago
Daniil Zhuravlev 0ec4fe42c1 build: migrate to gradle 9 8 months ago
Daniil Zhuravlev c9e4c7273e build: remove gradle from buildSrc dir 8 months ago
Draken 3e2515ac6a
Cứu Truyện: Improve search query with multiple tags (#2171) 8 months ago

@ -1 +1 @@
total: 1251 total: 1255

@ -13,15 +13,13 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up enviroment 🔧 - name: Set up enviroment 🔧
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with: with:
java-version: '17' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
- name: Set up Gradle 📦 - name: Set up Gradle 📦
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
with:
cache-read-only: true
- name: Compile parsers 🚀 - name: Compile parsers 🚀
run: ./gradlew compileKotlin run: ./gradlew compileKotlin

@ -17,15 +17,13 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up enviroment 🔧 - name: Set up enviroment 🔧
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with: with:
java-version: '17' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
- name: Set up Gradle 📦 - name: Set up Gradle 📦
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
with:
cache-read-only: true
- name: Compile parsers 🚀 - name: Compile parsers 🚀
run: ./gradlew compileKotlin run: ./gradlew compileKotlin

4
.gitignore vendored

@ -92,3 +92,7 @@ local.properties
.idea/**/runConfigurations.xml .idea/**/runConfigurations.xml
.idea/**/AndroidProjectSystem.xml .idea/**/AndroidProjectSystem.xml
.idea/caches/deviceStreaming.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.api)
testImplementation(libs.junit.engine) testImplementation(libs.junit.engine)
testImplementation(libs.junit.params) testImplementation(libs.junit.params)
testRuntimeOnly(libs.junit.launcher)
testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.quickjs) testImplementation(libs.quickjs)
} }

@ -1,5 +1,5 @@
plugins { plugins {
kotlin("jvm") version "2.2.10" `kotlin-dsl`
} }
repositories { repositories {
@ -11,8 +11,7 @@ kotlin {
} }
dependencies { dependencies {
implementation(gradleApi()) implementation(libs.korte)
implementation("org.simpleframework:simple-xml:2.7.1") implementation(libs.simplexml)
implementation("com.soywiz.korlibs.korte:korte-jvm:4.0.10") implementation(libs.kotlinx.coroutines.core)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
} }

Binary file not shown.

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

47
buildSrc/gradlew vendored

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +82,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@ -133,22 +133,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

@ -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 # https://proandroiddev.com/how-we-reduced-our-gradle-build-times-by-over-80-51f2b6d6b05b
kotlin.code.style=official kotlin.code.style=official
systemProp.org.gradle.unsafe.configuration-cache=false
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=4096m -XX:+UseParallelGC org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=4096m -XX:+UseParallelGC
org.gradle.vfs.watch=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.parallel=true org.gradle.configuration-cache.problems=warn
org.gradle.caching=true
org.gradle.unsafe.configuration-cache=true ## 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" androidx-collection = "1.5.0"
jsoup = "1.21.2" jsoup = "1.21.2"
quickjs = "1.1.0" quickjs = "1.1.0"
korte = "4.0.10"
simplexml = "2.7.1"
[plugins] [plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 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" } 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-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" } 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-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-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" } 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" } androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
quickjs = { module = "io.webfolder:quickjs", version.ref = "quickjs" } 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" }

Binary file not shown.

@ -1,7 +1,7 @@
#Wed Aug 27 01:56:37 ICT 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

49
gradlew vendored

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +82,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@ -133,22 +133,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

41
gradlew.bat vendored

@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

@ -0,0 +1,2 @@
jdk:
- openjdk17

@ -5,6 +5,7 @@ import androidx.collection.SparseArrayCompat
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
@ -18,6 +19,7 @@ import java.util.*
private const val CHAPTERS_LIMIT = 99999 private const val CHAPTERS_LIMIT = 99999
@Broken("Original site closed")
@MangaSourceParser("COMICK_FUN", "ComicK") @MangaSourceParser("COMICK_FUN", "ComicK")
internal class ComickFunParser(context: MangaLoaderContext) : internal class ComickFunParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) { PagedMangaParser(context, MangaParserSource.COMICK_FUN, 20) {
@ -145,8 +147,13 @@ internal class ComickFunParser(context: MangaLoaderContext) :
) )
} }
val ja = webClient.httpGet(url.build()).parseJsonArray() val ja = try {
val tagsMap = tagsArray.get() webClient.httpGet(url.build()).parseJsonArray()
} catch (_: Exception) {
throw IllegalArgumentException("ComicK is down!")
}
val tagsMap = tagsArray.get()
return ja.mapJSON { jo -> return ja.mapJSON { jo ->
val slug = jo.getString("slug") val slug = jo.getString("slug")
Manga( Manga(

@ -19,9 +19,7 @@ import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* 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) @MangaSourceParser("KOHARU", "Schale.network", type = ContentType.HENTAI)
internal class Koharu(context: MangaLoaderContext) : internal class Koharu(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.KOHARU, 24) { 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.bitmap.Rect
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser 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 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.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.text.SimpleDateFormat
import java.util.* import java.util.Base64
import java.util.EnumSet
import java.util.Locale
import kotlin.math.min import kotlin.math.min
private const val PIECE_SIZE = 200 private const val PIECE_SIZE = 200
@ -41,6 +70,10 @@ internal abstract class MangaFireParser(
SortOrder.RELEVANCE, SortOrder.RELEVANCE,
) )
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
.add("Referer", "https://$domain/")
.build()
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys) super.onCreateConfig(keys)
keys.add(userAgentKey) keys.add(userAgentKey)
@ -95,25 +128,30 @@ internal abstract class MangaFireParser(
addQueryParameter("page", page.toString()) addQueryParameter("page", page.toString())
addQueryParameter("language[]", siteLang) addQueryParameter("language[]", siteLang)
when { when {
!filter.query.isNullOrEmpty() -> { !filter.query.isNullOrEmpty() -> {
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part -> val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
part.urlEncoded() part.urlEncoded()
} }
addEncodedQueryParameter("keyword", encodedQuery) addEncodedQueryParameter("keyword", encodedQuery)
addQueryParameter(
name = "sort", // Generate VRF for search query
value = when (order) { val searchVrf = VrfGenerator.generate(filter.query.trim())
SortOrder.UPDATED -> "recently_updated" addQueryParameter("vrf", searchVrf)
SortOrder.POPULARITY -> "most_viewed"
SortOrder.RATING -> "scores" addQueryParameter(
SortOrder.NEWEST -> "release_date" name = "sort",
SortOrder.ALPHABETICAL -> "title_az" value = when (order) {
SortOrder.RELEVANCE -> "most_relevance" SortOrder.UPDATED -> "recently_updated"
else -> "" SortOrder.POPULARITY -> "most_viewed"
}, SortOrder.RATING -> "scores"
) SortOrder.NEWEST -> "release_date"
} SortOrder.ALPHABETICAL -> "title_az"
SortOrder.RELEVANCE -> "most_relevance"
else -> ""
},
)
}
else -> { else -> {
filter.tagsExclude.forEach { tag -> filter.tagsExclude.forEach { tag ->
@ -260,14 +298,18 @@ internal abstract class MangaFireParser(
} }
} }
private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> { private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> {
val chapterElements = webClient val readVrfInput = "$mangaId@${branch.type}@${branch.langCode}"
.httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}") val readVrf = VrfGenerator.generate(readVrfInput)
.parseJson()
.getJSONObject("result") val response = webClient
.getString("html") .httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}?vrf=$readVrf")
.let(Jsoup::parseBodyFragment)
.select("ul li a") val chapterElements = response.parseJson()
.getJSONObject("result")
.getString("html")
.let(Jsoup::parseBodyFragment)
.select("ul li a")
if (branch.type == "chapter") { if (branch.type == "chapter") {
val doc = webClient val doc = webClient
@ -276,31 +318,32 @@ internal abstract class MangaFireParser(
.getString("result") .getString("result")
.let(Jsoup::parseBodyFragment) .let(Jsoup::parseBodyFragment)
doc.select("ul li a").withIndex().forEach { (i, it) -> doc.select("ul li a").withIndex().forEach { (i, it) ->
val date = it.select("span")[1].ownText() val date = it.select("span").getOrNull(1)?.ownText() ?: ""
chapterElements[i].attr("upload-date", date) chapterElements[i].attr("upload-date", date)
chapterElements[i].attr("other-title", it.attr("title")) chapterElements[i].attr("other-title", it.attr("title"))
} }
} }
return chapterElements.mapChapters(reversed = true) { _, it -> return chapterElements.mapChapters(reversed = true) { _, it ->
MangaChapter( val chapterId = it.attr("data-id")
id = generateUid(it.attr("href")), MangaChapter(
title = it.attr("title").ifBlank { id = generateUid(it.attr("href")),
"${branch.type.toTitleCase()} ${it.attr("data-number")}" title = it.attr("title").ifBlank {
}, "${branch.type.toTitleCase()} ${it.attr("data-number")}"
number = it.attr("data-number").toFloat(), },
volume = it.attr("other-title").let { number = it.attr("data-number").toFloatOrNull() ?: -1f,
volumeNumRegex.find(it)?.groupValues?.getOrNull(2)?.toInt() ?: 0 volume = it.attr("other-title").let { title ->
}, volumeNumRegex.find(title)?.groupValues?.getOrNull(2)?.toInt() ?: 0
url = "${branch.type}/${it.attr("data-id")}", },
scanlator = null, url = "$mangaId/${branch.type}/${branch.langCode}/$chapterId",
uploadDate = dateFormat.parseSafe(it.attr("upload-date")), scanlator = null,
branch = "${branch.langTitle} ${branch.type.toTitleCase()}", uploadDate = dateFormat.parseSafe(it.attr("upload-date")),
source = source, branch = "${branch.langTitle} ${branch.type.toTitleCase()}",
) source = source,
} )
} }
}
private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH) private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)
private val volumeNumRegex = Regex("""vol(ume)?\s*(\d+)""", RegexOption.IGNORE_CASE) 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> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val images = webClient val chapterId = chapter.url.substringAfterLast('/')
.httpGet("https://$domain/ajax/read/${chapter.url}") val vrf = VrfGenerator.generate("chapter@$chapterId")
.parseJson()
.getJSONObject("result") val images = webClient
.getJSONArray("images") .httpGet("https://$domain/ajax/read/chapter/$chapterId?vrf=$vrf")
.parseJson()
.getJSONObject("result")
.getJSONArray("images")
val pages = ArrayList<MangaPage>(images.length()) val pages = ArrayList<MangaPage>(images.length())
@ -491,3 +537,186 @@ internal abstract class MangaFireParser(
class PortugueseBR(context: MangaLoaderContext) : class PortugueseBR(context: MangaLoaderContext) :
MangaFireParser(context, MangaParserSource.MANGAFIRE_PTBR, "pt-br") 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 package org.koitharu.kotatsu.parsers.site.all
import okhttp3.Headers
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.generateUid import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.mapToSet 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.parseHtml
import org.koitharu.kotatsu.parsers.util.splitByWhitespace
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import org.koitharu.kotatsu.parsers.util.urlEncoded
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.EnumSet import java.util.EnumSet
import java.util.Locale import java.util.Locale
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.collections.joinToString
@MangaSourceParser("MYREADINGMANGA", "MyReadingManga", type = ContentType.HENTAI) @MangaSourceParser("MYREADINGMANGA", "MyReadingManga", type = ContentType.HENTAI)
internal class MyReadingManga(context: MangaLoaderContext) : internal class MyReadingManga(context: MangaLoaderContext) :
@ -36,19 +39,22 @@ internal class MyReadingManga(context: MangaLoaderContext) :
override val configKeyDomain = ConfigKey.Domain("myreadingmanga.info") override val configKeyDomain = ConfigKey.Domain("myreadingmanga.info")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun getRequestHeaders(): Headers = Headers.Builder()
super.onCreateConfig(keys) .add("User-Agent", "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36")
keys.add(userAgentKey) .add("X-Requested-With", randomString((1..20).random()))
} .build()
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
isOriginalLocaleSupported = true, isSearchWithFiltersSupported = true,
isMultipleTagsSupported = true,
) )
override val availableSortOrders: Set<SortOrder> = EnumSet.of( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.RELEVANCE,
SortOrder.NEWEST,
SortOrder.NEWEST_ASC,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
@ -56,6 +62,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
availableStates = EnumSet.of( availableStates = EnumSet.of(
MangaState.ONGOING, MangaState.ONGOING,
MangaState.FINISHED, MangaState.FINISHED,
MangaState.PAUSED,
MangaState.ABANDONED,
), ),
availableContentRating = EnumSet.of(ContentRating.ADULT), availableContentRating = EnumSet.of(ContentRating.ADULT),
availableLocales = setOf( availableLocales = setOf(
@ -100,6 +108,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
private fun getLanguageSlug(locale: Locale?): String? { private fun getLanguageSlug(locale: Locale?): String? {
return when { return when {
locale?.language == "en" -> "english"
locale?.language == "ja" -> "japanese"
locale?.language == "fr" -> "french" locale?.language == "fr" -> "french"
locale?.language == "ja" -> "jp" locale?.language == "ja" -> "jp"
locale?.language == "zh" && locale.country == "TW" -> "traditional-chinese" 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> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
// Add language path if specified if (page > 1) {
val langSlug = getLanguageSlug(filter.locale) append("/page/")
if (langSlug != null) { append(page)
append("/lang/") }
append(langSlug)
} // order
append("/?ep_sort=")
when { when (order) {
!filter.query.isNullOrEmpty() -> { SortOrder.NEWEST -> append("date")
// Search with language: /lang/french/page/2/?s=example SortOrder.NEWEST_ASC -> append("date_asc")
if (page > 1) { else -> append("")
append("/page/") }
append(page)
}
append("/?s=") // fix order
append(filter.query.urlEncoded()) append("&s=")
} if (!filter.query.isNullOrEmpty()) {
append(filter.query.splitByWhitespace().joinToString(separator = "+"))
filter.tags.isNotEmpty() -> { }
// Genre filtering doesn't work with language, so we ignore language for genre
if (langSlug == null) { val langSlug = getLanguageSlug(filter.locale)
append("/genre/") if (langSlug != null) {
append(filter.tags.first().key) append("&ep_filter_lang=")
append("/page/") append(langSlug)
append(page) }
append("/")
} else { if (filter.states.isNotEmpty()) {
// If both language and genre are selected, just use language append("&ep_filter_status=")
append("/page/") filter.states.oneOrThrowIfMany()?.let {
append(page) append(
append("/") when (it) {
} MangaState.ONGOING -> "ongoing"
} MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "hiatus"
filter.states.isNotEmpty() -> { MangaState.ABANDONED -> "dropped"
// Status filtering doesn't work with language either else -> ""
if (langSlug == null) { }
append("/status/") )
append( }
when (filter.states.first()) { }
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed" if (filter.tags.isNotEmpty()) {
else -> "ongoing" append("&ep_filter_genre=")
}, append(filter.tags.joinToString(",") { it.key })
) }
append("/page/")
append(page)
append("/")
} else {
// If both language and status are selected, just use language
append("/page/")
append(page)
append("/")
}
}
else -> {
// Regular browsing with or without language
append("/page/")
append(page)
append("/")
}
}
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
@ -287,10 +278,10 @@ internal class MyReadingManga(context: MangaLoaderContext) :
val genres = mutableSetOf<MangaTag>() 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( genres.add(
MangaTag( MangaTag(
title = it.text(), title = it.text().removeSuffix(",").trim(),
key = it.attr("href").substringAfterLast("/genre/").substringBefore("/"), key = it.attr("href").substringAfterLast("/genre/").substringBefore("/"),
source = source, source = source,
), ),
@ -300,6 +291,8 @@ internal class MyReadingManga(context: MangaLoaderContext) :
val state = when (doc.select("a[href*=status]").firstOrNull()?.text()) { val state = when (doc.select("a[href*=status]").firstOrNull()?.text()) {
"Ongoing" -> MangaState.ONGOING "Ongoing" -> MangaState.ONGOING
"Completed" -> MangaState.FINISHED "Completed" -> MangaState.FINISHED
"Hiatus" -> MangaState.PAUSED
"Dropped" -> MangaState.ABANDONED
else -> null else -> null
} }
@ -447,5 +440,10 @@ internal class MyReadingManga(context: MangaLoaderContext) :
0L 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 val configKeyDomain = ConfigKey.Domain("batcave.biz")
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
.add("Referer", "https://$domain/")
.build()
private val availableTags = suspendLazy(initializer = ::fetchTags) private val availableTags = suspendLazy(initializer = ::fetchTags)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.Broken
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@Broken // The website is broken, it seems to be closed already. @Broken("Original site closed")
@MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS) @MangaSourceParser("COMICEXTRA", "ComicExtra", "en", ContentType.COMICS)
internal class ComicExtra(context: MangaLoaderContext) : internal class ComicExtra(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) { PagedMangaParser(context, MangaParserSource.COMICEXTRA, 36) {

@ -156,18 +156,21 @@ internal class MangaGeko(context: MangaLoaderContext) :
} }
} }
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain) val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
return doc.select("center img")
return doc.requireElementById("chapter-reader").select("img").map { img -> .mapNotNull { it.attr("src").takeIf { src -> src.isNotBlank() } }
val url = img.requireSrc().toRelativeUrl(domain) // remove all invaild images + credits
MangaPage( .filterNot { it.startsWith("data:image") || it.contains("credits-mgeko.png") }
id = generateUid(url), .distinct().map { url ->
url = url, val finalUrl = url.toRelativeUrl(domain)
preview = null, MangaPage(
source = source, 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey 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.*
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.generateUid
import org.koitharu.kotatsu.parsers.util.parseHtml import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
@ -23,7 +16,12 @@ import java.util.Locale
@MangaSourceParser("VIOLETSCANS", "VioletScans", "en") @MangaSourceParser("VIOLETSCANS", "VioletScans", "en")
internal class VioletScans(context: MangaLoaderContext) : 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") 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 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 getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
override suspend fun getListPage(query: MangaSearchQuery, page: Int): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
var searchParameter = "" return if (!filter.query.isNullOrEmpty()) {
query.criteria.forEach { criterion -> scrapeSearchList(filter.query, page)
when (criterion) { } else {
is QueryCriteria.Match<*> -> { scrapeNonSearchList(page)
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)
}
} }
private suspend fun scrapeNonSearchList(page: Int): List<Manga> { 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 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 chaptersList = root.selectFirstOrThrow("#chapterlist ul")
val chapters = chaptersList.select("li") 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.* 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.util.*
import org.koitharu.kotatsu.parsers.core.FlexiblePagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.exception.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -18,7 +13,7 @@ internal abstract class MTLParser(
context: MangaLoaderContext, context: MangaLoaderContext,
source: MangaParserSource, source: MangaParserSource,
domain: String, domain: String,
) : FlexiblePagedMangaParser(context, source, 24) { ) : PagedMangaParser(context, source, 24) {
override val configKeyDomain = ConfigKey.Domain(domain) override val configKeyDomain = ConfigKey.Domain(domain)
@ -30,21 +25,18 @@ internal abstract class MTLParser(
override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions() override suspend fun getFilterOptions(): MangaListFilterOptions = MangaListFilterOptions()
override val searchQueryCapabilities = MangaSearchQueryCapabilities( override val filterCapabilities: MangaListFilterCapabilities
SearchCapability( get() = MangaListFilterCapabilities(
field = SearchableField.TITLE_NAME, isSearchSupported = true,
criteriaTypes = setOf(Match::class), )
isMultiple = false,
),
)
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 { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
append("/search") append("/search")
append("?") append("?")
when (query.order) { when (order) {
SortOrder.POPULARITY -> append("sort_by=views") SortOrder.POPULARITY -> append("sort_by=views")
SortOrder.UPDATED -> append("sort_by=recent") SortOrder.UPDATED -> append("sort_by=recent")
else -> append("sort_by=recent") else -> append("sort_by=recent")
@ -53,19 +45,10 @@ internal abstract class MTLParser(
append("&page=") append("&page=")
append(page) append(page)
} }
query.criteria.find { it.field == SearchableField.TITLE_NAME }?.let { criteria -> append("&q=")
when (criteria) { if (!filter.query.isNullOrEmpty()) {
is Match -> { append(filter.query.urlEncoded())
append("&q=") }
append(criteria.value.toString())
}
is Include,
is Exclude,
is Range,
-> Unit // Not supported for this field
}
}
} }
val doc = webClient.httpGet(url).parseHtml() val doc = webClient.httpGet(url).parseHtml()
@ -190,7 +173,7 @@ internal abstract class MTLParser(
val sdf = SimpleDateFormat("dd MMMM yyyy", Locale.ENGLISH) val sdf = SimpleDateFormat("dd MMMM yyyy", Locale.ENGLISH)
sdf.timeZone = TimeZone.getTimeZone("UTC") sdf.timeZone = TimeZone.getTimeZone("UTC")
sdf.parse(dateString)?.time ?: 0L sdf.parse(dateString)?.time ?: 0L
} catch (e: Exception) { } catch (_: Exception) {
0L 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.site.en.MTL.MTLParser
@Broken
@MangaSourceParser("SNOWMTL", "SnowMTL", "en", type = ContentType.OTHER) @MangaSourceParser("SNOWMTL", "SnowMTL", "en", type = ContentType.OTHER)
internal class SnowMTL(context: MangaLoaderContext): 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.site.en.MTL.MTLParser
@Broken
@MangaSourceParser("SOLARMTL", "SolarMTL", "en", type = ContentType.MANGA) @MangaSourceParser("SOLARMTL", "SolarMTL", "en", type = ContentType.MANGA)
internal class SolarMTL(context: MangaLoaderContext): 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 org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@Broken // Website closed @Broken("Redirect to DragonTranslation.org source")
@MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es") @MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es")
internal class DragonTranslationParser(context: MangaLoaderContext) : internal class DragonTranslationParser(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.DRAGONTRANSLATION, 30) { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
@Broken // Not dead, changed template @Broken("Not dead, changed template")
@MangaSourceParser("TEMPLESCAN", "TempleScan", "en") @MangaSourceParser("TEMPLESCAN", "TempleScan", "en")
internal class TempleScan(context: MangaLoaderContext) : internal class TempleScan(context: MangaLoaderContext) :
HeanCms(context, MangaParserSource.TEMPLESCAN, "templetoons.com") { HeanCms(context, MangaParserSource.TEMPLESCAN, "templetoons.com") {
override val pathManga = "comic" override val pathManga = "comic"
override val apiPath: String 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.model.*
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms 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) @MangaSourceParser("YUGEN_MANGAS_ES", "YugenMangas.lat", "es", ContentType.HENTAI)
internal class YugenMangasEs(context: MangaLoaderContext) : internal class YugenMangasEs(context: MangaLoaderContext) :
HeanCms(context, MangaParserSource.YUGEN_MANGAS_ES, "lectorikigai.acamu.net") 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.site.heancmsalt.HeanCmsAlt
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("BRAKEOUT", "Brakeout", "es") @MangaSourceParser("BRAKEOUT", "Brakeout", "es")
internal class Brakeout(context: MangaLoaderContext) : internal class Brakeout(context: MangaLoaderContext) :
HeanCmsAlt(context, MangaParserSource.BRAKEOUT, "brakeout.xyz", 10) { 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt
import org.koitharu.kotatsu.parsers.Broken
@Broken("Not dead, changed template")
@MangaSourceParser("LEGIONSCANS", "CerberusSeries", "es") @MangaSourceParser("LEGIONSCANS", "CerberusSeries", "es")
internal class CerberuSeries(context: MangaLoaderContext) : 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt import org.koitharu.kotatsu.parsers.site.heancmsalt.HeanCmsAlt
@Broken // Not dead but changed template @Broken("Not dead, changed template")
@MangaSourceParser("MANGAESP", "MangaEsp", "es") @MangaSourceParser("MANGAESP", "MangaEsp", "es")
internal class MangaEsp(context: MangaLoaderContext) : internal class MangaEsp(context: MangaLoaderContext) :
HeanCmsAlt(context, MangaParserSource.MANGAESP, "mangaesp.topmanhuas.org", 15) { HeanCmsAlt(context, MangaParserSource.MANGAESP, "mangaesp.topmanhuas.org", 15) {

@ -30,6 +30,7 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = true, isSearchWithFiltersSupported = true,
isAuthorSearchSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( 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> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = urlBuilder().apply { val url = urlBuilder().apply {
addPathSegment("manga") when {
addPathSegment("page") page > 1 -> addPathSegments("manga/page/$page/")
addPathSegment("$page/") else -> addPathSegment("manga/")
}
addQueryParameter( addQueryParameter(
"title", "title",
filter.query?.let { filter.query?.let {
filter.query filter.query
}, },
) )
addQueryParameter(
name = "author",
value = filter.author?.let { it
space2plus(it).lowercase()
}
)
addQueryParameter( addQueryParameter(
"order", "order",
when (order) { when (order) {
@ -97,14 +106,6 @@ internal class DoujinDesuParser(context: MangaLoaderContext) :
}, },
) )
} }
// Author
// addQueryParameter("author",
// filter.author?.let {
// filter.author
// }
// )
}.build() }.build()
return webClient.httpGet(url).parseHtml() 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 val key = it.attrOrNull("value") ?: return@mapNotNullToSet null
MangaTag( MangaTag(
key = key, key = key,
title = (it.text() ?: key).toTitleCase(sourceLocale), title = it.text().ifBlank { key }.toTitleCase(sourceLocale),
source = source, source = source,
) )
} }
} }
@ -201,7 +201,7 @@ internal abstract class IkenParser(
protected fun Document.getNextJson(key: String): String { protected fun Document.getNextJson(key: String): String {
val scripts = select("script") val scripts = select("script")
val scriptData = scripts.find { script -> val scriptData = scripts.find { script ->
script.data()?.contains(key) == true script.data().contains(key)
}?.data() ?: throw Exception("Unable to retrieve NEXT data") }?.data() ?: throw Exception("Unable to retrieve NEXT data")
val keyIndex = scriptData.indexOf(key) 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.iken.IkenParser import org.koitharu.kotatsu.parsers.site.iken.IkenParser
import org.koitharu.kotatsu.parsers.Broken
@Broken("Need to fix getPages")
@MangaSourceParser("HIVECOMIC", "HiveComic", "en") @MangaSourceParser("HIVECOMIC", "HiveComic", "en")
internal class HiveComic(context: MangaLoaderContext) : 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.iken.IkenParser import org.koitharu.kotatsu.parsers.site.iken.IkenParser
@Broken // Redirect to @VORTEXSCANS @Broken("Redirect to VortexScans")
@MangaSourceParser("MANGAGALAXY", "MangaGalaxy", "en") @MangaSourceParser("MANGAGALAXY", "MangaGalaxy", "en")
internal class MangaGalaxyParser(context: MangaLoaderContext) : 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", "en curso",
"ongoing", "ongoing",
"on going", "on going",
"OnGoing",
"ativo", "ativo",
"en cours", "en cours",
"en cours \uD83D\uDFE2", "en cours \uD83D\uDFE2",
@ -478,7 +479,7 @@ internal abstract class MadaraParser(
return elements.map { div -> return elements.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary") 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( Manga(
id = generateUid(href), id = generateUid(href),
url = 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(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(É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(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 = protected open val selectAlt =
".post-content_item:contains(Alt) .summary-content, .post-content_item:contains(Nomes alternativos: ) .summary-content" ".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 href = doc.selectFirst("head meta[property='og:url']")?.attr("content")?.toRelativeUrl(domain) ?: manga.url
val testCheckAsync = doc.select(selectTestAsync) val testCheckAsync = doc.select(selectTestAsync)
val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) { val chaptersDeferred = if (testCheckAsync.isEmpty()) {
async { loadChapters(href, doc) } async { loadChapters(href, doc) }
} else { } else {
async { getChapters(manga, doc) } 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("COMICARAB", "ComicArab", "ar") @MangaSourceParser("COMICARAB", "ComicArab", "ar")
internal class ComicArab(context: MangaLoaderContext) : internal class ComicArab(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.COMICARAB, "comicarab.com", pageSize = 24) { 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("GATEMANGA", "GateManga", "ar") @MangaSourceParser("GATEMANGA", "GateManga", "ar")
internal class GateManga(context: MangaLoaderContext) : internal class GateManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.GATEMANGA, "gatemanga.com") { MadaraParser(context, MangaParserSource.GATEMANGA, "gatemanga.com") {

@ -9,4 +9,8 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
internal class FreeMangaTop(context: MangaLoaderContext) : internal class FreeMangaTop(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.FREEMANGATOP, "freemangatop.com") { MadaraParser(context, MangaParserSource.FREEMANGATOP, "freemangatop.com") {
override val datePattern = "MM/dd/yyyy" 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @hentai20 @Broken("Redirect to @hentai20")
@MangaSourceParser("HENTAI3Z", "Hentai3z", "en", ContentType.HENTAI) @MangaSourceParser("HENTAI3Z", "Hentai3z", "en", ContentType.HENTAI)
internal class Hentai3z(context: MangaLoaderContext) : internal class Hentai3z(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.HENTAI3Z, "manga18h.xyz", pageSize = 20) { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @MortalsGroove @Broken("Redirect to @MortalsGroove")
@MangaSourceParser("IMMORTALUPDATES", "ImmortalUpdates", "en") @MangaSourceParser("IMMORTALUPDATES", "ImmortalUpdates", "en")
internal class ImmortalUpdates(context: MangaLoaderContext) : internal class ImmortalUpdates(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.IMMORTALUPDATES, "immortalupdates.com") { 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 org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.* import java.util.*
@Broken // Redirect to @XMANHWA @Broken("Redirect to @XMANHWA")
@MangaSourceParser("INSTAMANHWA", "InstaManhwa", "en", ContentType.HENTAI) @MangaSourceParser("INSTAMANHWA", "InstaManhwa", "en", ContentType.HENTAI)
internal class InstaManhwa(context: MangaLoaderContext) : internal class InstaManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.INSTAMANHWA, "www.manhwaden.com", 15) { MadaraParser(context, MangaParserSource.INSTAMANHWA, "www.manhwaden.com", 15) {

@ -1,15 +1,90 @@
package org.koitharu.kotatsu.parsers.site.madara.en 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser 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) @MangaSourceParser("MADARADEX", "MadaraDex", "en", ContentType.HENTAI)
internal class MadaraDex(context: MangaLoaderContext) : internal class MadaraDex(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.MADARADEX, "madaradex.org") { MadaraParser(context, MangaParserSource.MADARADEX, "madaradex.org") {
override val listUrl = "title/"
override val tagPrefix = "genre/" init {
override val postReq = true 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @hentai20 @Broken("Redirect to @hentai20")
@MangaSourceParser("MANGA18H", "Manga18h", "en", ContentType.HENTAI) @MangaSourceParser("MANGA18H", "Manga18h", "en", ContentType.HENTAI)
internal class Manga18h(context: MangaLoaderContext) : internal class Manga18h(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.MANGA18H, "manga18h.xyz", 20) 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @MANGAREAD @Broken("Redirect to @MANGAREAD")
@MangaSourceParser("MANGAEFFECT", "MangaEffect", "en") @MangaSourceParser("MANGAEFFECT", "MangaEffect", "en")
internal class MangaEffect(context: MangaLoaderContext) : internal class MangaEffect(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.MANGAEFFECT, "www.mangaread.org") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("RESETSCANS", "ReadManhua", "en") @MangaSourceParser("RESETSCANS", "Resetscans", "en")
internal class ResetScans(context: MangaLoaderContext) : 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("SCANSRAW", "AquaScans.com", "en") @MangaSourceParser("SCANSRAW", "AquaScans.com", "en")
internal class Scansraw(context: MangaLoaderContext) : internal class Scansraw(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.SCANSRAW, "aquascans.com", 10) 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("STKISSMANGABLOG", "1StKissManga.net", "en") @MangaSourceParser("STKISSMANGABLOG", "1StKissManga.net", "en")
internal class StkissMangaBlog(context: MangaLoaderContext) : internal class StkissMangaBlog(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.STKISSMANGABLOG, "1stkissmanga.org", 20) { 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("STKISSMANGA_COM", "1stKissManga.com", "en") @MangaSourceParser("STKISSMANGA_COM", "1stKissManga.com", "en")
internal class StkissMangaCom(context: MangaLoaderContext) : internal class StkissMangaCom(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.STKISSMANGA_COM, "1stkissmanga.mom", 10) { 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("COCORIP", "Cocorip", "es") @MangaSourceParser("COCORIP", "Cocorip", "es")
internal class Cocorip(context: MangaLoaderContext) : internal class Cocorip(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.COCORIP, "cocorip.net", 16) { 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("DAPROB", "Daprob", "es") @MangaSourceParser("DAPROB", "Daprob", "es")
internal class Daprob(context: MangaLoaderContext) : internal class Daprob(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.DAPROB, "daprob.com") { 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("DARKNEBULUS", "Darknebulus", "es") @MangaSourceParser("DARKNEBULUS", "Darknebulus", "es")
internal class Darknebulus(context: MangaLoaderContext) : internal class Darknebulus(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.DARKNEBULUS, "www.darknebulus.com") 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") @MangaSourceParser("EMPERORSCAN", "EmperorScan", "es")
internal class EmperorScan(context: MangaLoaderContext) : 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken @Broken("Not dead, changed template")
@MangaSourceParser("HAREMSCANN", "HaremScann", "es") @MangaSourceParser("HAREMSCANN", "HaremScann", "es")
internal class HaremScann(context: MangaLoaderContext) : internal class HaremScann(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.HAREMSCANN, "haremscann.es") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken @Broken("Not dead, changed template")
@MangaSourceParser("HOUSEMANGAS", "HouseMangas", "es") @MangaSourceParser("HOUSEMANGAS", "HouseMangas", "es")
internal class HouseMangas(context: MangaLoaderContext) : 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("INMORALNOFANSUB", "InmoralNoFansub", "es") @MangaSourceParser("INMORALNOFANSUB", "InmoralNoFansub", "es")
internal class InmoralNoFansub(context: MangaLoaderContext) : internal class InmoralNoFansub(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.INMORALNOFANSUB, "inmoralnofansub.xyz") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("JEAZTWOBLUESCANS", "Marcialhub", "es") @MangaSourceParser("JEAZTWOBLUESCANS", "Lector HUB", "es")
internal class JeazTwoBlueScans(context: MangaLoaderContext) : internal class JeazTwoBlueScans(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.JEAZTWOBLUESCANS, "marcialhub.xyz") { MadaraParser(context, MangaParserSource.JEAZTWOBLUESCANS, "lectorhub.j5z.xyz") {
override val datePattern = "d MMMM, yyyy" 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("KNIGHTNOSCANLATION", "TwoBlueScans", "es") @MangaSourceParser("KNIGHTNOSCANLATION", "Lector KNS", "es")
internal class KnightnoScanlation(context: MangaLoaderContext) : internal class KnightnoScanlation(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.KNIGHTNOSCANLATION, "kns.cookni.net") { MadaraParser(context, MangaParserSource.KNIGHTNOSCANLATION, "lectorknight.com") {
override val listUrl = "sr/" override val listUrl = "sr/"
override val tagPrefix = "generos/" 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Not dead but changed template @Broken("Not dead, changed template")
@MangaSourceParser("KOINOBORISCAN", "KoinoboriScan", "es") @MangaSourceParser("KOINOBORISCAN", "KoinoboriScan", "es")
internal class KoinoboriScan(context: MangaLoaderContext) : internal class KoinoboriScan(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.KOINOBORISCAN, "koinoboriscan.com") { MadaraParser(context, MangaParserSource.KOINOBORISCAN, "visorkoi.com") {
override val postReq = true override val postReq = true
} }

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.parsers.site.madara.es 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken
@MangaSourceParser("LEGENDSCANLATIONS", "LegendScanlations", "es") @MangaSourceParser("LEGENDSCANLATIONS", "LegendScanlations", "es")
internal class LegendScanlations(context: MangaLoaderContext) : 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" 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken @Broken("Not dead, changed template")
@MangaSourceParser("JOBSIBE", "Jobsibe", "es") @MangaSourceParser("LMTOS", "Lmtos", "es")
internal class Jobsibe(context: MangaLoaderContext) : internal class Lmtos(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.JOBSIBE, "jobsibe.com") { MadaraParser(context, MangaParserSource.LMTOS, "lmtos.com") {
override val datePattern = "dd/MM" override val datePattern = "dd/MM"
} }

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGA_CRAB", "MangaCrab", "es") @MangaSourceParser("MANGA_CRAB", "MangaCrab", "es")
internal class MangaCrab(context: MangaLoaderContext) : 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 datePattern = "dd/MM/yyyy"
override val tagPrefix = "manga-genero/" override val tagPrefix = "manga-genero/"
override val listUrl = "series/" override val listUrl = "series/"

@ -1,15 +1,13 @@
package org.koitharu.kotatsu.parsers.site.madara.es 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.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken
@MangaSourceParser("MANTRAZSCAN", "MantrazScan", "es") @MangaSourceParser("MANTRAZSCAN", "MantrazScan", "es")
internal class MantrazScan(context: MangaLoaderContext) : 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 datePattern = "dd/MM/yyyy"
override val tagPrefix = "generos-de-manga/" override val tagPrefix = "generos-de-manga/"
} }

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MHSCANS", "MhScans", "es") @MangaSourceParser("MHSCANS", "MhScans", "es")
internal class MhScans(context: MangaLoaderContext) : 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 datePattern = "d 'de' MMMMM 'de' yyyy"
override val listUrl = "series/" 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("MMDAOS", "Mmdaos", "es") @MangaSourceParser("MMDAOS", "Mmdaos", "es")
internal class Mmdaos(context: MangaLoaderContext) : internal class Mmdaos(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.MMDAOS, "mmdaos.com") 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("MONARCAMANGA", "MonarcaManga", "es") @MangaSourceParser("MONARCAMANGA", "MonarcaManga", "es")
internal class MonarcaManga(context: MangaLoaderContext) : internal class MonarcaManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.MONARCAMANGA, "visormonarca.com") { MadaraParser(context, MangaParserSource.MONARCAMANGA, "visormonarca.com") {

@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("NOBLESSETRANSLATIONS", "NoblesseTranslations", "es") @MangaSourceParser("NOBLESSETRANSLATIONS", "NoblesseTranslations", "es")
internal class NoblesseTranslations(context: MangaLoaderContext) : 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 @Broken
@MangaSourceParser("RAGNAROKSCAN", "RagnarokScan", "es") @MangaSourceParser("RAGNAROKSCAN", "RagnarokScan", "es")
internal class RagnarokScan(context: MangaLoaderContext) : internal class RagnarokScan(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.RAGNAROKSCAN, "ragnarokscan.com") { MadaraParser(context, MangaParserSource.RAGNAROKSCAN, "ragnarokscan.com") {
override val stylePage = "" override val stylePage = ""
override val listUrl = "series/" override val listUrl = "series/"
override val tagPrefix = "genero/" override val tagPrefix = "genero/"
} }

@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("RAGNAROKSCANLATION", "RagnarokScanlation", "es") @MangaSourceParser("RAGNAROKSCANLATION", "RagnarokScanlation", "es")
internal class RagnarokScanlation(context: MangaLoaderContext) : 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") @MangaSourceParser("RICHTOSCAN", "RichtoScan", "es")
internal class RichtoScan(context: MangaLoaderContext) : 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 tagPrefix = "manga-generos/"
override val sourceLocale: Locale = Locale.ENGLISH override val sourceLocale: Locale = Locale.ENGLISH
} }

@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("SAMURAISCAN", "SamuraiScan", "es") @MangaSourceParser("SAMURAISCAN", "SamuraiScan", "es")
internal class SamuraiScan(context: MangaLoaderContext) : internal class SamuraiScan(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.SAMURAISCAN, "latan.visorsmr.com", 10) { MadaraParser(context, MangaParserSource.SAMURAISCAN, "samuraiscan.com", 10) {
override val listUrl = "read/" override val listUrl = "read/"
} }

@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TAURUSMANGA", "TaurusManga", "es") @MangaSourceParser("TAURUSMANGA", "TaurusManga", "es")
internal class TaurusManga(context: MangaLoaderContext) : internal class TaurusManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.TAURUSMANGA, "taurusmanga.com") { MadaraParser(context, MangaParserSource.TAURUSMANGA, "taurus.topmanhuas.org") {
override val datePattern = "dd/MM/yyyy" 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("TECNOPROJECTS", "TecnoProjects", "es") @MangaSourceParser("TECNOPROJECTS", "TecnoProjects", "es")
internal class TecnoProjects(context: MangaLoaderContext) : 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" override val datePattern = "dd 'de' MMMM 'de' yyyy"
} }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.site.madara.es package org.koitharu.kotatsu.parsers.site.madara.es
import org.koitharu.kotatsu.parsers.Broken
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
@Broken
@MangaSourceParser("TMOMANGA", "TmoManga", "es") @MangaSourceParser("TMOMANGA", "TmoManga", "es")
internal class TmoManga(context: MangaLoaderContext) : internal class TmoManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.TMOMANGA, "tmomanga.com") { 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.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken
@Broken
@MangaSourceParser("ZEVEP", "Zevep", "es") @MangaSourceParser("ZEVEP", "Zevep", "es")
internal class Zevep(context: MangaLoaderContext) : internal class Zevep(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.ZEVEP, "zevep.com", 16) 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) @MangaSourceParser("SCANHENTAIMENU", "ScanHentai.Menu", "fr", ContentType.HENTAI)
internal class ScanHentaiMenu(context: MangaLoaderContext) : 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Not dead, changed template @Broken("Not dead, changed template")
@MangaSourceParser("CERISE_SCANS", "CeriseScans", "pt") @MangaSourceParser("CERISE_SCANS", "CeriseScans", "pt")
internal class CeriseScans(context: MangaLoaderContext) : internal class CeriseScans(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.CERISE_SCANS, "cerise.leitorweb.com") { 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) @MangaSourceParser("CVNSCAN", "CvnScan", "pt", ContentType.HENTAI)
internal class CvnScan(context: MangaLoaderContext) : 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@Broken // Website closed @Broken("Original site closed")
@MangaSourceParser("GALINHASAMURAI", "GalinhaSamurai", "pt") @MangaSourceParser("GALINHASAMURAI", "GalinhaSamurai", "pt")
internal class GalinhaSamurai(context: MangaLoaderContext) : internal class GalinhaSamurai(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.GALINHASAMURAI, "galinhasamurai.com") { 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.Broken 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) @MangaSourceParser("GOOFFANSUB", "GoofFansub", "pt", ContentType.HENTAI)
internal class GoofFansub(context: MangaLoaderContext) : internal class GoofFansub(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.GOOFFANSUB, "gooffansub.com") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Not dead, changed template @Broken("Not dead, changed template")
@MangaSourceParser("SINENSISSCANS", "SinensisScans", "pt") @MangaSourceParser("SINENSISSCANS", "SinensisScans", "pt")
internal class SinensisScans(context: MangaLoaderContext) : internal class SinensisScans(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.SINENSISSCANS, "sinensis.leitorweb.com") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @KINGS_MANGA @Broken("Redirect to @KINGS_MANGA")
@MangaSourceParser("NEKOPOST", "NekoPost", "th", ContentType.HENTAI) @MangaSourceParser("NEKOPOST", "NekoPost", "th", ContentType.HENTAI)
internal class NekoPost(context: MangaLoaderContext) : internal class NekoPost(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.NEKOPOST, "www.superdoujin.org") { 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.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @WEBTOONHATTI @Broken("Redirect to @WEBTOONHATTI")
@MangaSourceParser("CLOVERMANGA", "CloverManga", "tr") @MangaSourceParser("CLOVERMANGA", "CloverManga", "tr")
internal class CloverManga(context: MangaLoaderContext) : internal class CloverManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.CLOVERMANGA, "webtoonhatti.me", 20) MadaraParser(context, MangaParserSource.CLOVERMANGA, "webtoonhatti.me", 20)

@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @GRIMELEK @Broken("Redirect to @GRIMELEK")
@MangaSourceParser("GHOSTFANSUB", "GhostFansub", "tr") @MangaSourceParser("GHOSTFANSUB", "GhostFansub", "tr")
internal class GhostFansub(context: MangaLoaderContext) : internal class GhostFansub(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.GHOSTFANSUB, "ghostfansub.co", 18) MadaraParser(context, MangaParserSource.GHOSTFANSUB, "ghostfansub.co", 18)

@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@Broken // Redirect to @MANGAGEZGINI @Broken("Redirect to @MANGAGEZGINI")
@MangaSourceParser("GLORYMANGA", "GloryManga", "tr") @MangaSourceParser("GLORYMANGA", "GloryManga", "tr")
internal class GloryManga(context: MangaLoaderContext) : internal class GloryManga(context: MangaLoaderContext) :
MadaraParser(context, MangaParserSource.GLORYMANGA, "mangagezgini.site", 18) { 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) @MangaSourceParser("HENTAIVNPLUS", "HentaiVN.plus", "vi", ContentType.HENTAI)
internal class HentaiVnPlus(context: MangaLoaderContext) : 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 listUrl = "truyen-hentai/"
override val tagPrefix = "the-loai/" override val tagPrefix = "the-loai/"
override val datePattern = "dd/MM/yyyy" 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.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.*
import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.Broken
@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 href = doc.selectFirst("head meta[property='og:url']")?.attr("content")?.toRelativeUrl(domain) ?: manga.url
val testCheckAsync = doc.select(selectTestAsync) val testCheckAsync = doc.select(selectTestAsync)
val chaptersDeferred = if (testCheckAsync.isNullOrEmpty()) { val chaptersDeferred = if (testCheckAsync.isEmpty()) {
async { loadChapters(href, doc) } async { loadChapters(href, doc) }
} else { } else {
async { getChapters(manga, doc) } async { getChapters(manga, doc) }

@ -11,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
@MangaSourceParser("QUAANHDAOCUTEO", "Quả Anh Đào Cuteo", "vi", ContentType.HENTAI) @MangaSourceParser("QUAANHDAOCUTEO", "Quả Anh Đào Cuteo", "vi", ContentType.HENTAI)
internal class Quaanhdaocuteo(context: MangaLoaderContext) : 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 datePattern = "dd/MM/yyyy"
override val selectPage = "p img" 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.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -141,7 +142,7 @@ internal abstract class MadthemeParser(
private suspend fun fetchAvailableTags(): Set<MangaTag> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox -> 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 val name = checkbox.selectFirst("span.radio__label")?.text() ?: key
MangaTag( MangaTag(
key = key, key = key,
@ -190,7 +191,7 @@ internal abstract class MadthemeParser(
altTitles = setOfNotNull(alt.nullIfEmpty()), altTitles = setOfNotNull(alt.nullIfEmpty()),
state = state, state = state,
chapters = chaptersDeferred.await(), chapters = chaptersDeferred.await(),
contentRating = if (nsfw || manga.isNsfw) { contentRating = if (nsfw || manga.contentRating == ContentRating.ADULT) {
ContentRating.ADULT ContentRating.ADULT
} else { } else {
ContentRating.SAFE ContentRating.SAFE

@ -271,7 +271,7 @@ internal abstract class MangaboxParser(
val fullUrl = chapter.url.toAbsoluteUrl(domain) val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml() 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 fullUrl2 = chapter.url.toAbsoluteUrl(domain).replace(domain, otherDomain)
val doc2 = webClient.httpGet(fullUrl2).parseHtml() val doc2 = webClient.httpGet(fullUrl2).parseHtml()

@ -132,7 +132,7 @@ internal class Mangakakalot(context: MangaLoaderContext) : MangaboxParser(contex
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml() 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") doc.select("div.story_item")
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") 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.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey 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 org.koitharu.kotatsu.parsers.util.*
import java.util.* import java.util.*
@Broken("Connection refused")
@MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en") @MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en")
internal class MangakakalotTv(context: MangaLoaderContext) : internal class MangakakalotTv(context: MangaLoaderContext) :
MangaboxParser(context, MangaParserSource.MANGAKAKALOTTV) { MangaboxParser(context, MangaParserSource.MANGAKAKALOTTV) {
@ -119,7 +121,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
val doc = webClient.httpGet(titleSearchUrl ?: url).parseHtml() 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") doc.select("div.story_item")
}.map { div -> }.map { div ->
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
@ -128,7 +130,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
url = href, url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain), publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src(), coverUrl = div.selectFirst("img")?.src(),
title = div.selectFirstOrThrow("h3").text().orEmpty(), title = div.selectFirstOrThrow("h3").text(),
altTitles = emptySet(), altTitles = emptySet(),
rating = RATING_UNKNOWN, rating = RATING_UNKNOWN,
tags = emptySet(), tags = emptySet(),

@ -4,6 +4,7 @@ import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* 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.site.mangareader.MangaReaderParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -111,7 +112,7 @@ internal class Normoyun(context: MangaLoaderContext) :
description = docs.selectFirst("span.desc")?.html(), description = docs.selectFirst("span.desc")?.html(),
state = mangaState, state = mangaState,
authors = setOfNotNull(author), authors = setOfNotNull(author),
contentRating = if (manga.isNsfw || nsfw) { contentRating = if (manga.contentRating == ContentRating.ADULT || nsfw) {
ContentRating.ADULT ContentRating.ADULT
} else { } else {
ContentRating.SAFE ContentRating.SAFE

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save