Compare commits

..

No commits in common. "56f64ce41c2b0041c2fb6f01692b2a62d293e695" and "152405613e1644c60544d49094223818d8979a1e" have entirely different histories.

884 changed files with 5670 additions and 7878 deletions

2
.gitattributes vendored
View File

@ -1,2 +0,0 @@
/gradlew linguist-generated
/gradlew.bat linguist-generated

View File

@ -198,7 +198,7 @@ The simplest extension structure looks like this:
```console
$ tree src/<lang>/<mysourcename>/
src/<lang>/<mysourcename>/
├── AndroidManifest.xml (optional)
├── AndroidManifest.xml
├── build.gradle
├── res
│   ├── mipmap-hdpi
@ -227,14 +227,15 @@ src/<lang>/<mysourcename>/
should be adapted from the site name, and can only contain lowercase ASCII letters and digits.
Your extension code must be placed in the package `eu.kanade.tachiyomi.extension.<lang>.<mysourcename>`.
#### AndroidManifest.xml (optional)
You only need to create this file if you want to add deep linking to your extension.
See [URL intent filter](#url-intent-filter) for more information.
#### AndroidManifest.xml
A minimal [Android manifest file](https://developer.android.com/guide/topics/manifest/manifest-intro)
is needed for Android to recognize an extension when it's compiled into an APK file. You can also add
intent filters inside this file (see [URL intent filter](#url-intent-filter) for more information).
#### build.gradle
Make sure that your new extension's `build.gradle` file follows the following structure:
```groovy
```gradle
ext {
extName = '<My source name>'
extClass = '.<MySourceName>'
@ -250,9 +251,10 @@ apply from: "$rootDir/common.gradle"
| `extName` | The name of the extension. Should be romanized if site name is not in English. |
| `extClass` | Points to the class that implements `Source`. You can use a relative path starting with a dot (the package name is the base path). This is used to find and instantiate the source(s). |
| `extVersionCode` | The extension version code. This must be a positive integer and incremented with any change to the code. |
| `libVersion` | (Optional, defaults to `1.4`) The version of the [extensions library](https://github.com/tachiyomiorg/extensions-lib) used. |
| `isNsfw` | (Optional, defaults to `false`) Flag to indicate that a source contains NSFW content. |
The extension's version name is generated automatically by concatenating `1.4` and `extVersionCode`.
The extension's version name is generated automatically by concatenating `libVersion` and `extVersionCode`.
With the example used above, the version would be `1.4.1`.
### Core dependencies
@ -270,7 +272,7 @@ Referencing the actual implementation will help with understanding extensions' c
for handling [base 64 encoded image data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
using an [OkHttp interceptor](https://square.github.io/okhttp/interceptors/).
```groovy
```gradle
dependencies {
implementation(project(':lib-dataimage'))
}
@ -282,7 +284,7 @@ dependencies {
internationalization in the sources. It allows loading `.properties` files with messages located under
the `assets/i18n` folder of each extension, that can be used to translate strings under the source.
```groovy
```gradle
dependencies {
implementation(project(':lib-i18n'))
}

View File

@ -19,3 +19,7 @@ allprojects {
maven(url = "https://jitpack.io")
}
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory.asFile.get())
}

View File

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

View File

@ -1,5 +1,6 @@
object AndroidConfig {
const val compileSdk = 34
const val minSdk = 21
@Suppress("UNUSED")
const val targetSdk = 34
}

View File

@ -3,11 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
apply plugin: 'org.jmailen.kotlinter'
assert !ext.has("pkgNameSuffix")
assert !ext.has("libVersion")
android {
compileSdk AndroidConfig.compileSdk
compileSdkVersion AndroidConfig.compileSdk
namespace "eu.kanade.tachiyomi.extension"
sourceSets {
@ -17,22 +14,39 @@ android {
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
release {
manifest.srcFile "AndroidManifest.xml"
}
debug {
manifest.srcFile "AndroidManifest.xml"
}
}
defaultConfig {
minSdk AndroidConfig.minSdk
targetSdk AndroidConfig.targetSdk
minSdkVersion AndroidConfig.minSdk
targetSdkVersion AndroidConfig.targetSdk
applicationIdSuffix project.parent.name + "." + project.name
versionCode extVersionCode
versionName "1.4.$versionCode"
versionName project.ext.properties.getOrDefault("libVersion", "1.4") + ".$extVersionCode"
base {
archivesName = "tachiyomi-$applicationIdSuffix-v$versionName"
}
assert extClass.startsWith(".")
def readmes = project.projectDir.listFiles({ File file ->
file.name == "README.md" || file.name == "CHANGELOG.md"
} as FileFilter)
def hasReadme = readmes != null && readmes.any { File file ->
file.name.startsWith("README")
}
def hasChangelog = readmes != null && readmes.any { File file ->
file.name.startsWith("CHANGELOG")
}
manifestPlaceholders = [
appName : "Tachiyomi: $extName",
extClass: extClass,
nsfw: project.ext.find("isNsfw") ? 1 : 0,
extFactory: project.ext.properties.getOrDefault("extFactory", ""),
nsfw: project.ext.properties.getOrDefault("isNsfw", false) ? 1 : 0,
hasReadme: hasReadme ? 1 : 0,
hasChangelog: hasChangelog ? 1 : 0,
]
}
@ -57,6 +71,9 @@ android {
}
buildFeatures {
// Disable unused AGP features
aidl false
renderScript false
resValues false
shaders false
buildConfig true
@ -90,20 +107,5 @@ dependencies {
compileOnly(libs.bundles.common)
}
tasks.register("writeManifestFile") {
doLast {
def manifest = android.sourceSets.getByName("main").manifest
if (!manifest.srcFile.exists()) {
File tempFile = layout.buildDirectory.get().file("tempAndroidManifest.xml").getAsFile()
if (!tempFile.exists()) {
tempFile.withWriter {
it.write('<?xml version="1.0" encoding="utf-8"?>\n<manifest />\n')
}
}
manifest.srcFile(tempFile.path)
}
}
}
preBuild.dependsOn(writeManifestFile, lintKotlin)
preBuild.dependsOn(lintKotlin)
lintKotlin.dependsOn(formatKotlin)

View File

@ -6,7 +6,10 @@
<application android:icon="@mipmap/ic_launcher" android:allowBackup="false" android:label="${appName}">
<meta-data android:name="tachiyomi.extension.class" android:value="${extClass}" />
<meta-data android:name="tachiyomi.extension.factory" android:value="${extFactory}" />
<meta-data android:name="tachiyomi.extension.nsfw" android:value="${nsfw}" />
<meta-data android:name="tachiyomi.extension.hasReadme" android:value="${hasReadme}" />
<meta-data android:name="tachiyomi.extension.hasChangelog" android:value="${hasChangelog}" />
</application>

View File

@ -11,6 +11,7 @@ android {
namespace = "eu.kanade.tachiyomi.extension.core"
@Suppress("UnstableApiUsage")
sourceSets {
named("main") {
manifest.srcFile("AndroidManifest.xml")
@ -18,8 +19,9 @@ android {
}
}
buildFeatures {
resValues = false
shaders = false
libraryVariants.all {
generateBuildConfigProvider?.configure {
enabled = false
}
}
}

Binary file not shown.

19
gradlew vendored
View File

@ -83,8 +83,10 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -131,13 +133,10 @@ location of your Java installation."
fi
else
JAVACMD=java
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.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@ -145,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -153,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -198,10 +197,6 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,4 +39,4 @@ object Deobfuscator {
}
// Update this when the script is updated!
private const val SCRIPT_NAME = "synchrony-v2.4.5.1.js"
private const val SCRIPT_NAME = "synchrony-v2.4.2.1.js"

View File

@ -31,10 +31,6 @@ configurations {
dependencies {
compileOnly(libs.bundles.common)
// Only PeachScan sources uses the image-decoder dependency.
//noinspection UseTomlInstead
compileOnly("com.github.tachiyomiorg:image-decoder:fbd6601290")
// Implements all :lib libraries on the multisrc generator
// Note that this does not mean that generated sources are going to
// implement them too; this is just to be able to compile and generate sources.

View File

@ -27,15 +27,9 @@ class BilibiliManga : Bilibili(
return emptyList()
}
val data = result.data!!
val id = data.id
return data.episodeList.mapNotNull { episode ->
if (episode.isInFree || !episode.isLocked) {
chapterFromObject(episode, id)
} else {
null
}
}
return result.data!!.episodeList
.filter { episode -> episode.isInFree || !episode.isLocked }
.map { ep -> chapterFromObject(ep, result.data.id) }
}
override val defaultPopularSort: Int = 0

View File

@ -1,67 +0,0 @@
package eu.kanade.tachiyomi.extension.vi.blogtruyen
import eu.kanade.tachiyomi.multisrc.blogtruyen.BlogTruyen
import eu.kanade.tachiyomi.network.interceptor.rateLimit
class BlogTruyenMoi : BlogTruyen("BlogTruyen", "https://blogtruyenmoi.com", "vi") {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
override fun getGenreList() = listOf(
Genre("Action", "1"),
Genre("Adventure", "3"),
Genre("Comedy", "5"),
Genre("Comic", "6"),
Genre("Doujinshi", "7"),
Genre("Drama", "49"),
Genre("Ecchi", "48"),
Genre("Event BT", "60"),
Genre("Fantasy", "50"),
Genre("Full màu", "64"),
Genre("Game", "61"),
Genre("Gender Bender", "51"),
Genre("Harem", "12"),
Genre("Historical", "13"),
Genre("Horror", "14"),
Genre("Isekai/Dị giới/Trọng sinh", "63"),
Genre("Josei", "15"),
Genre("Live action", "16"),
Genre("Magic", "46"),
Genre("manga", "55"),
Genre("Manhua", "17"),
Genre("Manhwa", "18"),
Genre("Martial Arts", "19"),
Genre("Mecha", "21"),
Genre("Mystery", "22"),
Genre("Nấu Ăn", "56"),
Genre("Ngôn Tình", "65"),
Genre("NTR", "62"),
Genre("One shot", "23"),
Genre("Psychological", "24"),
Genre("Romance", "25"),
Genre("School Life", "26"),
Genre("Sci-fi", "27"),
Genre("Seinen", "28"),
Genre("Shoujo", "29"),
Genre("Shoujo Ai", "30"),
Genre("Shounen", "31"),
Genre("Shounen Ai", "32"),
Genre("Slice of life", "33"),
Genre("Smut", "34"),
Genre("Soft Yaoi", "35"),
Genre("Soft Yuri", "36"),
Genre("Sports", "37"),
Genre("Supernatural", "38"),
Genre("Tạp chí truyện tranh", "39"),
Genre("Tragedy", "40"),
Genre("Trap (Crossdressing)", "58"),
Genre("Trinh Thám", "57"),
Genre("Truyện scan", "41"),
Genre("Tu chân - tu tiên", "66"),
Genre("Video Clip", "53"),
Genre("VnComic", "42"),
Genre("Webtoon", "52"),
Genre("Yuri", "59"),
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -1,56 +0,0 @@
package eu.kanade.tachiyomi.extension.vi.blogtruyenvn
import eu.kanade.tachiyomi.multisrc.blogtruyen.BlogTruyen
class BlogTruyenVn : BlogTruyen("BlogTruyen.vn (unoriginal)", "https://blogtruyenvn.com", "vi") {
override fun getGenreList() = listOf(
Genre("Action", "1"),
Genre("Adventure", "3"),
Genre("Comedy", "5"),
Genre("Comic", "6"),
Genre("Doujinshi", "7"),
Genre("Drama", "49"),
Genre("Ecchi", "48"),
Genre("Event BT", "60"),
Genre("Fantasy", "50"),
Genre("Full màu", "64"),
Genre("Game", "61"),
Genre("Harem", "12"),
Genre("Historical", "13"),
Genre("Horror", "14"),
Genre("Isekai/Dị giới/Trọng sinh", "63"),
Genre("Josei", "15"),
Genre("Live action", "16"),
Genre("Magic", "46"),
Genre("manga", "55"),
Genre("Manhua", "17"),
Genre("Manhwa", "18"),
Genre("Martial Arts", "19"),
Genre("Mecha", "21"),
Genre("Mystery", "22"),
Genre("Nấu Ăn", "56"),
Genre("Ngôn Tình", "65"),
Genre("NTR", "62"),
Genre("One shot", "23"),
Genre("Psychological", "24"),
Genre("Romance", "25"),
Genre("School Life", "26"),
Genre("Sci-fi", "27"),
Genre("Seinen", "28"),
Genre("Shoujo", "29"),
Genre("Shounen", "31"),
Genre("Shounen Ai", "32"),
Genre("Slice of life", "33"),
Genre("Smut", "34"),
Genre("Sports", "37"),
Genre("Supernatural", "38"),
Genre("Tạp chí truyện tranh", "39"),
Genre("Tragedy", "40"),
Genre("Trinh Thám", "57"),
Genre("Truyện scan", "41"),
Genre("Tu chân - tu tiên", "66"),
Genre("Video Clip", "53"),
Genre("VnComic", "42"),
Genre("Webtoon", "52"),
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.extension.tr.epikmanga
import eu.kanade.tachiyomi.multisrc.fmreader.FMReader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import rx.Observable
class EpikManga : FMReader("Epik Manga", "https://www.epikmanga.com", "tr") {
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=views&sorting-type=DESC&Sayfa=$page", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=lastUpdate&sorting-type=DESC&Sayfa=$page", headers)
override fun popularMangaNextPageSelector() = "ul.pagination li.active + li:not(.disabled)"
override val headerSelector = "h4 a"
// search wasn't working on source's website
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
searchMangaParse(response, query)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/seri-listesi?type=text", headers)
private fun searchMangaParse(response: Response, query: String): MangasPage {
val mangas = response.asJsoup().select("div.char.col-lg-4 a").toList()
.filter { it.text().contains(query, ignoreCase = true) }
.map {
SManga.create().apply {
setUrlWithoutDomain(it.attr("href"))
title = it.text()
}
}
return MangasPage(mangas, false)
}
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.col-md-9 div.row").first()!!
return SManga.create().apply {
status = parseStatus(infoElement.select("h4:contains(Durum:)").firstOrNull()?.ownText())
author = infoElement.select("h4:contains(Yazar:)").firstOrNull()?.ownText()
artist = infoElement.select("h4:contains(Çizer:)").firstOrNull()?.ownText()
genre = infoElement.select("h4:contains(Türler:) a").joinToString { it.text() }
thumbnail_url = infoElement.select("img.thumbnail").imgAttr()
description = document.select("div.col-md-12 p").text()
}
}
override fun chapterListSelector() = "table.table tbody tr"
override fun getFilterList(): FilterList = FilterList()
}

View File

@ -1,41 +1,21 @@
package eu.kanade.tachiyomi.extension.ja.manga1000
import eu.kanade.tachiyomi.multisrc.fmreader.FMReader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import org.jsoup.nodes.Document
import rx.Observable
import org.jsoup.nodes.Element
import java.util.Calendar
class Manga1000 : FMReader("Manga1000", "https://manga1000.top", "ja") {
// source is picky about URL format
private fun mangaRequest(sortBy: String, page: Int): Request {
return GET("$baseUrl/manga-list.html?listType=pagination&page=$page&artist=&author=&group=&m_status=&name=&genre=&ungenre=&magazine=&sort=$sortBy&sort_type=DESC", headers)
}
override fun popularMangaRequest(page: Int): Request = mangaRequest("views", page)
override fun latestUpdatesRequest(page: Int): Request = mangaRequest("last_update", page)
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val slug = manga.url.substringAfter("manga-").substringBefore(".html")
return client.newCall(GET("$baseUrl/app/manga/controllers/cont.Listchapterapi.php?slug=$slug", headers))
.asObservableSuccess()
.map { res ->
res.asJsoup().select(".at-series a").map {
SChapter.create().apply {
name = it.select(".chapter-name").text()
url = it.attr("abs:href").substringAfter("controllers")
date_upload = parseChapterDate(it.select(".chapter-time").text())
}
}
override fun chapterFromElement(element: Element, mangaTitle: String): SChapter {
return SChapter.create().apply {
element.let {
setUrlWithoutDomain(it.attr("abs:href"))
name = it.attr("title")
}
date_upload = element.select(chapterTimeSelector)
.let { if (it.hasText()) parseChapterDate(it.text()) else 0 }
}
}
private fun parseChapterDate(date: String): Long {
@ -57,18 +37,4 @@ class Manga1000 : FMReader("Manga1000", "https://manga1000.top", "ja") {
return chapterDate.timeInMillis
}
override fun pageListParse(document: Document): List<Page> {
return document.select("script:containsData(imgsListchap)")
.html()
.substringAfter("(")
.substringBefore(",")
.let { cid ->
client.newCall(GET("$baseUrl/app/manga/controllers/cont.imgsList.php?cid=$cid", headers)).execute().asJsoup()
}
.select(".lazyload")
.mapIndexed { i, e ->
Page(i, "", e.attr("abs:data-src"))
}
}
}

View File

@ -1,82 +0,0 @@
package eu.kanade.tachiyomi.extension.ja.nicomanga
import eu.kanade.tachiyomi.multisrc.fmreader.FMReader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
class Nicomanga : FMReader("Nicomanga", "https://nicomanga.com", "ja") {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
// =========================== Manga Details ============================
override val infoElementSelector = ".card-body > div.row"
override val mangaDetailsSelectorGenre = "li:has(b:contains(Genre)) a.btn-danger"
// ============================== Chapters ==============================
override fun chapterListRequest(manga: SManga): Request {
val slug = urlRegex.find(manga.url)?.groupValues?.get(1) ?: throw Exception("Unable to get slug")
val headers = headersBuilder().apply {
add("Accept", "*/*")
add("Host", baseUrl.toHttpUrl().host)
set("Referer", baseUrl + manga.url)
}.build()
return GET("$baseUrl/app/manga/controllers/cont.Listchapterapi.php?slug=$slug", headers)
}
override fun chapterFromElement(element: Element, mangaTitle: String): SChapter = SChapter.create().apply {
element.select(chapterUrlSelector).first()!!.let {
setUrlWithoutDomain("$baseUrl/${it.attr("href")}")
name = it.attr("title")
}
date_upload = element.select(chapterTimeSelector)
.let { if (it.hasText()) parseRelativeDate(it.text()) else 0 }
}
// =============================== Pages ================================
override fun pageListParse(response: Response): List<Page> {
val id = chapterIdRegex.find(response.use { it.body.string() })?.groupValues?.get(1) ?: throw Exception("chapter-id not found")
val doc = client.newCall(
GET("$baseUrl/app/manga/controllers/cont.imgsList.php?cid=$id", headers),
).execute().asJsoup()
return doc.select("img.chapter-img[data-src]").mapIndexed { i, page ->
Page(i + 1, imageUrl = page.attr("data-src"))
}
}
// ============================= Utilities ==============================
override fun getImgAttr(element: Element?): String? {
return when {
element?.attr("style")?.contains("background-image") == true -> {
val url = thumbnailURLRegex.find(element.attr("style"))?.groupValues?.get(1)
when {
url?.startsWith("/") == true -> baseUrl + url
else -> url
}
}
else -> super.getImgAttr(element)
}
}
companion object {
private val thumbnailURLRegex = Regex("background-image:[^;]?url\\s*\\(\\s*'?([^')]+?)'?(\\)|\$)")
private val urlRegex = Regex("manga-([^/]+)\\.html\$")
private val chapterIdRegex = Regex("imgsListchap\\((\\d+)")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.extension.vi.saytruyen
import eu.kanade.tachiyomi.multisrc.fmreader.FMReader
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import org.jsoup.nodes.Document
class SayTruyen : FMReader("Say Truyen", "https://saytruyenvip.com", "vi") {
override fun mangaDetailsParse(document: Document): SManga {
val info = document.select("div.row").first()!!
return SManga.create().apply {
author = info.select("div.row li:has(b:contains(Tác giả)) small").text()
genre = info.select("div.row li:has(b:contains(Thể loại)) small a").joinToString { it.text() }
status = parseStatus(info.select("div.row li:has(b:contains(Tình trạng)) a").text())
description = document.select("div.description").text()
thumbnail_url = info.select("img.thumbnail").attr("abs:src")
}
}
override fun chapterListParse(response: Response): List<SChapter> {
return response.asJsoup().let { document ->
document.select(chapterListSelector()).map {
chapterFromElement(it).apply {
scanlator = document.select("div.row li:has(b:contains(Nhóm dịch)) small").text()
}
}
}
}
override fun pageListParse(document: Document): List<Page> = super.pageListParse(document).onEach { it.imageUrl!!.trim() }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.extension.pt.baixarhentai
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
import java.util.concurrent.TimeUnit
class BaixarHentai : FoolSlide("Baixar Hentai", "https://leitura.baixarhentai.net", "pt-BR") {
// Hardcode the id because the language wasn't specific.
override val id = 8908032188831949972
override val client = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h1.title").text()
thumbnail_url = getDetailsThumbnail(document, "div.title a")
}
// Always show adult content
override val allowAdult = true
override fun setupPreferenceScreen(screen: PreferenceScreen) {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.fr.lecercleduscan
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import java.util.Locale
class LeCercleDuScan : FoolSlide("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr") {
override fun parseChapterDate(date: String) = super.parseChapterDate(
when (val lcDate = date.lowercase(Locale.FRENCH)) {
"hier" -> "yesterday"
"aujourd'hui" -> "today"
"demain" -> "tomorrow"
else -> lcDate
},
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.extension.en.hentaisphere
import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand
import okhttp3.OkHttpClient
class HentaiSphere : HentaiHand("HentaiSphere", "https://hentaisphere.com", "en", false) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor { authIntercept(it) }
.build()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.extension.en.readmanhwa
import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand
import okhttp3.OkHttpClient
class ReadManhwa : HentaiHand("ReadManhwa", "https://readmanhwa.com", "en", true) {
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor { authIntercept(it) }
.build()
}

View File

@ -1,7 +1,25 @@
package eu.kanade.tachiyomi.extension.en.adultwebtoon
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import okhttp3.CacheControl
import okhttp3.Request
class AdultWebtoon : Madara("Adult Webtoon", "https://adultwebtoon.com", "en") {
override val mangaSubString = "adult-webtoon"
override fun popularMangaRequest(page: Int): Request {
val pageSuffix = if (page != 1) "page/$page/" else ""
return GET(
"$baseUrl/manga/$pageSuffix?m_orderby=trending",
headers,
CacheControl.FORCE_NETWORK,
)
}
override fun latestUpdatesRequest(page: Int): Request {
val pageSuffix = if (page != 1) "page/$page/" else ""
return GET(
"$baseUrl/manga/$pageSuffix?m_orderby=latest",
headers,
CacheControl.FORCE_NETWORK,
)
}
}

View File

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.extension.en.akumanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
class AkuManga : Madara("AkuManga", "https://akumanga.com", "en") {
override val id: Long = 107810123708352143
override val chapterUrlSuffix = ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.burningscans
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.Request
import java.text.SimpleDateFormat
import java.util.Locale
class BurningScans : Madara(
"Burning Scans",
"https://burningscans.com",
"pt-BR",
SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")),
) {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun genresRequest(): Request {
return GET("$baseUrl/?s=&post_type=wp-manga", headers)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

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