Compare commits

..

No commits in common. "5ecf338be0dd21937706e3284fc9d0edc5950e62" and "ac57f5e3dde6197b96a3ae66e311c3dfeac4ec1d" have entirely different histories.

468 changed files with 2903 additions and 3746 deletions

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 2 baseVersionCode = 1

View File

@ -291,7 +291,7 @@ abstract class BlogTruyen(
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select(".content > img, #content > img").forEachIndexed { i, e -> document.select("#content > img").forEachIndexed { i, e ->
pages.add(Page(i, imageUrl = e.absUrl("src"))) pages.add(Page(i, imageUrl = e.absUrl("src")))
} }

View File

@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import kotlin.system.exitProcess import kotlin.system.exitProcess
/** /**
* Springboard that accepts https://1.readmanga.io/xxx intents and redirects them to * Springboard that accepts https://readmanga.live/xxx intents and redirects them to
* the main tachiyomi process. The idea is to not install the intent filter unless * 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 * you have this extension installed, but still let the main tachiyomi app control
* things. * things.

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 27 baseVersionCode = 25
dependencies { dependencies {
api(project(":lib:i18n")) api(project(":lib:i18n"))

View File

@ -72,8 +72,6 @@ abstract class HeanCms(
protected open val coverPath: String = "" protected open val coverPath: String = ""
protected open val cdnUrl = apiUrl
protected open val mangaSubDirectory: String = "series" protected open val mangaSubDirectory: String = "series"
protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US) protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US)
@ -208,7 +206,7 @@ abstract class HeanCms(
val result = json.parseAs<HeanCmsQuerySearchDto>() val result = json.parseAs<HeanCmsQuerySearchDto>()
val mangaList = result.data.map { val mangaList = result.data.map {
it.toSManga(cdnUrl, coverPath, mangaSubDirectory) it.toSManga(apiUrl, coverPath, mangaSubDirectory)
} }
return MangasPage(mangaList, result.meta?.hasNextPage() ?: false) return MangasPage(mangaList, result.meta?.hasNextPage() ?: false)
@ -244,7 +242,7 @@ abstract class HeanCms(
val seriesResult = result.getOrNull() val seriesResult = result.getOrNull()
?: throw Exception(intl.format("url_changed_error", name, name)) ?: throw Exception(intl.format("url_changed_error", name, name))
val seriesDetails = seriesResult.toSManga(cdnUrl, coverPath, mangaSubDirectory) val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory)
return seriesDetails.apply { return seriesDetails.apply {
status = status.takeUnless { it == SManga.UNKNOWN } status = status.takeUnless { it == SManga.UNKNOWN }
@ -347,8 +345,8 @@ abstract class HeanCms(
} }
} }
protected open fun String.toAbsoluteUrl(): String { private fun String.toAbsoluteUrl(): String {
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this" return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
} }
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)

View File

@ -63,7 +63,7 @@ class HeanCmsSeriesDto(
) { ) {
fun toSManga( fun toSManga(
cdnUrl: String, apiUrl: String,
coverPath: String, coverPath: String,
mangaSubDirectory: String, mangaSubDirectory: String,
): SManga = SManga.create().apply { ): SManga = SManga.create().apply {
@ -79,7 +79,7 @@ class HeanCmsSeriesDto(
.sortedBy(HeanCmsTagDto::name) .sortedBy(HeanCmsTagDto::name)
.joinToString { it.name } .joinToString { it.name }
thumbnail_url = thumbnail.ifEmpty { null } thumbnail_url = thumbnail.ifEmpty { null }
?.toAbsoluteThumbnailUrl(cdnUrl, coverPath) ?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
url = "/$mangaSubDirectory/$slug#$id" url = "/$mangaSubDirectory/$slug#$id"
} }
@ -103,7 +103,6 @@ class HeanCmsChapterPayloadDto(
class HeanCmsChapterDto( class HeanCmsChapterDto(
private val id: Int, private val id: Int,
@SerialName("chapter_name") private val name: String, @SerialName("chapter_name") private val name: String,
@SerialName("chapter_title") private val title: String? = null,
@SerialName("chapter_slug") private val slug: String, @SerialName("chapter_slug") private val slug: String,
@SerialName("created_at") private val createdAt: String, @SerialName("created_at") private val createdAt: String,
val price: Int? = null, val price: Int? = null,
@ -115,10 +114,6 @@ class HeanCmsChapterDto(
): SChapter = SChapter.create().apply { ): SChapter = SChapter.create().apply {
name = this@HeanCmsChapterDto.name.trim() name = this@HeanCmsChapterDto.name.trim()
if (title != null) {
name += " - ${title.trim()}"
}
if (price != 0) { if (price != 0) {
name += " \uD83D\uDD12" name += " \uD83D\uDD12"
} }
@ -166,8 +161,8 @@ class HeanCmsGenreDto(
val name: String, val name: String,
) )
private fun String.toAbsoluteThumbnailUrl(cdnUrl: String, coverPath: String): String { private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): String {
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this" return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
} }
fun String.toStatus(): Int = when (this) { fun String.toStatus(): Int = when (this) {

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 3 baseVersionCode = 1

View File

@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -30,10 +31,14 @@ class Manga(
private val seriesStatus: String? = null, private val seriesStatus: String? = null,
val genres: List<Genre> = emptyList(), val genres: List<Genre> = emptyList(),
) { ) {
fun toSManga() = SManga.create().apply { fun toSManga(baseUrl: String) = SManga.create().apply {
url = "$slug#$id" url = "$slug#$id"
title = postTitle title = postTitle
thumbnail_url = featuredImage thumbnail_url = "$baseUrl/_next/image".toHttpUrl().newBuilder().apply {
addQueryParameter("url", featuredImage)
addQueryParameter("w", "828")
addQueryParameter("q", "75")
}.toString()
author = this@Manga.author?.takeUnless { it.isEmpty() } author = this@Manga.author?.takeUnless { it.isEmpty() }
artist = this@Manga.artist?.takeUnless { it.isEmpty() } artist = this@Manga.artist?.takeUnless { it.isEmpty() }
description = buildString { description = buildString {

View File

@ -14,7 +14,6 @@ import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
abstract class Iken( abstract class Iken(
@ -44,7 +43,7 @@ abstract class Iken(
it.genres.map { genre -> it.genres.map { genre ->
genre.name to genre.id.toString() genre.name to genre.id.toString()
} }
}.distinct() }
} }
.associateBy { it.slug } .associateBy { it.slug }
} }
@ -57,7 +56,7 @@ abstract class Iken(
.map { it.absUrl("href").substringAfterLast("/series/") } .map { it.absUrl("href").substringAfterLast("/series/") }
val entries = slugs.mapNotNull { val entries = slugs.mapNotNull {
titleCache[it]?.toSManga() titleCache[it]?.toSManga(baseUrl)
} }
return MangasPage(entries, false) return MangasPage(entries, false)
@ -85,7 +84,7 @@ abstract class Iken(
val entries = data.posts val entries = data.posts
.filterNot { it.isNovel } .filterNot { it.isNovel }
.map { it.toSManga() } .map { it.toSManga(baseUrl) }
val hasNextPage = data.totalCount > (page * perPage) val hasNextPage = data.totalCount > (page * perPage)
@ -99,28 +98,32 @@ abstract class Iken(
Filter.Header("Open popular mangas if genre filter is empty"), 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 { override fun getMangaUrl(manga: SManga): String {
val slug = manga.url.substringBeforeLast("#") val slug = manga.url.substringBeforeLast("#")
return "$baseUrl/series/$slug" return "$baseUrl/series/$slug"
} }
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun mangaDetailsParse(response: Response): SManga {
val slug = manga.url.substringBeforeLast("#") val data = response.parseAs<Post<Manga>>()
val update = titleCache[slug]?.toSManga() ?: manga
return Observable.just(update) 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()
}
} }
override fun mangaDetailsParse(response: Response) = override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
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> { override fun chapterListParse(response: Response): List<SChapter> {
val data = response.parseAs<Post<ChapterListResponse>>() val data = response.parseAs<Post<ChapterListResponse>>()
@ -135,7 +138,7 @@ abstract class Iken(
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup() 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")) Page(idx, imageUrl = img.absUrl("src"))
} }
} }

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application> <application>
<activity <activity
android:name=".pt.exhentainetbr.ExHentaiNetBRUrlActivity" android:name="eu.kanade.tachiyomi.multisrc.lectortmo.LectorTmoUrlActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="true" android:exported="true"
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.NoDisplay">
@ -11,11 +12,10 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:host="exhentai.net.br" android:host="${SOURCEHOST}"
android:pathPattern="/manga/..*" android:pathPattern="/library/..*/..*/..*"
android:scheme="https" /> android:scheme="${SOURCESCHEME}" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

@ -0,0 +1,5 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 6

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.es.lectortmo package eu.kanade.tachiyomi.multisrc.lectortmo
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
@ -42,7 +42,6 @@ abstract class LectorTmo(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String, override val lang: String,
private val rateLimitClient: OkHttpClient,
) : ParsedHttpSource(), ConfigurableSource { ) : ParsedHttpSource(), ConfigurableSource {
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
@ -92,7 +91,7 @@ abstract class LectorTmo(
} }
private val ignoreSslClient: OkHttpClient by lazy { private val ignoreSslClient: OkHttpClient by lazy {
rateLimitClient.newBuilder() network.cloudflareClient.newBuilder()
.ignoreAllSSLErrors() .ignoreAllSSLErrors()
.followRedirects(false) .followRedirects(false)
.rateLimit( .rateLimit(
@ -104,7 +103,7 @@ abstract class LectorTmo(
private var lastCFDomain: String = "" private var lastCFDomain: String = ""
override val client: OkHttpClient by lazy { override val client: OkHttpClient by lazy {
rateLimitClient.newBuilder() network.cloudflareClient.newBuilder()
.addInterceptor { chain -> .addInterceptor { chain ->
val request = chain.request() val request = chain.request()
val url = request.url val url = request.url
@ -245,8 +244,6 @@ abstract class LectorTmo(
return super.getMangaUrl(manga) return super.getMangaUrl(manga)
} }
override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, tmoHeaders)
override fun mangaDetailsParse(document: Document) = SManga.create().apply { override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h2.element-subtitle").text() title = document.select("h2.element-subtitle").text()
document.select("h5.card-title").let { document.select("h5.card-title").let {
@ -274,8 +271,6 @@ abstract class LectorTmo(
return super.getChapterUrl(chapter) return super.getChapterUrl(chapter)
} }
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.es.lectortmo package eu.kanade.tachiyomi.multisrc.lectortmo
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.es.lectortmo package eu.kanade.tachiyomi.multisrc.lectortmo
import android.app.Activity import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
@ -19,7 +19,7 @@ class LectorTmoUrlActivity : Activity() {
val mainIntent = Intent().apply { val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH" action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "slug:$type/$id/$slug") putExtra("query", "${LectorTmo.PREFIX_SLUG_SEARCH}$type/$id/$slug")
putExtra("filter", packageName) putExtra("filter", packageName)
} }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@ -31,18 +32,8 @@ abstract class MangaThemesiaAlt(
private val randomUrlPrefKey: String = "pref_auto_random_url", private val randomUrlPrefKey: String = "pref_auto_random_url",
) : MangaThemesia(name, baseUrl, lang, mangaUrlDirectory, dateFormat), ConfigurableSource { ) : 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 { protected val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000).also { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
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) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -56,11 +47,183 @@ abstract class MangaThemesiaAlt(
private fun getRandomUrlPref() = preferences.getBoolean(randomUrlPrefKey, true) 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 val mutex = Mutex()
private var cachedValue: SoftReference<Map<String, String>>? = null private var cachedValue: SoftReference<String>? = null
private var fetchTime = 0L private var fetchTime = 0L
private suspend fun getUrlMapInternal(): Map<String, String> { suspend fun get(): String {
if (fetchTime + 3600000 < System.currentTimeMillis()) { if (fetchTime + 3600000 < System.currentTimeMillis()) {
// reset cache // reset cache
cachedValue = null cachedValue = null
@ -75,104 +238,22 @@ abstract class MangaThemesiaAlt(
return it return it
} }
fetchUrlMap().also { initializer().also { set(it) }
cachedValue = SoftReference(it)
fetchTime = System.currentTimeMillis()
preferences.urlMapCache = it
}
} }
} }
protected open fun fetchUrlMap(): Map<String, String> { fun set(newVal: String) {
client.newCall(GET("$baseUrl$listUrl", headers)).execute().use { response -> cachedValue = SoftReference(newVal)
val document = response.asJsoup() fetchTime = System.currentTimeMillis()
return document.select(listSelector).associate { saveCache(newVal)
val url = it.absUrl("href")
val slug = url.removeSuffix("/")
.substringAfterLast("/")
val permaSlug = slug
.replaceFirst(slugRegex, "")
permaSlug to slug
}
}
} }
protected fun getUrlMap(cached: Boolean = false): Map<String, String> { fun peek(): String? {
return if (cached && cachedValue == null) { return cachedValue?.get()
preferences.urlMapCache
} else {
runBlocking { getUrlMapInternal() }
}
} }
// cache in preference for webview urls fun blockingGet(): String {
private var SharedPreferences.urlMapCache: Map<String, String> return runBlocking { get() }
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)
} }

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Bato.to' extName = 'Bato.to'
extClass = '.BatoToFactory' extClass = '.BatoToFactory'
extVersionCode = 37 extVersionCode = 36
isNsfw = true isNsfw = true
} }

View File

@ -384,14 +384,11 @@ open class BatoTo(
val chapter = SChapter.create() val chapter = SChapter.create()
val urlElement = element.select("a.chapt") val urlElement = element.select("a.chapt")
val group = element.select("div.extra > a:not(.ps-3)").text() 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() val time = element.select("div.extra > i.ps-3").text()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.scanlator = when { if (group != "") {
group.isNotBlank() -> group chapter.scanlator = group
user.isNotBlank() -> user
else -> "Unknown"
} }
if (time != "") { if (time != "") {
chapter.date_upload = parseChapterDate(time) chapter.date_upload = parseChapterDate(time)

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Comick' extName = 'Comick'
extClass = '.ComickFactory' extClass = '.ComickFactory'
extVersionCode = 47 extVersionCode = 46
isNsfw = true isNsfw = true
} }

View File

@ -463,16 +463,7 @@ abstract class Comick(
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<PageList>() val result = response.parseAs<PageList>()
val images = result.chapter.images.ifEmpty { return result.chapter.images.mapIndexedNotNull { index, data ->
// 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) if (data.url == null) null else Page(index = index, imageUrl = data.url)
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,127 +0,0 @@
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)
}
}
}

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hentai Cosplay' extName = 'Hentai Cosplay'
extClass = '.HentaiCosplay' extClass = '.HentaiCosplay'
extVersionCode = 4 extVersionCode = 3
isNsfw = true isNsfw = true
} }

View File

@ -24,7 +24,7 @@ class HentaiCosplay : HttpSource() {
override val name = "Hentai Cosplay" override val name = "Hentai Cosplay"
override val baseUrl = "https://hentai-cosplay-xxx.com" override val baseUrl = "https://hentai-cosplays.com"
override val lang = "all" override val lang = "all"

View File

@ -1,8 +1,8 @@
ext { ext {
extName = 'Xmanhwa' extName = 'MangaTop.site'
extClass = '.Xmanhwa' extClass = '.MangaTopSite'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://www.xmanhwa.me' baseUrl = 'https://mangatop.site'
overrideVersionCode = 0 overrideVersionCode = 0
isNsfw = true isNsfw = true
} }

View File

@ -0,0 +1,15 @@
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 = ""
}

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'NHentai' extName = 'NHentai'
extClass = '.NHFactory' extClass = '.NHFactory'
extVersionCode = 41 extVersionCode = 40
isNsfw = true isNsfw = true
} }

View File

@ -220,7 +220,7 @@ open class NHentai(
thumbnail_url = document.select("#cover > a > img").attr("data-src") thumbnail_url = document.select("#cover > a > img").attr("data-src")
status = SManga.COMPLETED status = SManga.COMPLETED
artist = getArtists(document) artist = getArtists(document)
author = getGroups(document) author = artist
// Some people want these additional details in description // Some people want these additional details in description
description = "Full English and Japanese titles:\n" description = "Full English and Japanese titles:\n"
.plus("$fullTitle\n") .plus("$fullTitle\n")

View File

@ -2,8 +2,8 @@ ext {
extName = 'Thunder Scans' extName = 'Thunder Scans'
extClass = '.ThunderScansFactory' extClass = '.ThunderScansFactory'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://en-thunderscans.com' baseUrl = 'https://en-thunderepic.com'
overrideVersionCode = 4 overrideVersionCode = 2
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -14,14 +14,14 @@ class ThunderScansFactory : SourceFactory {
class ThunderScansAR : MangaThemesiaAlt( class ThunderScansAR : MangaThemesiaAlt(
"Thunder Scans", "Thunder Scans",
"https://thunderscans.com", "https://ar-thunderepic.com",
"ar", "ar",
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")), dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
) )
class ThunderScansEN : MangaThemesiaAlt( class ThunderScansEN : MangaThemesiaAlt(
"Thunder Scans", "Thunder Scans",
"https://en-thunderscans.com", "https://en-thunderepic.com",
"en", "en",
mangaUrlDirectory = "/comics", mangaUrlDirectory = "/comics",
) )

View File

@ -1,10 +0,0 @@
ext {
extName = 'MangaHub'
extClass = '.MangaHub'
themePkg = 'zeistmanga'
baseUrl = 'https://www.mangahub.link'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,23 +0,0 @@
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"
}

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaNoon' extName = 'MangaNoon'
extClass = '.MangaNoon' extClass = '.MangaNoon'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://noonscan.net' baseUrl = 'https://manjanoon.co'
overrideVersionCode = 4 overrideVersionCode = 3
isNsfw = false isNsfw = false
} }

View File

@ -7,7 +7,7 @@ import java.util.Calendar
class MangaNoon : MangaThemesia( class MangaNoon : MangaThemesia(
"مانجا نون", "مانجا نون",
"https://noonscan.net", "https://manjanoon.co",
"ar", "ar",
) { ) {

View File

@ -3,7 +3,7 @@ ext {
extClass = '.MangaPro' extClass = '.MangaPro'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://promanga.pro' baseUrl = 'https://promanga.pro'
overrideVersionCode = 3 overrideVersionCode = 2
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,9 +1,6 @@
package eu.kanade.tachiyomi.extension.ar.mangapro package eu.kanade.tachiyomi.extension.ar.mangapro
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia 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.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -14,30 +11,4 @@ class MangaPro : MangaThemesia(
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) { ) {
override val versionId = 3 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""")

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaSwat' extName = 'MangaSwat'
extClass = '.MangaSwat' extClass = '.MangaSwat'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://tatwt.com' baseUrl = 'https://maxlevelteam.com'
overrideVersionCode = 21 overrideVersionCode = 20
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -24,7 +24,7 @@ import java.util.Locale
class MangaSwat : class MangaSwat :
MangaThemesia( MangaThemesia(
"MangaSwat", "MangaSwat",
"https://tatwt.com", "https://maxlevelteam.com",
"ar", "ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
), ),

View File

@ -2,8 +2,8 @@ ext {
extName = 'Rocks Manga' extName = 'Rocks Manga'
extClass = '.RocksManga' extClass = '.RocksManga'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://rocksmanga.com' baseUrl = 'https://rocks-manga.com'
overrideVersionCode = 1 overrideVersionCode = 0
isNsfw = false isNsfw = false
} }

View File

@ -2,65 +2,39 @@ package eu.kanade.tachiyomi.extension.ar.rocksmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class RocksManga : Madara( class RocksManga : Madara(
"Rocks Manga", "Rocks Manga",
"https://rocksmanga.com", "https://rocks-manga.com",
"ar", "ar",
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar")), dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar")),
) { ) {
override fun popularMangaSelector() = "div.page-content-listing > .manga" override fun popularMangaSelector() = ".shido-manga"
override val popularMangaUrlSelector = "div.manga-poster a" override val popularMangaUrlSelector = "a.s-manga-title"
override val mangaDetailsSelectorTitle = ".title"
override fun popularMangaFromElement(element: Element): SManga { override val mangaDetailsSelectorAuthor = ".heading:contains(المؤلف:) + .content a"
return super.popularMangaFromElement(element).apply { override val mangaDetailsSelectorArtist = ".heading:contains(الرسام:) + .content a"
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 mangaDetailsSelectorStatus = ".status"
override val mangaDetailsSelectorDescription = "div.description" override val mangaDetailsSelectorDescription = ".story"
override val mangaDetailsSelectorThumbnail = ".manga-poster img" override val mangaDetailsSelectorThumbnail = ".profile-manga .poster img"
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a" override val mangaDetailsSelectorGenre = ".heading:contains(التصنيف:) + .content a"
override val altNameSelector = "div.alternative" override val altNameSelector = ".other-name"
override fun chapterListSelector() = ".chapters-list li.chapter-item" override fun chapterListSelector() = "#chapter-list li.chapter-item"
override fun chapterDateSelector() = ".chapter-release-date" override fun chapterDateSelector() = ".ch-post-time"
override val pageListParseSelector = ".chapter-reading-page img" override val pageListParseSelector = ".reading-content img"
override val useLoadMoreRequest = LoadMoreStrategy.Never override val useLoadMoreRequest = LoadMoreStrategy.Never
override val useNewChapterEndpoint = false override val useNewChapterEndpoint = false
override val fetchGenres = false
override val filterNonMangaItems = false override val filterNonMangaItems = false
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val chapter = super.chapterFromElement(element) val chapter = super.chapterFromElement(element)
chapter.name = element.selectFirst(".num")!!.text() chapter.name = element.selectFirst(".detail-ch")!!.text()
return chapter return chapter
} }
} }

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Team X' extName = 'Team X'
extClass = '.TeamX' extClass = '.TeamX'
extVersionCode = 18 extVersionCode = 17
isNsfw = false isNsfw = false
} }

View File

@ -1,12 +1,7 @@
package eu.kanade.tachiyomi.extension.ar.teamx 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.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@ -19,19 +14,15 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class TeamX : ParsedHttpSource(), ConfigurableSource { class TeamX : ParsedHttpSource() {
override val name = "Team X" override val name = "Team X"
private val defaultBaseUrl = "https://teamoney.site" override val baseUrl = "https://teamxnovel.com"
override val baseUrl by lazy { getPrefBaseUrl() }
override val lang = "ar" override val lang = "ar"
@ -43,10 +34,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
.rateLimit(10, 1, TimeUnit.SECONDS) .rateLimit(10, 1, TimeUnit.SECONDS)
.build() .build()
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// Popular // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
@ -143,14 +130,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
} }
genre = document.select("div.review-author-info a").joinToString { it.text() } genre = document.select("div.review-author-info a").joinToString { it.text() }
thumbnail_url = document.select("div.text-right img").first()!!.absUrl("src") 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 != "غير معروف" }
} }
} }
@ -208,14 +187,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
}.getOrNull() ?: 0 }.getOrNull() ?: 0
} }
private fun String?.toStatus() = when (this) {
"مستمرة" -> SManga.ONGOING
"قادم قريبًا" -> SManga.ONGOING // "coming soon"
"مكتمل" -> SManga.COMPLETED
"متوقف" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
// Pages // Pages
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
@ -225,41 +196,4 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() 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()
}
}
}
} }

View File

@ -1,9 +0,0 @@
ext {
extName = 'Altay Scans'
extClass = '.AltayScans'
themePkg = 'mangathemesia'
baseUrl = 'https://altayscans.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,14 +0,0 @@
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()
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'Ansh Scans'
extClass = '.AnshScans'
themePkg = 'madara'
baseUrl = 'https://anshscans.org'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,11 @@
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"
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Astra Scans'
extClass = '.AstraScans'
themePkg = 'mangathemesia'
baseUrl = 'https://astrascans.org'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,15 +0,0 @@
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()
}

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Asura Scans' extName = 'Asura Scans'
extClass = '.AsuraScans' extClass = '.AsuraScans'
extVersionCode = 39 extVersionCode = 36
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -205,12 +205,7 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
description = document.selectFirst("span.font-medium.text-sm")?.text() 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() 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() artist = document.selectFirst("div.grid > div:has(h3:eq(0):containsOwn(Artist)) > h3:eq(1)")?.ownText()
genre = buildList { genre = document.select("div[class^=space] > div.flex > button.text-white").joinToString { it.ownText() }
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()) status = parseStatus(document.selectFirst("div.flex:has(h3:eq(0):containsOwn(Status)) > h3:eq(1)")?.ownText())
} }
@ -234,10 +229,10 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > div.group" override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > a.block"
override fun chapterFromElement(element: Element) = SChapter.create().apply { override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href").toPermSlugIfNeeded()) setUrlWithoutDomain(element.attr("abs:href").toPermSlugIfNeeded())
name = element.selectFirst("h3:eq(0)")!!.text() name = element.selectFirst("h3:eq(0)")!!.text()
date_upload = try { date_upload = try {
val text = element.selectFirst("h3:eq(1)")!!.ownText() val text = element.selectFirst("h3:eq(1)")!!.ownText()
@ -258,7 +253,7 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
} }
override fun pageListParse(document: Document): List<Page> { 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")) Page(i, imageUrl = element.attr("abs:src"))
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,156 +0,0 @@
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())
}
}

View File

@ -1,115 +0,0 @@
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,
)

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'ColoredManga' extName = 'ColoredManga'
extClass = '.ColoredManga' extClass = '.ColoredManga'
extVersionCode = 35 extVersionCode = 3
isNsfw = true isNsfw = true
} }

View File

@ -0,0 +1,9 @@
ext {
extName = 'Comic Scans'
extClass = '.ComicScans'
themePkg = 'madara'
baseUrl = 'https://www.comicscans.org'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,7 @@
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
}

View File

@ -2,8 +2,8 @@ ext {
extName = 'EnryuManga' extName = 'EnryuManga'
extClass = '.EnryuManga' extClass = '.EnryuManga'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://enryumanga.net' baseUrl = 'https://enryumanga.com'
overrideVersionCode = 1 overrideVersionCode = 0
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -2,4 +2,4 @@ package eu.kanade.tachiyomi.extension.en.enryumanga
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
class EnryuManga : MangaThemesia("EnryuManga", "https://enryumanga.net", "en") class EnryuManga : MangaThemesia("EnryuManga", "https://enryumanga.com", "en")

View File

@ -1,9 +0,0 @@
ext {
extName = 'Eros Scans'
extClass = '.ErosScans'
themePkg = 'mangathemesia'
baseUrl = 'https://erosscans.xyz'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,14 +0,0 @@
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()
}

View File

@ -1,9 +1,9 @@
ext { ext {
extName = 'Firecomics' extName = 'Fire Scans'
extClass = '.Firecomics' extClass = '.FireScans'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://firecomics.org' baseUrl = 'https://firescans.xyz'
overrideVersionCode = 2 overrideVersionCode = 1
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -11,9 +11,8 @@ import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
class Firecomics : Madara("Firecomics", "https://firecomics.org", "en") { class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
override val id: Long = 5761461704760730187
override val client: OkHttpClient = super.client.newBuilder() override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(20, 5) .rateLimit(20, 5)
.build() .build()

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hentai2Read' extName = 'Hentai2Read'
extClass = '.Hentai2Read' extClass = '.Hentai2Read'
extVersionCode = 17 extVersionCode = 16
isNsfw = true isNsfw = true
} }

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