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")
}
baseVersionCode = 2
baseVersionCode = 1

View File

@ -291,7 +291,7 @@ abstract class BlogTruyen(
override fun pageListParse(document: Document): List<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")))
}

View File

@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log
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
* you have this extension installed, but still let the main tachiyomi app control
* things.

View File

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

View File

@ -72,8 +72,6 @@ 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)
@ -208,7 +206,7 @@ abstract class HeanCms(
val result = json.parseAs<HeanCmsQuerySearchDto>()
val mangaList = result.data.map {
it.toSManga(cdnUrl, coverPath, mangaSubDirectory)
it.toSManga(apiUrl, coverPath, mangaSubDirectory)
}
return MangasPage(mangaList, result.meta?.hasNextPage() ?: false)
@ -244,7 +242,7 @@ abstract class HeanCms(
val seriesResult = result.getOrNull()
?: 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 {
status = status.takeUnless { it == SManga.UNKNOWN }
@ -347,8 +345,8 @@ abstract class HeanCms(
}
}
protected open fun String.toAbsoluteUrl(): String {
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this"
private fun String.toAbsoluteUrl(): String {
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)

View File

@ -63,7 +63,7 @@ class HeanCmsSeriesDto(
) {
fun toSManga(
cdnUrl: String,
apiUrl: 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(cdnUrl, coverPath)
?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
url = "/$mangaSubDirectory/$slug#$id"
}
@ -103,7 +103,6 @@ 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,
@ -115,10 +114,6 @@ class HeanCmsChapterDto(
): SChapter = SChapter.create().apply {
name = this@HeanCmsChapterDto.name.trim()
if (title != null) {
name += " - ${title.trim()}"
}
if (price != 0) {
name += " \uD83D\uDD12"
}
@ -166,8 +161,8 @@ class HeanCmsGenreDto(
val name: String,
)
private fun String.toAbsoluteThumbnailUrl(cdnUrl: String, coverPath: String): String {
return if (startsWith("https://") || startsWith("http://")) this else "$cdnUrl/$coverPath$this"
private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): String {
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
}
fun String.toStatus(): Int = when (this) {

View File

@ -2,4 +2,4 @@ plugins {
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 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
@ -30,10 +31,14 @@ class Manga(
private val seriesStatus: String? = null,
val genres: List<Genre> = emptyList(),
) {
fun toSManga() = SManga.create().apply {
fun toSManga(baseUrl: String) = SManga.create().apply {
url = "$slug#$id"
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() }
artist = this@Manga.artist?.takeUnless { it.isEmpty() }
description = buildString {

View File

@ -14,7 +14,6 @@ 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(
@ -44,7 +43,7 @@ abstract class Iken(
it.genres.map { genre ->
genre.name to genre.id.toString()
}
}.distinct()
}
}
.associateBy { it.slug }
}
@ -57,7 +56,7 @@ abstract class Iken(
.map { it.absUrl("href").substringAfterLast("/series/") }
val entries = slugs.mapNotNull {
titleCache[it]?.toSManga()
titleCache[it]?.toSManga(baseUrl)
}
return MangasPage(entries, false)
@ -85,7 +84,7 @@ abstract class Iken(
val entries = data.posts
.filterNot { it.isNovel }
.map { it.toSManga() }
.map { it.toSManga(baseUrl) }
val hasNextPage = data.totalCount > (page * perPage)
@ -99,28 +98,32 @@ 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 fetchMangaDetails(manga: SManga): Observable<SManga> {
val slug = manga.url.substringBeforeLast("#")
val update = titleCache[slug]?.toSManga() ?: manga
override fun mangaDetailsParse(response: Response): SManga {
val data = response.parseAs<Post<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) =
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 chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val data = response.parseAs<Post<ChapterListResponse>>()
@ -135,7 +138,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"))
}
}

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.exhentainetbr.ExHentaiNetBRUrlActivity"
android:name="eu.kanade.tachiyomi.multisrc.lectortmo.LectorTmoUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
@ -11,11 +12,10 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="exhentai.net.br"
android:pathPattern="/manga/..*"
android:scheme="https" />
android:host="${SOURCEHOST}"
android:pathPattern="/library/..*/..*/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</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.app.Application
@ -42,7 +42,6 @@ abstract class LectorTmo(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val rateLimitClient: OkHttpClient,
) : ParsedHttpSource(), ConfigurableSource {
private val preferences: SharedPreferences by lazy {
@ -92,7 +91,7 @@ abstract class LectorTmo(
}
private val ignoreSslClient: OkHttpClient by lazy {
rateLimitClient.newBuilder()
network.cloudflareClient.newBuilder()
.ignoreAllSSLErrors()
.followRedirects(false)
.rateLimit(
@ -104,7 +103,7 @@ abstract class LectorTmo(
private var lastCFDomain: String = ""
override val client: OkHttpClient by lazy {
rateLimitClient.newBuilder()
network.cloudflareClient.newBuilder()
.addInterceptor { chain ->
val request = chain.request()
val url = request.url
@ -245,8 +244,6 @@ abstract class LectorTmo(
return super.getMangaUrl(manga)
}
override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, tmoHeaders)
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h2.element-subtitle").text()
document.select("h5.card-title").let {
@ -274,8 +271,6 @@ abstract class LectorTmo(
return super.getChapterUrl(chapter)
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
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

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.content.ActivityNotFoundException
@ -19,7 +19,7 @@ class LectorTmoUrlActivity : Activity() {
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "slug:$type/$id/$slug")
putExtra("query", "${LectorTmo.PREFIX_SLUG_SEARCH}$type/$id/$slug")
putExtra("filter", packageName)
}

View File

@ -5,6 +5,7 @@ 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
@ -31,18 +32,8 @@ 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).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()
}
}
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
@ -56,11 +47,183 @@ 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<Map<String, String>>? = null
private var cachedValue: SoftReference<String>? = null
private var fetchTime = 0L
private suspend fun getUrlMapInternal(): Map<String, String> {
suspend fun get(): String {
if (fetchTime + 3600000 < System.currentTimeMillis()) {
// reset cache
cachedValue = null
@ -75,104 +238,22 @@ abstract class MangaThemesiaAlt(
return it
}
fetchUrlMap().also {
cachedValue = SoftReference(it)
initializer().also { set(it) }
}
}
fun set(newVal: String) {
cachedValue = SoftReference(newVal)
fetchTime = System.currentTimeMillis()
preferences.urlMapCache = it
}
}
saveCache(newVal)
}
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 peek(): String? {
return cachedValue?.get()
}
protected fun getUrlMap(cached: Boolean = false): Map<String, String> {
return if (cached && cachedValue == null) {
preferences.urlMapCache
} else {
runBlocking { getUrlMapInternal() }
fun blockingGet(): String {
return runBlocking { get() }
}
}
// 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)
}

View File

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

View File

@ -384,14 +384,11 @@ 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()
chapter.scanlator = when {
group.isNotBlank() -> group
user.isNotBlank() -> user
else -> "Unknown"
if (group != "") {
chapter.scanlator = group
}
if (time != "") {
chapter.date_upload = parseChapterDate(time)

View File

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

View File

@ -463,16 +463,7 @@ abstract class Comick(
override fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<PageList>()
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 ->
return result.chapter.images.mapIndexedNotNull { index, data ->
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 {
extName = 'Hentai Cosplay'
extClass = '.HentaiCosplay'
extVersionCode = 4
extVersionCode = 3
isNsfw = true
}

View File

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

View File

@ -1,8 +1,8 @@
ext {
extName = 'Xmanhwa'
extClass = '.Xmanhwa'
extName = 'MangaTop.site'
extClass = '.MangaTopSite'
themePkg = 'madara'
baseUrl = 'https://www.xmanhwa.me'
baseUrl = 'https://mangatop.site'
overrideVersionCode = 0
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 {
extName = 'NHentai'
extClass = '.NHFactory'
extVersionCode = 41
extVersionCode = 40
isNsfw = true
}

View File

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

View File

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

View File

@ -14,14 +14,14 @@ class ThunderScansFactory : SourceFactory {
class ThunderScansAR : MangaThemesiaAlt(
"Thunder Scans",
"https://thunderscans.com",
"https://ar-thunderepic.com",
"ar",
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
)
class ThunderScansEN : MangaThemesiaAlt(
"Thunder Scans",
"https://en-thunderscans.com",
"https://en-thunderepic.com",
"en",
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'
extClass = '.MangaNoon'
themePkg = 'mangathemesia'
baseUrl = 'https://noonscan.net'
overrideVersionCode = 4
baseUrl = 'https://manjanoon.co'
overrideVersionCode = 3
isNsfw = false
}

View File

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

View File

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

View File

@ -1,9 +1,6 @@
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
@ -14,30 +11,4 @@ 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""")

View File

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

View File

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

View File

@ -2,8 +2,8 @@ ext {
extName = 'Rocks Manga'
extClass = '.RocksManga'
themePkg = 'madara'
baseUrl = 'https://rocksmanga.com'
overrideVersionCode = 1
baseUrl = 'https://rocks-manga.com'
overrideVersionCode = 0
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.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://rocksmanga.com",
"https://rocks-manga.com",
"ar",
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ar")),
) {
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 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 val mangaDetailsSelectorStatus = ".status"
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 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 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(".num")!!.text()
chapter.name = element.selectFirst(".detail-ch")!!.text()
return chapter
}
}

View File

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

View File

@ -1,12 +1,7 @@
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
@ -19,19 +14,15 @@ 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(), ConfigurableSource {
class TeamX : ParsedHttpSource() {
override val name = "Team X"
private val defaultBaseUrl = "https://teamoney.site"
override val baseUrl by lazy { getPrefBaseUrl() }
override val baseUrl = "https://teamxnovel.com"
override val lang = "ar"
@ -43,10 +34,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
.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 {
@ -143,14 +130,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
}
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 != "غير معروف" }
}
}
@ -208,14 +187,6 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
}.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> {
@ -225,41 +196,4 @@ class TeamX : ParsedHttpSource(), ConfigurableSource {
}
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 {
extName = 'Asura Scans'
extClass = '.AsuraScans'
extVersionCode = 39
extVersionCode = 36
}
apply from: "$rootDir/common.gradle"

View File

@ -205,12 +205,7 @@ 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 = 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()
genre = document.select("div[class^=space] > div.flex > button.text-white").joinToString { it.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 chapterListSelector() = "div.scrollbar-thumb-themecolor > div.group"
override fun chapterListSelector() = "div.scrollbar-thumb-themecolor > a.block"
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()
date_upload = try {
val text = element.selectFirst("h3:eq(1)")!!.ownText()
@ -258,7 +253,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"))
}
}

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 {
extName = 'ColoredManga'
extClass = '.ColoredManga'
extVersionCode = 35
extVersionCode = 3
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'
extClass = '.EnryuManga'
themePkg = 'mangathemesia'
baseUrl = 'https://enryumanga.net'
overrideVersionCode = 1
baseUrl = 'https://enryumanga.com'
overrideVersionCode = 0
}
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
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 {
extName = 'Firecomics'
extClass = '.Firecomics'
extName = 'Fire Scans'
extClass = '.FireScans'
themePkg = 'madara'
baseUrl = 'https://firecomics.org'
overrideVersionCode = 2
baseUrl = 'https://firescans.xyz'
overrideVersionCode = 1
}
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 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()
.rateLimit(20, 5)
.build()

View File

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

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