OPSCANS: move to Madara (#862)

This commit is contained in:
AwkwardPeak7 2024-01-31 21:34:42 +05:00 committed by GitHub
parent dd95fe5fd0
commit e64519d783
10 changed files with 16 additions and 238 deletions

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.en.opscans
import eu.kanade.tachiyomi.multisrc.madara.Madara
class OPSCANS : Madara("OPSCANS", "https://opchapters.com", "en") {
override val versionId = 2
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
}

View File

@ -371,6 +371,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("Oh No Manga", "https://ohnomanga.com", "en", isNsfw = true), SingleLang("Oh No Manga", "https://ohnomanga.com", "en", isNsfw = true),
SingleLang("Olaoe", "https://olaoe.cyou", "ar"), SingleLang("Olaoe", "https://olaoe.cyou", "ar"),
SingleLang("OnlyManhwa", "https://onlymanhwa.org", "en", isNsfw = true), SingleLang("OnlyManhwa", "https://onlymanhwa.org", "en", isNsfw = true),
SingleLang("OPSCANS", "https://opchapters.com", "en"),
SingleLang("Pantheon Scan", "https://pantheon-scan.com", "fr", overrideVersionCode = 1), SingleLang("Pantheon Scan", "https://pantheon-scan.com", "fr", overrideVersionCode = 1),
SingleLang("Paragon Scans", "https://paragonscans.com", "en", isNsfw = true), SingleLang("Paragon Scans", "https://paragonscans.com", "en", isNsfw = true),
SingleLang("Passa Mão Scan", "https://passamaoscan.com", "pt-BR", isNsfw = true, className = "PassaMaoScan"), SingleLang("Passa Mão Scan", "https://passamaoscan.com", "pt-BR", isNsfw = true, className = "PassaMaoScan"),

View File

@ -1,11 +0,0 @@
ext {
extName = 'OPSCANS'
extClass = '.OpScans'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:dataimage"))
}

View File

@ -1,181 +0,0 @@
package eu.kanade.tachiyomi.extension.en.opscans
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.Headers
import okhttp3.Interceptor
import okhttp3.MultipartBody
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.lang.UnsupportedOperationException
import java.text.SimpleDateFormat
import java.util.Locale
class OpScans : HttpSource() {
override val name = "OPSCANS"
override val lang = "en"
override val baseUrl = "https://opchapters.com"
private val apiUrl = "https://opscanlations.com"
override val supportsLatest = false
private val json: Json by injectLazy()
override val versionId = 2
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::imageInterceptor)
.addInterceptor(DataImageInterceptor())
.build()
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request {
return GET("$apiUrl/api/mangaData", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val mangaData = response.parseAs<List<MangaData>>()
return MangasPage(
mangaData.map { it.toSManga() },
false,
)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$apiUrl/api/mangaData#${query.trim()}", headers)
}
override fun searchMangaParse(response: Response): MangasPage {
val mangaData = response.parseAs<List<MangaData>>()
val query = response.request.url.fragment!!
return MangasPage(
mangaData.filter {
it.name.contains(query, true) ||
it.author.contains(query, true) ||
it.info.contains(query, true)
}.map { it.toSManga() },
false,
)
}
override fun mangaDetailsRequest(manga: SManga): Request {
return GET("$apiUrl/api/mangaData#${manga.url}", headers)
}
override fun getMangaUrl(manga: SManga) = "$baseUrl/${manga.url}"
override fun mangaDetailsParse(response: Response): SManga {
val mangaData = response.parseAs<List<MangaData>>()
val mangaId = response.request.url.fragment!!
return mangaData.firstOrNull { it.id == mangaId }!!.toSManga()
}
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun getChapterUrl(chapter: SChapter) = "$baseUrl${chapter.url}"
override fun chapterListParse(response: Response): List<SChapter> {
val mangaData = response.parseAs<List<MangaData>>()
val mangaId = response.request.url.fragment!!
return mangaData.firstOrNull { it.id == mangaId }
?.chapters.orEmpty().map {
SChapter.create().apply {
url = "/$mangaId/${it.id}"
name = it.number + if (it.title.isNullOrEmpty()) "" else ": ${it.title}"
date_upload = runCatching {
dateFormat.parse(it.date!!)!!.time
}.getOrDefault(0L)
}
}.reversed()
}
override fun pageListRequest(chapter: SChapter): Request {
return GET("$apiUrl/api/mangaData#${chapter.url}", headers)
}
override fun pageListParse(response: Response): List<Page> {
val mangaData = response.parseAs<List<MangaData>>()
val ids = response.request.url.fragment!!.split("/")
val mangaId = ids[1]
val chapterId = ids[2]
return mangaData.firstOrNull { it.id == mangaId }
?.chapters?.firstOrNull { it.id == chapterId }
?.images.orEmpty().mapIndexed { idx, img ->
Page(idx, "", "https://127.0.0.1/image#${img.source}")
}
}
private fun imageInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url
if (url.pathSegments.lastOrNull() != "image" || url.fragment.isNullOrEmpty()) {
return chain.proceed(request)
}
val image = url.fragment!!
val boundary = buildString {
append((1..9).random())
repeat(28) {
append((0..9).random())
}
}
val form = MultipartBody.Builder("-----------------------------$boundary").apply {
setType(MultipartBody.FORM)
addPart(
Headers.headersOf("Content-Disposition", "form-data; name=\"image\""),
image.toRequestBody(null),
)
}.build()
val response = client.newCall(
POST("$apiUrl/api/loadImages", headers, form),
).execute().parseAs<ImageResponse>()
val newUrl = "https://127.0.0.1/?${response.image.substringAfter(":")}"
return chain.proceed(
request.newBuilder()
.url(newUrl)
.build(),
)
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body.string())
}
companion object {
private val dateFormat by lazy {
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
}
}
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
}

View File

@ -1,46 +0,0 @@
package eu.kanade.tachiyomi.extension.en.opscans
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.Serializable
@Serializable
data class MangaData(
val id: String,
val name: String,
val author: String,
val info: String,
val genre1: String,
val genre2: String,
val genre3: String,
val cover: String,
val chapters: List<Chapter>,
) {
fun toSManga() = SManga.create().apply {
url = id
title = name
author = this@MangaData.author
description = info
genre = listOf(genre1, genre2, genre3).joinToString()
thumbnail_url = "https://127.0.0.1/image#$cover"
initialized = true
}
}
@Serializable
data class Chapter(
val id: String,
val title: String? = "",
val date: String? = "",
val number: String,
val images: List<Image>? = emptyList(),
)
@Serializable
data class Image(
val source: String,
)
@Serializable
data class ImageResponse(
val image: String,
)