[Madara] Decrypt image list for Amuy and Cerise Scans (#14876)
* madata: add chapter image list decryption * add final newline * Exit early if there's no chapter protection Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * Change approach to individual sources multisrc doesn't like external dependencies * unbump baseversioncode Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									589f46de0b
								
							
						
					
					
						commit
						ca789eca79
					
				
							
								
								
									
										3
									
								
								multisrc/overrides/madara/amuy/additional.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								multisrc/overrides/madara/amuy/additional.gradle
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(project(':lib-cryptoaes'))
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,15 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.amuy
 | 
			
		||||
 | 
			
		||||
import android.util.Base64
 | 
			
		||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
 | 
			
		||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
@ -19,4 +26,44 @@ class Amuy : Madara(
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override val useNewChapterEndpoint = true
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        val chapterProtector = document.getElementById("chapter-protector-data")?.html()
 | 
			
		||||
            ?: return super.pageListParse(document)
 | 
			
		||||
 | 
			
		||||
        val password = chapterProtector
 | 
			
		||||
            .substringAfter("wpmangaprotectornonce='")
 | 
			
		||||
            .substringBefore("';")
 | 
			
		||||
        val chapterData = json.parseToJsonElement(
 | 
			
		||||
            chapterProtector
 | 
			
		||||
                .substringAfter("chapter_data='")
 | 
			
		||||
                .substringBefore("';")
 | 
			
		||||
                .replace("\\/", "/")
 | 
			
		||||
        ).jsonObject
 | 
			
		||||
 | 
			
		||||
        val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
 | 
			
		||||
        val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
 | 
			
		||||
        val ciphertext = SALTED + salt + unsaltedCiphertext
 | 
			
		||||
 | 
			
		||||
        val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
 | 
			
		||||
        val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
 | 
			
		||||
        val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
 | 
			
		||||
 | 
			
		||||
        return imgArray.mapIndexed { idx, it ->
 | 
			
		||||
            Page(idx, document.location(), it.jsonPrimitive.content)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // https://stackoverflow.com/a/66614516
 | 
			
		||||
    private fun String.decodeHex(): ByteArray {
 | 
			
		||||
        check(length % 2 == 0) { "Must have an even length" }
 | 
			
		||||
 | 
			
		||||
        return chunked(2)
 | 
			
		||||
            .map { it.toInt(16).toByte() }
 | 
			
		||||
            .toByteArray()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								multisrc/overrides/madara/cerisescans/additional.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								multisrc/overrides/madara/cerisescans/additional.gradle
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(project(':lib-cryptoaes'))
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,15 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.pt.cerisescans
 | 
			
		||||
 | 
			
		||||
import android.util.Base64
 | 
			
		||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
 | 
			
		||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
@ -19,4 +26,44 @@ class CeriseScans : Madara(
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override val useNewChapterEndpoint = true
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        val chapterProtector = document.getElementById("chapter-protector-data")?.html()
 | 
			
		||||
            ?: return super.pageListParse(document)
 | 
			
		||||
 | 
			
		||||
        val password = chapterProtector
 | 
			
		||||
            .substringAfter("wpmangaprotectornonce='")
 | 
			
		||||
            .substringBefore("';")
 | 
			
		||||
        val chapterData = json.parseToJsonElement(
 | 
			
		||||
            chapterProtector
 | 
			
		||||
                .substringAfter("chapter_data='")
 | 
			
		||||
                .substringBefore("';")
 | 
			
		||||
                .replace("\\/", "/")
 | 
			
		||||
        ).jsonObject
 | 
			
		||||
 | 
			
		||||
        val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
 | 
			
		||||
        val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
 | 
			
		||||
        val ciphertext = SALTED + salt + unsaltedCiphertext
 | 
			
		||||
 | 
			
		||||
        val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
 | 
			
		||||
        val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
 | 
			
		||||
        val imgArray = json.parseToJsonElement(imgArrayString).jsonArray
 | 
			
		||||
 | 
			
		||||
        return imgArray.mapIndexed { idx, it ->
 | 
			
		||||
            Page(idx, document.location(), it.jsonPrimitive.content)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // https://stackoverflow.com/a/66614516
 | 
			
		||||
    private fun String.decodeHex(): ByteArray {
 | 
			
		||||
        check(length % 2 == 0) { "Must have an even length" }
 | 
			
		||||
 | 
			
		||||
        return chunked(2)
 | 
			
		||||
            .map { it.toInt(16).toByte() }
 | 
			
		||||
            .toByteArray()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -358,6 +358,7 @@ abstract class Madara(
 | 
			
		||||
                            if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
                else -> {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
@ -488,6 +489,7 @@ abstract class Madara(
 | 
			
		||||
                            taxQueryIdx++
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else -> {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -816,7 +818,7 @@ abstract class Madara(
 | 
			
		||||
    }
 | 
			
		||||
    open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
 | 
			
		||||
 | 
			
		||||
    public fun String.notUpdating(): Boolean {
 | 
			
		||||
    fun String.notUpdating(): Boolean {
 | 
			
		||||
        return this.contains(updatingRegex).not()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ class MadaraGenerator : ThemeSourceGenerator {
 | 
			
		||||
        SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"),
 | 
			
		||||
        SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true),
 | 
			
		||||
        SingleLang("Aln Scans", "https://alnscans.com", "en"),
 | 
			
		||||
        SingleLang("Amuy", "https://amuyscans.com", "pt-BR", isNsfw = true),
 | 
			
		||||
        SingleLang("Amuy", "https://amuyscans.com", "pt-BR", isNsfw = true, overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("Anikiga", "https://anikiga.com", "tr"),
 | 
			
		||||
        SingleLang("Anisa Manga", "https://anisamanga.com", "tr"),
 | 
			
		||||
        SingleLang("Ansh Scans", "https://anshscans.org", "en"),
 | 
			
		||||
@ -64,7 +64,7 @@ class MadaraGenerator : ThemeSourceGenerator {
 | 
			
		||||
        SingleLang("CAT-translator", "https://cats-translator.com/manga", "th", className = "CatTranslator", overrideVersionCode = 2),
 | 
			
		||||
        SingleLang("Cat300", "https://cat300.com", "th", isNsfw = true, className = "Cat300", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("CatOnHeadTranslations", "https://catonhead.com", "en", overrideVersionCode = 2),
 | 
			
		||||
        SingleLang("Cerise Scans", "https://cerisescan.com", "pt-BR", overrideVersionCode = 3),
 | 
			
		||||
        SingleLang("Cerise Scans", "https://cerisescan.com", "pt-BR", overrideVersionCode = 4),
 | 
			
		||||
        SingleLang("Chibi Manga", "https://www.cmreader.info", "en", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("Clover Manga", "https://clover-manga.com", "tr", overrideVersionCode = 2),
 | 
			
		||||
        SingleLang("Coffee Manga", "https://coffeemanga.io", "en", overrideVersionCode = 1),
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user