Compare commits
130 Commits
ac57f5e3dd
...
5ecf338be0
Author | SHA1 | Date |
---|---|---|
Chopper | 5ecf338be0 | |
子斗子 | d2ad77c0a3 | |
Vetle Ledaal | ac55757327 | |
KenjieDec | a1c813a40d | |
AwkwardPeak7 | 9211ccee37 | |
AwkwardPeak7 | 10a0b67bf4 | |
Vetle Ledaal | e67653580e | |
Vetle Ledaal | 69ace8bba9 | |
Vetle Ledaal | b1f0d14217 | |
Vetle Ledaal | 9ee1be41b8 | |
Vetle Ledaal | 89dbb360d3 | |
Vetle Ledaal | 15aebe8edb | |
Vetle Ledaal | b4dd656175 | |
AwkwardPeak7 | c62cec150f | |
AwkwardPeak7 | 77ac036786 | |
AwkwardPeak7 | 39b1e20401 | |
KenjieDec | b65681b8b7 | |
KenjieDec | db149972bd | |
AwkwardPeak7 | b99fc8af0d | |
AwkwardPeak7 | 41cebfbf1d | |
AwkwardPeak7 | ca739c2278 | |
AwkwardPeak7 | 10a970a56d | |
AwkwardPeak7 | 32220c1673 | |
KenjieDec | d63c947ca7 | |
AwkwardPeak7 | 48b0101075 | |
AwkwardPeak7 | a14bf9cc8b | |
AwkwardPeak7 | fc27271192 | |
AwkwardPeak7 | 18863bcd7f | |
AwkwardPeak7 | 292bcc93bd | |
AwkwardPeak7 | 6b764484d6 | |
AwkwardPeak7 | 490a7b1c0c | |
AwkwardPeak7 | afe2bf4e55 | |
AwkwardPeak7 | e0a7051166 | |
AwkwardPeak7 | f4124bb944 | |
KenjieDec | 159bee785a | |
TheKingTermux | 2bab0cccdb | |
KenjieDec | a27b852546 | |
Chopper | 4955c0f1e5 | |
AwkwardPeak7 | fa0910b72c | |
AwkwardPeak7 | 425d6e839d | |
AwkwardPeak7 | 93c5a106c4 | |
AwkwardPeak7 | 4a7de77df7 | |
AwkwardPeak7 | 847f8d084f | |
AwkwardPeak7 | 6711c29e74 | |
KenjieDec | b6cf811a63 | |
Chopper | 18b6668b54 | |
AwkwardPeak7 | 987aa83570 | |
AwkwardPeak7 | 7ac206a61a | |
AwkwardPeak7 | fdcae35d12 | |
AwkwardPeak7 | 5fca0cb71f | |
AwkwardPeak7 | 271855037e | |
AwkwardPeak7 | 0ea2b99616 | |
AwkwardPeak7 | c16c663fba | |
AwkwardPeak7 | b50b2c3275 | |
AwkwardPeak7 | 6f51e9d50f | |
are-are-are | 45ec1a302d | |
Wackery | 6f98841b14 | |
KenjieDec | cdb8de9fbf | |
Chopper | 9730a445c8 | |
Pedro Azevedo | a4558c60eb | |
bapeey | 6b8d072c6c | |
bapeey | 8b0ff6e537 | |
Chopper | 99caea527a | |
Chopper | f765a61aa4 | |
Vetle Ledaal | fb6bd6a041 | |
Chaos Pjeles | 39f83d1d77 | |
Chopper | c78f0e45f1 | |
kana-shii | cea1f3c81c | |
bapeey | cca5e276d2 | |
jckli | 118a905cef | |
lamaxama | c02273905e | |
lamaxama | 56aa26d330 | |
Chopper | 2d41904b79 | |
Chopper | 3030e0c74f | |
Chopper | 8c4ee90ccc | |
Chopper | 90ba562e46 | |
Chopper | 329738e39d | |
Vetle Ledaal | ebdcdea164 | |
Vetle Ledaal | aece7b3d39 | |
Vetle Ledaal | 4dac2439f8 | |
Vetle Ledaal | a60cd07d45 | |
KenjieDec | da46ebfa9a | |
AwkwardPeak7 | 1a1566be23 | |
AwkwardPeak7 | 7a4fa5d46a | |
Wackery | 4554a0c717 | |
CriosChan | 7e3d185dab | |
Chopper | 5824d4adfa | |
Yush0DAN | ea99a44cf3 | |
AwkwardPeak7 | a82548860a | |
Secozzi | 20b9eff851 | |
Chopper | e8d27b655e | |
KenjieDec | 869afb9534 | |
KirinRaikage | 311bea3a8a | |
KenjieDec | 860155e34d | |
Chopper | 5ba43beecc | |
Chopper | 709769b171 | |
Secozzi | e9d0013df3 | |
Norsze | 0251a55109 | |
Norsze | 530e5500da | |
Norsze | 5ae345113f | |
AwkwardPeak7 | fa37d45021 | |
Luqman | 4fc5107823 | |
Chopper | 01f3fa0191 | |
AwkwardPeak7 | 902e0242dd | |
Chopper | 9e8187cd1f | |
Chopper | 362b739c88 | |
Vetle Ledaal | e171d141f0 | |
AwkwardPeak7 | 401e53d45a | |
AwkwardPeak7 | e5a68f8ba4 | |
Chopper | d07fbd5294 | |
Chopper | ee3d182a5f | |
Chopper | 40f2170702 | |
Chopper | f0e17a6531 | |
Chopper | 5dbcb16307 | |
KenjieDec | ef7ff81f1b | |
AwkwardPeak7 | 8cb6533dcd | |
Vetle Ledaal | 0e2d9bf970 | |
Vetle Ledaal | 7dbe265e52 | |
Vetle Ledaal | 2623bf72c5 | |
Vetle Ledaal | 13fcfc6996 | |
Vetle Ledaal | 0bf59dd5e8 | |
KenjieDec | 38ef5386a6 | |
AwkwardPeak7 | 4eacdd057e | |
KenjieDec | a5e5ccceec | |
kana-shii | 10ddb3734f | |
AwkwardPeak7 | 3e6c9170ab | |
bapeey | 4d291d571d | |
bapeey | 303f6d7737 | |
KenjieDec | 88fffed4f9 | |
KenjieDec | 2f9ebadb08 |
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 1
|
||||
baseVersionCode = 2
|
||||
|
|
|
@ -291,7 +291,7 @@ abstract class BlogTruyen(
|
|||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
document.select("#content > img").forEachIndexed { i, e ->
|
||||
document.select(".content > img, #content > img").forEachIndexed { i, e ->
|
||||
pages.add(Page(i, imageUrl = e.absUrl("src")))
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
/**
|
||||
* Springboard that accepts https://readmanga.live/xxx intents and redirects them to
|
||||
* Springboard that accepts https://1.readmanga.io/xxx intents and redirects them to
|
||||
* the main tachiyomi process. The idea is to not install the intent filter unless
|
||||
* you have this extension installed, but still let the main tachiyomi app control
|
||||
* things.
|
||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 25
|
||||
baseVersionCode = 27
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:i18n"))
|
||||
|
|
|
@ -72,6 +72,8 @@ abstract class HeanCms(
|
|||
|
||||
protected open val coverPath: String = ""
|
||||
|
||||
protected open val cdnUrl = apiUrl
|
||||
|
||||
protected open val mangaSubDirectory: String = "series"
|
||||
|
||||
protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US)
|
||||
|
@ -206,7 +208,7 @@ abstract class HeanCms(
|
|||
|
||||
val result = json.parseAs<HeanCmsQuerySearchDto>()
|
||||
val mangaList = result.data.map {
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory)
|
||||
it.toSManga(cdnUrl, coverPath, mangaSubDirectory)
|
||||
}
|
||||
|
||||
return MangasPage(mangaList, result.meta?.hasNextPage() ?: false)
|
||||
|
@ -242,7 +244,7 @@ abstract class HeanCms(
|
|||
val seriesResult = result.getOrNull()
|
||||
?: throw Exception(intl.format("url_changed_error", name, name))
|
||||
|
||||
val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory)
|
||||
val seriesDetails = seriesResult.toSManga(cdnUrl, coverPath, mangaSubDirectory)
|
||||
|
||||
return seriesDetails.apply {
|
||||
status = status.takeUnless { it == SManga.UNKNOWN }
|
||||
|
@ -345,8 +347,8 @@ abstract class HeanCms(
|
|||
}
|
||||
}
|
||||
|
||||
private fun String.toAbsoluteUrl(): String {
|
||||
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
|
||||
protected open fun String.toAbsoluteUrl(): String {
|
||||
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this"
|
||||
}
|
||||
|
||||
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
|
||||
|
|
|
@ -63,7 +63,7 @@ class HeanCmsSeriesDto(
|
|||
) {
|
||||
|
||||
fun toSManga(
|
||||
apiUrl: String,
|
||||
cdnUrl: String,
|
||||
coverPath: String,
|
||||
mangaSubDirectory: String,
|
||||
): SManga = SManga.create().apply {
|
||||
|
@ -79,7 +79,7 @@ class HeanCmsSeriesDto(
|
|||
.sortedBy(HeanCmsTagDto::name)
|
||||
.joinToString { it.name }
|
||||
thumbnail_url = thumbnail.ifEmpty { null }
|
||||
?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
?.toAbsoluteThumbnailUrl(cdnUrl, coverPath)
|
||||
status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
|
||||
url = "/$mangaSubDirectory/$slug#$id"
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ class HeanCmsChapterPayloadDto(
|
|||
class HeanCmsChapterDto(
|
||||
private val id: Int,
|
||||
@SerialName("chapter_name") private val name: String,
|
||||
@SerialName("chapter_title") private val title: String? = null,
|
||||
@SerialName("chapter_slug") private val slug: String,
|
||||
@SerialName("created_at") private val createdAt: String,
|
||||
val price: Int? = null,
|
||||
|
@ -114,6 +115,10 @@ class HeanCmsChapterDto(
|
|||
): SChapter = SChapter.create().apply {
|
||||
name = this@HeanCmsChapterDto.name.trim()
|
||||
|
||||
if (title != null) {
|
||||
name += " - ${title.trim()}"
|
||||
}
|
||||
|
||||
if (price != 0) {
|
||||
name += " \uD83D\uDD12"
|
||||
}
|
||||
|
@ -161,8 +166,8 @@ class HeanCmsGenreDto(
|
|||
val name: String,
|
||||
)
|
||||
|
||||
private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): String {
|
||||
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
|
||||
private fun String.toAbsoluteThumbnailUrl(cdnUrl: String, coverPath: String): String {
|
||||
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this"
|
||||
}
|
||||
|
||||
fun String.toStatus(): Int = when (this) {
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 1
|
||||
baseVersionCode = 3
|
||||
|
|
|
@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.jsoup.Jsoup
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -31,14 +30,10 @@ class Manga(
|
|||
private val seriesStatus: String? = null,
|
||||
val genres: List<Genre> = emptyList(),
|
||||
) {
|
||||
fun toSManga(baseUrl: String) = SManga.create().apply {
|
||||
fun toSManga() = SManga.create().apply {
|
||||
url = "$slug#$id"
|
||||
title = postTitle
|
||||
thumbnail_url = "$baseUrl/_next/image".toHttpUrl().newBuilder().apply {
|
||||
addQueryParameter("url", featuredImage)
|
||||
addQueryParameter("w", "828")
|
||||
addQueryParameter("q", "75")
|
||||
}.toString()
|
||||
thumbnail_url = featuredImage
|
||||
author = this@Manga.author?.takeUnless { it.isEmpty() }
|
||||
artist = this@Manga.artist?.takeUnless { it.isEmpty() }
|
||||
description = buildString {
|
||||
|
|
|
@ -14,6 +14,7 @@ import kotlinx.serialization.json.Json
|
|||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
abstract class Iken(
|
||||
|
@ -43,7 +44,7 @@ abstract class Iken(
|
|||
it.genres.map { genre ->
|
||||
genre.name to genre.id.toString()
|
||||
}
|
||||
}
|
||||
}.distinct()
|
||||
}
|
||||
.associateBy { it.slug }
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ abstract class Iken(
|
|||
.map { it.absUrl("href").substringAfterLast("/series/") }
|
||||
|
||||
val entries = slugs.mapNotNull {
|
||||
titleCache[it]?.toSManga(baseUrl)
|
||||
titleCache[it]?.toSManga()
|
||||
}
|
||||
|
||||
return MangasPage(entries, false)
|
||||
|
@ -84,7 +85,7 @@ abstract class Iken(
|
|||
|
||||
val entries = data.posts
|
||||
.filterNot { it.isNovel }
|
||||
.map { it.toSManga(baseUrl) }
|
||||
.map { it.toSManga() }
|
||||
|
||||
val hasNextPage = data.totalCount > (page * perPage)
|
||||
|
||||
|
@ -98,32 +99,28 @@ abstract class Iken(
|
|||
Filter.Header("Open popular mangas if genre filter is empty"),
|
||||
)
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
val id = manga.url.substringAfterLast("#")
|
||||
val url = "$baseUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid="
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
val slug = manga.url.substringBeforeLast("#")
|
||||
|
||||
return "$baseUrl/series/$slug"
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val data = response.parseAs<Post<Manga>>()
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
val slug = manga.url.substringBeforeLast("#")
|
||||
val update = titleCache[slug]?.toSManga() ?: manga
|
||||
|
||||
assert(!data.post.isNovel) { "Novels are unsupported" }
|
||||
|
||||
// genres are only returned in search call
|
||||
// and not when fetching details
|
||||
return data.post.toSManga(baseUrl).apply {
|
||||
genre = titleCache[data.post.slug]?.getGenres()
|
||||
}
|
||||
return Observable.just(update)
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
override fun mangaDetailsParse(response: Response) =
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
val id = manga.url.substringAfterLast("#")
|
||||
val url = "$baseUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid="
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val data = response.parseAs<Post<ChapterListResponse>>()
|
||||
|
@ -138,7 +135,7 @@ abstract class Iken(
|
|||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.asJsoup()
|
||||
|
||||
return document.select("main section > img").mapIndexed { idx, img ->
|
||||
return document.select("main section img").mapIndexed { idx, img ->
|
||||
Page(idx, imageUrl = img.absUrl("src"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 6
|
|
@ -5,7 +5,6 @@ import android.content.SharedPreferences
|
|||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
|
@ -32,8 +31,18 @@ abstract class MangaThemesiaAlt(
|
|||
private val randomUrlPrefKey: String = "pref_auto_random_url",
|
||||
) : MangaThemesia(name, baseUrl, lang, mangaUrlDirectory, dateFormat), ConfigurableSource {
|
||||
|
||||
protected open val listUrl = "$mangaUrlDirectory/list-mode/"
|
||||
protected open val listSelector = "div#content div.soralist ul li a.series"
|
||||
|
||||
protected val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000).also {
|
||||
if (it.contains("__random_part_cache")) {
|
||||
it.edit().remove("__random_part_cache").apply()
|
||||
}
|
||||
if (it.contains("titles_without_random_part")) {
|
||||
it.edit().remove("titles_without_random_part").apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
|
@ -47,183 +56,11 @@ abstract class MangaThemesiaAlt(
|
|||
|
||||
private fun getRandomUrlPref() = preferences.getBoolean(randomUrlPrefKey, true)
|
||||
|
||||
private var randomPartCache = SuspendLazy(::getUpdatedRandomPart) { preferences.randomPartCache = it }
|
||||
|
||||
// cache in preference for webview urls
|
||||
private var SharedPreferences.randomPartCache: String
|
||||
get() = getString("__random_part_cache", "")!!
|
||||
set(newValue) = edit().putString("__random_part_cache", newValue).apply()
|
||||
|
||||
// some new titles don't have random part
|
||||
// se we save their slug and when they
|
||||
// finally add it, we remove the slug in the interceptor
|
||||
private var SharedPreferences.titlesWithoutRandomPart: MutableSet<String>
|
||||
get() {
|
||||
val value = getString("titles_without_random_part", null)
|
||||
?: return mutableSetOf()
|
||||
|
||||
return json.decodeFromString(value)
|
||||
}
|
||||
set(newValue) {
|
||||
val encodedValue = json.encodeToString(newValue)
|
||||
|
||||
edit().putString("titles_without_random_part", encodedValue).apply()
|
||||
}
|
||||
|
||||
protected open fun getRandomPartFromUrl(url: String): String {
|
||||
val slug = url
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
return slugRegex.find(slug)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
|
||||
protected open fun getRandomPartFromResponse(response: Response): String {
|
||||
return response.asJsoup()
|
||||
.selectFirst(searchMangaSelector())!!
|
||||
.select("a").attr("href")
|
||||
.let(::getRandomPartFromUrl)
|
||||
}
|
||||
|
||||
protected suspend fun getUpdatedRandomPart(): String =
|
||||
client.newCall(GET("$baseUrl$mangaUrlDirectory/", headers))
|
||||
.await()
|
||||
.use(::getRandomPartFromResponse)
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val mp = super.searchMangaParse(response)
|
||||
|
||||
if (!getRandomUrlPref()) return mp
|
||||
|
||||
// extract random part during browsing to avoid extra call
|
||||
mp.mangas.firstOrNull()?.run {
|
||||
val randomPart = getRandomPartFromUrl(url)
|
||||
|
||||
if (randomPart.isNotEmpty()) {
|
||||
randomPartCache.set(randomPart)
|
||||
}
|
||||
}
|
||||
|
||||
val mangas = mp.mangas.toPermanentMangaUrls()
|
||||
|
||||
return MangasPage(mangas, mp.hasNextPage)
|
||||
}
|
||||
|
||||
protected fun List<SManga>.toPermanentMangaUrls(): List<SManga> {
|
||||
// some absolutely new titles don't have the random part yet
|
||||
// save them so we know where to not apply it
|
||||
val foundTitlesWithoutRandomPart = mutableSetOf<String>()
|
||||
|
||||
for (i in indices) {
|
||||
val slug = this[i].url
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
if (slugRegex.find(slug)?.groupValues?.get(1) == null) {
|
||||
foundTitlesWithoutRandomPart.add(slug)
|
||||
}
|
||||
|
||||
val permaSlug = slug
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
this[i].url = "$mangaUrlDirectory/$permaSlug/"
|
||||
}
|
||||
|
||||
if (foundTitlesWithoutRandomPart.isNotEmpty()) {
|
||||
foundTitlesWithoutRandomPart.addAll(preferences.titlesWithoutRandomPart)
|
||||
|
||||
preferences.titlesWithoutRandomPart = foundTitlesWithoutRandomPart
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
protected open val slugRegex = Regex("""^(\d+-)""")
|
||||
|
||||
override val client = super.client.newBuilder()
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (request.url.fragment != "titlesWithoutRandomPart") {
|
||||
return@addInterceptor response
|
||||
}
|
||||
|
||||
if (!response.isSuccessful && response.code == 404) {
|
||||
response.close()
|
||||
|
||||
val slug = request.url.toString()
|
||||
.substringBefore("#")
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
preferences.titlesWithoutRandomPart.run {
|
||||
remove(slug)
|
||||
|
||||
preferences.titlesWithoutRandomPart = this
|
||||
}
|
||||
|
||||
val randomPart = randomPartCache.blockingGet()
|
||||
val newRequest = request.newBuilder()
|
||||
.url("$baseUrl$mangaUrlDirectory/$randomPart$slug/")
|
||||
.build()
|
||||
|
||||
return@addInterceptor chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
return@addInterceptor response
|
||||
}
|
||||
.build()
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
if (!getRandomUrlPref()) return super.mangaDetailsRequest(manga)
|
||||
|
||||
val slug = manga.url
|
||||
.substringBefore("#")
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
if (preferences.titlesWithoutRandomPart.contains(slug)) {
|
||||
return GET("$baseUrl${manga.url}#titlesWithoutRandomPart")
|
||||
}
|
||||
|
||||
val randomPart = randomPartCache.blockingGet()
|
||||
|
||||
return GET("$baseUrl$mangaUrlDirectory/$randomPart$slug/", headers)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
if (!getRandomUrlPref()) return super.getMangaUrl(manga)
|
||||
|
||||
val slug = manga.url
|
||||
.substringBefore("#")
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
if (preferences.titlesWithoutRandomPart.contains(slug)) {
|
||||
return "$baseUrl${manga.url}"
|
||||
}
|
||||
|
||||
val randomPart = randomPartCache.peek() ?: preferences.randomPartCache
|
||||
|
||||
return "$baseUrl$mangaUrlDirectory/$randomPart$slug/"
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
internal class SuspendLazy(
|
||||
private val initializer: suspend () -> String,
|
||||
private val saveCache: (String) -> Unit,
|
||||
) {
|
||||
|
||||
private val mutex = Mutex()
|
||||
private var cachedValue: SoftReference<String>? = null
|
||||
private var cachedValue: SoftReference<Map<String, String>>? = null
|
||||
private var fetchTime = 0L
|
||||
|
||||
suspend fun get(): String {
|
||||
private suspend fun getUrlMapInternal(): Map<String, String> {
|
||||
if (fetchTime + 3600000 < System.currentTimeMillis()) {
|
||||
// reset cache
|
||||
cachedValue = null
|
||||
|
@ -238,22 +75,104 @@ internal class SuspendLazy(
|
|||
return it
|
||||
}
|
||||
|
||||
initializer().also { set(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun set(newVal: String) {
|
||||
cachedValue = SoftReference(newVal)
|
||||
fetchUrlMap().also {
|
||||
cachedValue = SoftReference(it)
|
||||
fetchTime = System.currentTimeMillis()
|
||||
|
||||
saveCache(newVal)
|
||||
preferences.urlMapCache = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun peek(): String? {
|
||||
return cachedValue?.get()
|
||||
protected open fun fetchUrlMap(): Map<String, String> {
|
||||
client.newCall(GET("$baseUrl$listUrl", headers)).execute().use { response ->
|
||||
val document = response.asJsoup()
|
||||
|
||||
return document.select(listSelector).associate {
|
||||
val url = it.absUrl("href")
|
||||
|
||||
val slug = url.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
val permaSlug = slug
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
permaSlug to slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun blockingGet(): String {
|
||||
return runBlocking { get() }
|
||||
protected fun getUrlMap(cached: Boolean = false): Map<String, String> {
|
||||
return if (cached && cachedValue == null) {
|
||||
preferences.urlMapCache
|
||||
} else {
|
||||
runBlocking { getUrlMapInternal() }
|
||||
}
|
||||
}
|
||||
|
||||
// cache in preference for webview urls
|
||||
private var SharedPreferences.urlMapCache: Map<String, String>
|
||||
get(): Map<String, String> {
|
||||
val value = getString("url_map_cache", "{}")!!
|
||||
return try {
|
||||
json.decodeFromString(value)
|
||||
} catch (_: Exception) {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
set(newMap) = edit().putString("url_map_cache", json.encodeToString(newMap)).apply()
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val mp = super.searchMangaParse(response)
|
||||
|
||||
if (!getRandomUrlPref()) return mp
|
||||
|
||||
val mangas = mp.mangas.toPermanentMangaUrls()
|
||||
|
||||
return MangasPage(mangas, mp.hasNextPage)
|
||||
}
|
||||
|
||||
protected fun List<SManga>.toPermanentMangaUrls(): List<SManga> {
|
||||
return onEach {
|
||||
val slug = it.url
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
val permaSlug = slug
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
it.url = "$mangaUrlDirectory/$permaSlug/"
|
||||
}
|
||||
}
|
||||
|
||||
protected open val slugRegex = Regex("""^(\d+-)""")
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
if (!getRandomUrlPref()) return super.mangaDetailsRequest(manga)
|
||||
|
||||
val slug = manga.url
|
||||
.substringBefore("#")
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
val randomSlug = getUrlMap()[slug] ?: slug
|
||||
|
||||
return GET("$baseUrl$mangaUrlDirectory/$randomSlug/", headers)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
if (!getRandomUrlPref()) return super.getMangaUrl(manga)
|
||||
|
||||
val slug = manga.url
|
||||
.substringBefore("#")
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
.replaceFirst(slugRegex, "")
|
||||
|
||||
val randomSlug = getUrlMap(true)[slug] ?: slug
|
||||
|
||||
return "$baseUrl$mangaUrlDirectory/$randomSlug/"
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Bato.to'
|
||||
extClass = '.BatoToFactory'
|
||||
extVersionCode = 36
|
||||
extVersionCode = 37
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -384,11 +384,14 @@ open class BatoTo(
|
|||
val chapter = SChapter.create()
|
||||
val urlElement = element.select("a.chapt")
|
||||
val group = element.select("div.extra > a:not(.ps-3)").text()
|
||||
val user = element.select("div.extra > a.ps-3").text()
|
||||
val time = element.select("div.extra > i.ps-3").text()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = urlElement.text()
|
||||
if (group != "") {
|
||||
chapter.scanlator = group
|
||||
chapter.scanlator = when {
|
||||
group.isNotBlank() -> group
|
||||
user.isNotBlank() -> user
|
||||
else -> "Unknown"
|
||||
}
|
||||
if (time != "") {
|
||||
chapter.date_upload = parseChapterDate(time)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Comick'
|
||||
extClass = '.ComickFactory'
|
||||
extVersionCode = 46
|
||||
extVersionCode = 47
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -463,7 +463,16 @@ abstract class Comick(
|
|||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val result = response.parseAs<PageList>()
|
||||
return result.chapter.images.mapIndexedNotNull { index, data ->
|
||||
val images = result.chapter.images.ifEmpty {
|
||||
// cache busting
|
||||
val url = response.request.url.newBuilder()
|
||||
.addQueryParameter("_", System.currentTimeMillis().toString())
|
||||
.build()
|
||||
|
||||
client.newCall(GET(url, headers)).execute()
|
||||
.parseAs<PageList>().chapter.images
|
||||
}
|
||||
return images.mapIndexedNotNull { index, data ->
|
||||
if (data.url == null) null else Page(index = index, imageUrl = data.url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
ext {
|
||||
extName = 'MangaHot'
|
||||
extClass = '.MangaHot'
|
||||
extName = 'FoamGirl'
|
||||
extClass = '.FoamGirl'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,127 @@
|
|||
package eu.kanade.tachiyomi.extension.all.foamgirl
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
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.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class FoamGirl() : ParsedHttpSource() {
|
||||
override val baseUrl = "https://foamgirl.net"
|
||||
override val lang = "all"
|
||||
override val name = "FoamGirl"
|
||||
override val supportsLatest = false
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
// Popular
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
thumbnail_url = element.select("img").attr("data-original")
|
||||
title = element.select("a.meta-title").text()
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
initialized = true
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return Observable.just(manga)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
override fun popularMangaNextPageSelector() = "a.next"
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/page/$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = ".update_area .i_list"
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
return GET(
|
||||
baseUrl.toHttpUrl().newBuilder().apply {
|
||||
addPathSegment("page")
|
||||
addPathSegment("$page")
|
||||
addQueryParameter("post_type", "post")
|
||||
addQueryParameter("s", query)
|
||||
}.build(),
|
||||
headers,
|
||||
)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
val imageCount = document.select(".post_title_topimg").text().substringAfter("(").substringBefore("P").toInt()
|
||||
val imageUrl = document.select(".imageclick-imgbox").attr("href").toHttpUrl()
|
||||
val imagePrefix = imageUrl.pathSegments.last().substringBefore(".").toLong() / 10
|
||||
for (i in 0 until imageCount) {
|
||||
pages.add(
|
||||
Page(
|
||||
i,
|
||||
imageUrl = imageUrl.newBuilder().apply {
|
||||
removePathSegment(imageUrl.pathSize - 1)
|
||||
addPathSegment("${imagePrefix}${i + 2}.jpg")
|
||||
}.build().toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.select("link[rel=canonical]").attr("abs:href"))
|
||||
chapter_number = 0F
|
||||
name = "GALLERY"
|
||||
date_upload = getDate(element.select("span.image-info-time").text().substring(1))
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "html"
|
||||
|
||||
// Pages
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun getDate(str: String): Long {
|
||||
return try {
|
||||
DATE_FORMAT.parse(str)?.time ?: 0L
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DATE_FORMAT by lazy {
|
||||
SimpleDateFormat("yyyy.M.d", Locale.ENGLISH)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Hentai Cosplay'
|
||||
extClass = '.HentaiCosplay'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class HentaiCosplay : HttpSource() {
|
|||
|
||||
override val name = "Hentai Cosplay"
|
||||
|
||||
override val baseUrl = "https://hentai-cosplays.com"
|
||||
override val baseUrl = "https://hentai-cosplay-xxx.com"
|
||||
|
||||
override val lang = "all"
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangatopsite
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class MangaTopSite : Madara(
|
||||
"MangaTop.site",
|
||||
"https://mangatop.site",
|
||||
"all",
|
||||
dateFormat = SimpleDateFormat("d MMM yyyy", Locale.ENGLISH),
|
||||
) {
|
||||
override val useNewChapterEndpoint = false
|
||||
override val chapterUrlSuffix = ""
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'NHentai'
|
||||
extClass = '.NHFactory'
|
||||
extVersionCode = 40
|
||||
extVersionCode = 41
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ open class NHentai(
|
|||
thumbnail_url = document.select("#cover > a > img").attr("data-src")
|
||||
status = SManga.COMPLETED
|
||||
artist = getArtists(document)
|
||||
author = artist
|
||||
author = getGroups(document)
|
||||
// Some people want these additional details in description
|
||||
description = "Full English and Japanese titles:\n"
|
||||
.plus("$fullTitle\n")
|
||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'Thunder Scans'
|
||||
extClass = '.ThunderScansFactory'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://en-thunderepic.com'
|
||||
overrideVersionCode = 2
|
||||
baseUrl = 'https://en-thunderscans.com'
|
||||
overrideVersionCode = 4
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -14,14 +14,14 @@ class ThunderScansFactory : SourceFactory {
|
|||
|
||||
class ThunderScansAR : MangaThemesiaAlt(
|
||||
"Thunder Scans",
|
||||
"https://ar-thunderepic.com",
|
||||
"https://thunderscans.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
|
||||
)
|
||||
|
||||
class ThunderScansEN : MangaThemesiaAlt(
|
||||
"Thunder Scans",
|
||||
"https://en-thunderepic.com",
|
||||
"https://en-thunderscans.com",
|
||||
"en",
|
||||
mangaUrlDirectory = "/comics",
|
||||
)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
ext {
|
||||
extName = 'MangaHub'
|
||||
extClass = '.MangaHub'
|
||||
themePkg = 'zeistmanga'
|
||||
baseUrl = 'https://www.mangahub.link'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,23 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.mangahub
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
||||
class MangaHub : ZeistManga(
|
||||
"MangaHub",
|
||||
"https://www.mangahub.link",
|
||||
"ar",
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
|
||||
override val popularMangaSelector = "div#PopularPosts2 article"
|
||||
override val popularMangaSelectorTitle = "h3"
|
||||
override val popularMangaSelectorUrl = "a"
|
||||
|
||||
override val mangaDetailsSelector = ".grid.gap-5.gta-series"
|
||||
override val mangaDetailsSelectorGenres = "dt:contains(التصنيف) + dd a[rel=tag]"
|
||||
|
||||
override val pageListSelector = "article#reader .separator, div.image-container"
|
||||
}
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'MangaNoon'
|
||||
extClass = '.MangaNoon'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://manjanoon.co'
|
||||
overrideVersionCode = 3
|
||||
baseUrl = 'https://noonscan.net'
|
||||
overrideVersionCode = 4
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Calendar
|
|||
|
||||
class MangaNoon : MangaThemesia(
|
||||
"مانجا نون",
|
||||
"https://manjanoon.co",
|
||||
"https://noonscan.net",
|
||||
"ar",
|
||||
) {
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.MangaPro'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://promanga.pro'
|
||||
overrideVersionCode = 2
|
||||
overrideVersionCode = 3
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.mangapro
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.jsoup.nodes.Document
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
|
@ -11,4 +14,30 @@ class MangaPro : MangaThemesia(
|
|||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||
) {
|
||||
override val versionId = 3
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return super.pageListParse(document).onEach {
|
||||
val httpUrl = it.imageUrl!!.toHttpUrl()
|
||||
|
||||
if (wpImgRegex.containsMatchIn(httpUrl.host)) {
|
||||
it.imageUrl = StringBuilder().apply {
|
||||
val ssl = httpUrl.queryParameter("ssl")
|
||||
when (ssl) {
|
||||
null -> append(httpUrl.scheme)
|
||||
"0" -> append("http")
|
||||
else -> append("https")
|
||||
}
|
||||
append("://")
|
||||
append(httpUrl.pathSegments.joinToString("/"))
|
||||
val search = httpUrl.queryParameter("q")
|
||||
if (search != null) {
|
||||
append("?q=")
|
||||
append(search)
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val wpImgRegex = Regex("""i\d+\.wp\.com""")
|
||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'MangaSwat'
|
||||
extClass = '.MangaSwat'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://maxlevelteam.com'
|
||||
overrideVersionCode = 20
|
||||
baseUrl = 'https://tatwt.com'
|
||||
overrideVersionCode = 21
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Locale
|
|||
class MangaSwat :
|
||||
MangaThemesia(
|
||||
"MangaSwat",
|
||||
"https://maxlevelteam.com",
|
||||
"https://tatwt.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||
),
|
||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'Rocks Manga'
|
||||
extClass = '.RocksManga'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://rocks-manga.com'
|
||||
overrideVersionCode = 0
|
||||
baseUrl = 'https://rocksmanga.com'
|
||||
overrideVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -2,39 +2,65 @@ package eu.kanade.tachiyomi.extension.ar.rocksmanga
|
|||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class RocksManga : Madara(
|
||||
"Rocks Manga",
|
||||
"https://rocks-manga.com",
|
||||
"https://rocksmanga.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar")),
|
||||
) {
|
||||
|
||||
override fun popularMangaSelector() = ".shido-manga"
|
||||
override val popularMangaUrlSelector = "a.s-manga-title"
|
||||
override val mangaDetailsSelectorTitle = ".title"
|
||||
override val mangaDetailsSelectorAuthor = ".heading:contains(المؤلف:) + .content a"
|
||||
override val mangaDetailsSelectorArtist = ".heading:contains(الرسام:) + .content a"
|
||||
override fun popularMangaSelector() = "div.page-content-listing > .manga"
|
||||
override val popularMangaUrlSelector = "div.manga-poster a"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return super.popularMangaFromElement(element).apply {
|
||||
title = element.selectFirst(popularMangaUrlSelector)!!.attr("title")
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "#manga-search-results .manga-item"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
|
||||
with(element) {
|
||||
selectFirst("a.cover")!!.let {
|
||||
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
||||
manga.title = it.attr("title")
|
||||
}
|
||||
selectFirst("img")?.let {
|
||||
manga.thumbnail_url = imageFromElement(it)
|
||||
}
|
||||
}
|
||||
|
||||
return manga
|
||||
}
|
||||
|
||||
override val mangaDetailsSelectorTitle = ".manga-title"
|
||||
override val mangaDetailsSelectorAuthor = "div.meta span:contains(المؤلف:) + span a"
|
||||
override val mangaDetailsSelectorArtist = "div.meta span:contains(الرسام:) + span a"
|
||||
override val mangaDetailsSelectorStatus = ".status"
|
||||
override val mangaDetailsSelectorDescription = ".story"
|
||||
override val mangaDetailsSelectorThumbnail = ".profile-manga .poster img"
|
||||
override val mangaDetailsSelectorGenre = ".heading:contains(التصنيف:) + .content a"
|
||||
override val altNameSelector = ".other-name"
|
||||
override fun chapterListSelector() = "#chapter-list li.chapter-item"
|
||||
override fun chapterDateSelector() = ".ch-post-time"
|
||||
override val pageListParseSelector = ".reading-content img"
|
||||
override val mangaDetailsSelectorDescription = "div.description"
|
||||
override val mangaDetailsSelectorThumbnail = ".manga-poster img"
|
||||
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
|
||||
override val altNameSelector = "div.alternative"
|
||||
override fun chapterListSelector() = ".chapters-list li.chapter-item"
|
||||
override fun chapterDateSelector() = ".chapter-release-date"
|
||||
override val pageListParseSelector = ".chapter-reading-page img"
|
||||
|
||||
override val useLoadMoreRequest = LoadMoreStrategy.Never
|
||||
override val useNewChapterEndpoint = false
|
||||
|
||||
override val fetchGenres = false
|
||||
override val filterNonMangaItems = false
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val chapter = super.chapterFromElement(element)
|
||||
chapter.name = element.selectFirst(".detail-ch")!!.text()
|
||||
chapter.name = element.selectFirst(".num")!!.text()
|
||||
return chapter
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Team X'
|
||||
extClass = '.TeamX'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 18
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.teamx
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
@ -14,15 +19,19 @@ import okhttp3.Request
|
|||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class TeamX : ParsedHttpSource() {
|
||||
class TeamX : ParsedHttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "Team X"
|
||||
|
||||
override val baseUrl = "https://teamxnovel.com"
|
||||
private val defaultBaseUrl = "https://teamoney.site"
|
||||
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
override val lang = "ar"
|
||||
|
||||
|
@ -34,6 +43,10 @@ class TeamX : ParsedHttpSource() {
|
|||
.rateLimit(10, 1, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// Popular
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
|
@ -130,6 +143,14 @@ class TeamX : ParsedHttpSource() {
|
|||
}
|
||||
genre = document.select("div.review-author-info a").joinToString { it.text() }
|
||||
thumbnail_url = document.select("div.text-right img").first()!!.absUrl("src")
|
||||
status = document
|
||||
.selectFirst(".full-list-info > small:first-child:contains(الحالة) + small")
|
||||
?.text()
|
||||
.toStatus()
|
||||
author = document
|
||||
.selectFirst(".full-list-info > small:first-child:contains(الرسام) + small")
|
||||
?.text()
|
||||
?.takeIf { it != "غير معروف" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +208,14 @@ class TeamX : ParsedHttpSource() {
|
|||
}.getOrNull() ?: 0
|
||||
}
|
||||
|
||||
private fun String?.toStatus() = when (this) {
|
||||
"مستمرة" -> SManga.ONGOING
|
||||
"قادم قريبًا" -> SManga.ONGOING // "coming soon"
|
||||
"مكتمل" -> SManga.COMPLETED
|
||||
"متوقف" -> SManga.ON_HIATUS
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
// Pages
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
|
@ -196,4 +225,41 @@ class TeamX : ParsedHttpSource() {
|
|||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
||||
companion object {
|
||||
private const val RESTART_APP = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق"
|
||||
private const val BASE_URL_PREF_TITLE = "تعديل الرابط"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl"
|
||||
private const val BASE_URL_PREF_SUMMARY = ".للاستخدام المؤقت. تحديث التطبيق سيؤدي الى حذف الإعدادات"
|
||||
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
this.setDefaultValue(defaultBaseUrl)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: $defaultBaseUrl"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
screen.addPreference(baseUrlPref)
|
||||
}
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||
|
||||
init {
|
||||
preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl ->
|
||||
if (prefDefaultBaseUrl != defaultBaseUrl) {
|
||||
preferences.edit()
|
||||
.putString(BASE_URL_PREF, defaultBaseUrl)
|
||||
.putString(DEFAULT_BASE_URL_PREF, defaultBaseUrl)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'Altay Scans'
|
||||
extClass = '.AltayScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://altayscans.com'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,14 @@
|
|||
package eu.kanade.tachiyomi.extension.en.altayscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
||||
class AltayScans : MangaThemesia(
|
||||
"Altay Scans",
|
||||
"https://altayscans.com",
|
||||
"en",
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Ansh Scans'
|
||||
extClass = '.AnshScans'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://anshscans.org'
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB |
|
@ -1,11 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.anshscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class AnshScans : Madara("Ansh Scans", "https://anshscans.org", "en") {
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
|
||||
|
||||
override val mangaSubString = "comic"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'Astra Scans'
|
||||
extClass = '.AstraScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://astrascans.org'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,15 @@
|
|||
package eu.kanade.tachiyomi.extension.en.astrascans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
||||
class AstraScans : MangaThemesia(
|
||||
"Astra Scans",
|
||||
"https://astrascans.org",
|
||||
"en",
|
||||
"/series",
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Asura Scans'
|
||||
extClass = '.AsuraScans'
|
||||
extVersionCode = 36
|
||||
extVersionCode = 39
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -205,7 +205,12 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||
description = document.selectFirst("span.font-medium.text-sm")?.text()
|
||||
author = document.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Author)) > h3:eq(1)")?.ownText()
|
||||
artist = document.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Artist)) > h3:eq(1)")?.ownText()
|
||||
genre = document.select("div[class^=space] > div.flex > button.text-white").joinToString { it.ownText() }
|
||||
genre = buildList {
|
||||
document.selectFirst("div.flex:has(h3:eq(0):containsOwn(type)) > h3:eq(1)")
|
||||
?.ownText()?.let(::add)
|
||||
document.select("div[class^=space] > div.flex > button.text-white")
|
||||
.forEach { add(it.ownText()) }
|
||||
}.joinToString()
|
||||
status = parseStatus(document.selectFirst("div.flex:has(h3:eq(0):containsOwn(Status)) > h3:eq(1)")?.ownText())
|
||||
}
|
||||
|
||||
|
@ -229,10 +234,10 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
|
||||
override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > a.block"
|
||||
override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > div.group"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.attr("abs:href").toPermSlugIfNeeded())
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href").toPermSlugIfNeeded())
|
||||
name = element.selectFirst("h3:eq(0)")!!.text()
|
||||
date_upload = try {
|
||||
val text = element.selectFirst("h3:eq(1)")!!.ownText()
|
||||
|
@ -253,7 +258,7 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select("div > img[alt=chapter]").mapIndexed { i, element ->
|
||||
return document.select("div > img[alt*=chapter]").mapIndexed { i, element ->
|
||||
Page(i, imageUrl = element.attr("abs:src"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
ext {
|
||||
extName = 'AgiToon'
|
||||
extClass = '.AgiToon'
|
||||
extName = 'Atsumaru'
|
||||
extClass = '.Atsumaru'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,156 @@
|
|||
package eu.kanade.tachiyomi.extension.en.atsumaru
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
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.source.online.HttpSource
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class Atsumaru : HttpSource() {
|
||||
|
||||
override val name = "Atsumaru"
|
||||
|
||||
override val baseUrl = "https://atsu.moe"
|
||||
private val apiUrl = "$baseUrl/api/v1"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private fun apiHeadersBuilder() = headersBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("Host", apiUrl.toHttpUrl().host)
|
||||
}
|
||||
|
||||
private val apiHeaders by lazy { apiHeadersBuilder().build() }
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$apiUrl/layouts/s1/sliders/hotUpdates", apiHeaders)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val data = response.parseAs<BrowseMangaDto>().items
|
||||
|
||||
return MangasPage(data.map { it.manga.toSManga() }, false)
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$apiUrl/layouts/s1/latest-updates", apiHeaders)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
return popularMangaParse(response)
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$apiUrl/search".toHttpUrl().newBuilder()
|
||||
.addPathSegment(query)
|
||||
.build()
|
||||
|
||||
return GET(url, apiHeaders)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val data = response.parseAs<SearchResultsDto>().hits
|
||||
|
||||
return MangasPage(data.map { it.info.toSManga() }, false)
|
||||
}
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
return baseUrl + manga.url
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
return GET(apiUrl + manga.url, apiHeaders)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
return response.parseAs<MangaObjectDto>().manga.toSManga()
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
return mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val chapterList = response.parseAs<MangaObjectDto>().manga.chapters!!.map {
|
||||
it.toSChapter(response.request.url.pathSegments.last())
|
||||
}
|
||||
|
||||
return chapterList.sortedWith(
|
||||
compareBy(
|
||||
{ it.chapter_number },
|
||||
{ it.scanlator },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
val (slug, name) = chapter.url.split("/")
|
||||
return "$baseUrl/read/s1/$slug/$name/1"
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
val (slug, name) = chapter.url.split("/")
|
||||
return GET("$apiUrl/manga/s1/$slug#$name", apiHeaders)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val chapter = response.parseAs<MangaObjectDto>().manga.chapters!!.first {
|
||||
it.name == response.request.url.fragment
|
||||
}
|
||||
|
||||
return chapter.pages.map { page ->
|
||||
Page(page.name.toInt(), imageUrl = page.pageURLs.first())
|
||||
}.sortedBy { it.index }
|
||||
}
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
val imgHeaders = headersBuilder().apply {
|
||||
add("Accept", "image/avif,image/webp,*/*")
|
||||
add("Host", page.imageUrl!!.toHttpUrl().host)
|
||||
}.build()
|
||||
|
||||
return GET(page.imageUrl!!, imgHeaders)
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T {
|
||||
return json.decodeFromString(body.string())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package eu.kanade.tachiyomi.extension.en.atsumaru
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Serializable
|
||||
class BrowseMangaDto(
|
||||
val items: List<MangaObjectDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class MangaObjectDto(
|
||||
val manga: MangaDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class SearchResultsDto(
|
||||
val hits: List<SearchMangaDto>,
|
||||
) {
|
||||
@Serializable
|
||||
class SearchMangaDto(
|
||||
val info: MangaDto,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class MangaDto(
|
||||
// Common
|
||||
private val title: String,
|
||||
private val cover: String,
|
||||
private val slug: String,
|
||||
|
||||
// Details
|
||||
private val authors: List<String>? = null,
|
||||
private val description: String? = null,
|
||||
private val genres: List<String>? = null,
|
||||
private val statuses: List<String>? = null,
|
||||
|
||||
// Chapters
|
||||
val chapters: List<ChapterDto>? = null,
|
||||
) {
|
||||
fun toSManga(): SManga = SManga.create().apply {
|
||||
title = this@MangaDto.title
|
||||
thumbnail_url = cover
|
||||
url = "/manga/s1/$slug"
|
||||
|
||||
authors?.let {
|
||||
author = it.joinToString()
|
||||
}
|
||||
description = this@MangaDto.description
|
||||
genres?.let {
|
||||
genre = it.joinToString()
|
||||
}
|
||||
statuses?.let {
|
||||
status = when (it.first().lowercase().substringBefore(" ")) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"complete" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class ChapterDto(
|
||||
val pages: List<PageDto>,
|
||||
val name: String,
|
||||
private val type: String,
|
||||
private val title: String? = null,
|
||||
private val date: String? = null,
|
||||
) {
|
||||
fun toSChapter(slug: String): SChapter = SChapter.create().apply {
|
||||
val chapterNumber = this@ChapterDto.name.replace("_", ".")
|
||||
.filter { it.isDigit() || it == '.' }
|
||||
|
||||
name = buildString {
|
||||
append("Chapter ")
|
||||
append(chapterNumber)
|
||||
if (title != null) {
|
||||
append(" - ")
|
||||
append(title)
|
||||
}
|
||||
}
|
||||
url = "$slug/${this@ChapterDto.name}"
|
||||
chapter_number = chapterNumber.toFloat()
|
||||
scanlator = type.takeUnless { it == "Chapter" }
|
||||
date?.let {
|
||||
date_upload = parseDate(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String): Long {
|
||||
return try {
|
||||
DATE_FORMAT.parse(dateStr)!!.time
|
||||
} catch (_: ParseException) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DATE_FORMAT by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class PageDto(
|
||||
val pageURLs: List<String>,
|
||||
val name: String,
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'ColoredManga'
|
||||
extClass = '.ColoredManga'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 35
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Comic Scans'
|
||||
extClass = '.ComicScans'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://www.comicscans.org'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
|
@ -1,7 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.comicscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class ComicScans : Madara("Comic Scans", "https://www.comicscans.org", "en") {
|
||||
override val useNewChapterEndpoint = true
|
||||
}
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'EnryuManga'
|
||||
extClass = '.EnryuManga'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://enryumanga.com'
|
||||
overrideVersionCode = 0
|
||||
baseUrl = 'https://enryumanga.net'
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -2,4 +2,4 @@ package eu.kanade.tachiyomi.extension.en.enryumanga
|
|||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
|
||||
class EnryuManga : MangaThemesia("EnryuManga", "https://enryumanga.com", "en")
|
||||
class EnryuManga : MangaThemesia("EnryuManga", "https://enryumanga.net", "en")
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'Eros Scans'
|
||||
extClass = '.ErosScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://erosscans.xyz'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 40 KiB |
|
@ -0,0 +1,14 @@
|
|||
package eu.kanade.tachiyomi.extension.en.erosscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
||||
class ErosScans : MangaThemesia(
|
||||
"Eros Scans",
|
||||
"https://erosscans.xyz",
|
||||
"en",
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
ext {
|
||||
extName = 'Fire Scans'
|
||||
extClass = '.FireScans'
|
||||
extName = 'Firecomics'
|
||||
extClass = '.Firecomics'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://firescans.xyz'
|
||||
overrideVersionCode = 1
|
||||
baseUrl = 'https://firecomics.org'
|
||||
overrideVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 68 KiB |
|
@ -11,8 +11,9 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
|
||||
class Firecomics : Madara("Firecomics", "https://firecomics.org", "en") {
|
||||
|
||||
override val id: Long = 5761461704760730187
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimit(20, 5)
|
||||
.build()
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Hentai2Read'
|
||||
extClass = '.Hentai2Read'
|
||||
extVersionCode = 16
|
||||
extVersionCode = 17
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
ext {
|
||||
extName = 'HentaiDex'
|
||||
extClass = '.HentaiDex'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://dexhentai.com'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 3.6 KiB |