Reaper Scans new site (#13751)
* reaperscans * use livewire to load chapterlist * bring back other langs * fix imports * add search to reaperscans Co-authored-by: henrik9999 <22085664+henrik9999@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									40b6e31505
								
							
						
					
					
						commit
						5d09f08d37
					
				@ -1,10 +1,29 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.reaperscans
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceFactory
 | 
			
		||||
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.ParsedHttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.json.JsonObject
 | 
			
		||||
import kotlinx.serialization.json.buildJsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
			
		||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.RequestBody.Companion.toRequestBody
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.Jsoup
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
@ -40,13 +59,205 @@ abstract class ReaperScans(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ReaperScansEn : ReaperScans(
 | 
			
		||||
    "https://reaperscans.com",
 | 
			
		||||
    "en",
 | 
			
		||||
    SimpleDateFormat("MMM dd,yyyy", Locale.US)
 | 
			
		||||
) {
 | 
			
		||||
class ReaperScansEn : ParsedHttpSource() {
 | 
			
		||||
    override val name = "Reaper Scans"
 | 
			
		||||
 | 
			
		||||
    override val versionId = 2
 | 
			
		||||
    override val baseUrl = "https://reaperscans.com"
 | 
			
		||||
 | 
			
		||||
    override val lang = "en"
 | 
			
		||||
 | 
			
		||||
    override val id = 5177220001642863679
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = false
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
    // Popular
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics?page=$page", headers)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector(): String = "button[wire:click*=nextPage]"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector(): String = "li"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga {
 | 
			
		||||
        return SManga.create().apply {
 | 
			
		||||
            element.select("a.text-white").let {
 | 
			
		||||
                title = it.text()
 | 
			
		||||
                setUrlWithoutDomain(it.attr("href"))
 | 
			
		||||
            }
 | 
			
		||||
            thumbnail_url = element.select("img").attr("abs:src")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Latest
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    // Details
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        thumbnail_url = document.select("div > img").first().attr("abs:src")
 | 
			
		||||
        title = document.select("h1").first().text()
 | 
			
		||||
 | 
			
		||||
        status = when (document.select("dt:contains(Source Status)").next().text()) {
 | 
			
		||||
            "On hold" -> SManga.ON_HIATUS
 | 
			
		||||
            "Complete" -> SManga.COMPLETED
 | 
			
		||||
            "Ongoing" -> SManga.ONGOING
 | 
			
		||||
            else -> SManga.UNKNOWN
 | 
			
		||||
        }
 | 
			
		||||
        description = document.select("section > div:nth-child(1) > div > p").first().text()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Chapters
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector() = "ul > li"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Recursively merges j2 onto j1 in place
 | 
			
		||||
     * If j1 and j2 both contain keys whose values aren't both jsonObjects, j2's value overwrites j1's
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    private fun mergeLeft(j1: JsonObject, j2: JsonObject): JsonObject = buildJsonObject {
 | 
			
		||||
        j1.keys.forEach { put(it, j1[it]!!) }
 | 
			
		||||
        j2.keys.forEach { k ->
 | 
			
		||||
            when {
 | 
			
		||||
                j1[k] !is JsonObject -> put(k, j2[k]!!)
 | 
			
		||||
                j1[k] is JsonObject && j2[k] is JsonObject -> put(k, mergeLeft(j1[k]!!.jsonObject, j2[k]!!.jsonObject))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        var document = response.asJsoup()
 | 
			
		||||
        val chapters = mutableListOf<SChapter>()
 | 
			
		||||
        document.select("div.pb-4 > div >" + chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
 | 
			
		||||
 | 
			
		||||
        val csrfToken = document.selectFirst("meta[name=csrf-token]")?.attr("content")
 | 
			
		||||
 | 
			
		||||
        val initialProps = document.selectFirst("div[wire:initial-data*=frontend.comic-chapters-list]")?.attr("wire:initial-data")?.let {
 | 
			
		||||
            json.parseToJsonElement(it)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (csrfToken != null && initialProps is JsonObject) {
 | 
			
		||||
            var csrf = csrfToken
 | 
			
		||||
            var serverMemo = initialProps["serverMemo"]!!.jsonObject
 | 
			
		||||
            var fingerprint = initialProps["fingerprint"]!!
 | 
			
		||||
 | 
			
		||||
            var nextPage = 2
 | 
			
		||||
            while (document.select(popularMangaNextPageSelector()).isNotEmpty()) {
 | 
			
		||||
                val payload = buildJsonObject {
 | 
			
		||||
                    put("fingerprint", fingerprint)
 | 
			
		||||
                    put("serverMemo", serverMemo)
 | 
			
		||||
                    put("updates", json.parseToJsonElement("[{\"type\":\"callMethod\",\"payload\":{\"id\":\"9jhcg\",\"method\":\"gotoPage\",\"params\":[$nextPage,\"page\"]}}]"))
 | 
			
		||||
                }.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
 | 
			
		||||
 | 
			
		||||
                val request = Request.Builder().url("$baseUrl/livewire/message/frontend.comic-chapters-list").method("POST", payload).addHeader("x-csrf-token", csrf).addHeader("x-livewire", "true").build()
 | 
			
		||||
 | 
			
		||||
                val response1 = client.newCall(request).execute()
 | 
			
		||||
                val responseText = response1.body!!.string()
 | 
			
		||||
 | 
			
		||||
                val responseJson = json.parseToJsonElement(responseText).jsonObject
 | 
			
		||||
 | 
			
		||||
                // response contains state that we need to preserve
 | 
			
		||||
                serverMemo = mergeLeft(serverMemo, responseJson["serverMemo"]!!.jsonObject)
 | 
			
		||||
 | 
			
		||||
                document = Jsoup.parse(responseJson["effects"]!!.jsonObject.get("html")?.jsonPrimitive?.content)
 | 
			
		||||
 | 
			
		||||
                document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
 | 
			
		||||
                nextPage++
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return chapters
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter {
 | 
			
		||||
        val chapter = SChapter.create()
 | 
			
		||||
 | 
			
		||||
        with(element) {
 | 
			
		||||
            select("a").first()?.let { urlElement ->
 | 
			
		||||
                chapter.setUrlWithoutDomain(urlElement.attr("abs:href"))
 | 
			
		||||
                chapter.name = urlElement.select("p").first().text()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return chapter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Search
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector(): String = "a[href*=/comics/]"
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element) = SManga.create().apply {
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
 | 
			
		||||
        element.select("img").first()?.let {
 | 
			
		||||
            thumbnail_url = it.attr("abs:src")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        title = element.select("p").first().text()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not Used")
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val response = client.newCall(GET(baseUrl)).execute()
 | 
			
		||||
        val soup = response.asJsoup()
 | 
			
		||||
 | 
			
		||||
        val csrfToken = soup.selectFirst("meta[name=csrf-token]")?.attr("content")
 | 
			
		||||
 | 
			
		||||
        val initialProps = soup.selectFirst("div[wire:initial-data*=frontend.global-search]")?.attr("wire:initial-data")?.let {
 | 
			
		||||
            json.parseToJsonElement(it)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (csrfToken != null && initialProps is JsonObject) {
 | 
			
		||||
            var serverMemo = initialProps["serverMemo"]!!.jsonObject
 | 
			
		||||
            var fingerprint = initialProps["fingerprint"]!!
 | 
			
		||||
 | 
			
		||||
            val payload = buildJsonObject {
 | 
			
		||||
                put("fingerprint", fingerprint)
 | 
			
		||||
                put("serverMemo", serverMemo)
 | 
			
		||||
                put("updates", json.parseToJsonElement("[{\"type\":\"syncInput\",\"payload\":{\"id\":\"03r6\",\"name\":\"query\",\"value\":\"$query\"}}]"))
 | 
			
		||||
            }.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
 | 
			
		||||
 | 
			
		||||
            return Request.Builder().url("$baseUrl/livewire/message/frontend.global-search").method("POST", payload).addHeader("x-csrf-token", csrfToken).addHeader("x-livewire", "true").build()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw Exception("search error")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val responseText = response.body!!.string()
 | 
			
		||||
 | 
			
		||||
        val responseJson = json.parseToJsonElement(responseText).jsonObject
 | 
			
		||||
 | 
			
		||||
        val document = Jsoup.parse(responseJson["effects"]!!.jsonObject.get("html")?.jsonPrimitive?.content)
 | 
			
		||||
 | 
			
		||||
        val mangas = document.select(searchMangaSelector()).map { element ->
 | 
			
		||||
            searchMangaFromElement(element)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MangasPage(mangas, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Page
 | 
			
		||||
 | 
			
		||||
    override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl${chapter.url}")
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("p.py-4 > img").mapIndexed { index, element ->
 | 
			
		||||
            Page(index, "", element.attr("src"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ReaperScansTr : ReaperScans(
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ class MadaraGenerator : ThemeSourceGenerator {
 | 
			
		||||
        MultiLang("MangaForFree.net", "https://mangaforfree.net", listOf("en", "ko", "all"), isNsfw = true, className = "MangaForFreeFactory", pkgName = "mangaforfree", overrideVersionCode = 1),
 | 
			
		||||
        MultiLang("Manhwa18.cc", "https://manhwa18.cc", listOf("en", "ko", "all"), isNsfw = true, className = "Manhwa18CcFactory", pkgName = "manhwa18cc", overrideVersionCode = 2),
 | 
			
		||||
        MultiLang("Olympus Scanlation", "https://olympusscanlation.com", listOf("es", "pt-BR")),
 | 
			
		||||
        MultiLang("Reaper Scans", "https://reaperscans.com", listOf("en", "fr", "id", "tr"), className = "ReaperScansFactory", pkgName = "reaperscans", overrideVersionCode = 7),
 | 
			
		||||
        MultiLang("Reaper Scans", "https://reaperscans.com", listOf("en", "fr", "id", "tr"), className = "ReaperScansFactory", pkgName = "reaperscans", overrideVersionCode = 8),
 | 
			
		||||
        MultiLang("Seven King Scanlation", "https://sksubs.net", listOf("es", "en"), isNsfw = true),
 | 
			
		||||
        SingleLang("1st Kiss Manga.love", "https://1stkissmanga.love", "en", className = "FirstKissMangaLove", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("1st Kiss Manhua", "https://1stkissmanhua.com", "en", className = "FirstKissManhua", overrideVersionCode = 3),
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user