Remove Immortal Updates (#4326)

This commit is contained in:
Norsze 2024-07-30 09:26:23 +02:00 committed by Draff
parent fa37d45021
commit 5ae345113f
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
7 changed files with 0 additions and 213 deletions

View File

@ -1,9 +0,0 @@
ext {
extName = 'Immortal Updates'
extClass = '.ImmortalUpdates'
themePkg = 'madara'
baseUrl = 'https://immortalupdates.com'
overrideVersionCode = 6
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 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: 18 KiB

View File

@ -1,204 +0,0 @@
package eu.kanade.tachiyomi.extension.en.immortalupdates
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.graphics.Rect
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.nodes.Document
import java.io.ByteArrayOutputStream
import java.io.InputStream
class ImmortalUpdates : Madara("Immortal Updates", "https://immortalupdates.com", "en") {
override val useNewChapterEndpoint: Boolean = true
override val client = super.client.newBuilder()
.rateLimit(1, 2)
.addInterceptor { chain ->
val response = chain.proceed(chain.request())
if (response.request.url.fragment?.contains(DESCRAMBLE) != true) {
return@addInterceptor response
}
val fragment = response.request.url.fragment!!
val args = fragment.substringAfter("$DESCRAMBLE=").split(",")
val image = unscrambleImage(response.body.byteStream(), args)
val body = image.toResponseBody("image/jpeg".toMediaTypeOrNull())
return@addInterceptor response.newBuilder()
.body(body)
.build()
}.build()
override fun pageListParse(document: Document): List<Page> {
val pageList = super.pageListParse(document).toMutableList()
val unscramblingCallsPage = pageList.firstOrNull { it.imageUrl!!.contains("00-call") }
?: return pageList
val unscramblingCalls = client.newCall(GET(unscramblingCallsPage.imageUrl!!, headers))
.execute()
.use { it.body.string() }
unscramblingCalls.replace("\r", "").split("\n").filter { !it.isNullOrBlank() }.forEach {
val args = unfuckJs(it)
.substringAfter("[")
.substringBefore("]")
val filenameFragment = args.split(",")[0].removeSurrounding("\"")
val page = pageList.firstOrNull { page -> page.imageUrl!!.contains(filenameFragment, ignoreCase = true) }
?: return@forEach
val newPageUrl = page.imageUrl!!.toHttpUrl().newBuilder()
.fragment("$DESCRAMBLE=$args")
.build()
.toString()
pageList[page.index] = Page(page.index, document.location(), newPageUrl)
}
pageList.remove(unscramblingCallsPage)
return pageList
}
// Converted from _0x3bc005: Find the CanvasRenderingContext2D.drawImage call basically
//
// `args` is the arguments of the original get_img call:
// get_img(file_to_match, indexer, iterations, sectionWidth, sectionHeight, isBackgroundBlack, shouldFillColor, ???, key, keyAddition, ???)
//
// The boolean after shouldFillColor seems to always be `false` so I have optimized it out for now.
// If it fucks up someone will make an issue anyways /shrug
//
// I assumed the last argument was to check if versions match or something (since it was 1.0.1)
// but it was used in some canvas thingy that I didn't bother to check
private fun unscrambleImage(image: InputStream, args: List<String>): ByteArray {
val indexer = args[1].toInt()
val iterations = args[2].toInt()
val sectionWidth = args[3].toInt()
val sectionHeight = args[4].toInt()
val isBackgroundBlack = args[5] == "true"
val shouldFillColor = args[6] == "true"
val key = args[8].toInt()
val keyAddition = args[9].toInt()
val bitmap = BitmapFactory.decodeStream(image)
val result = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
val heightSectionCount = bitmap.height / sectionHeight
val widthSectionCount = bitmap.width / sectionWidth
val sectionCount = heightSectionCount * widthSectionCount
val descramblingArray = createDescramblingArray(indexer, sectionCount, key, keyAddition, iterations)
if (shouldFillColor) {
val backgroundColor = if (isBackgroundBlack) Color.BLACK else Color.WHITE
canvas.drawColor(backgroundColor)
}
var i = 0
for (vertical in 0 until heightSectionCount) {
for (horizontal in 0 until widthSectionCount) {
val swap = descramblingArray[i]
val baseHeight = swap.floorDiv(widthSectionCount)
val baseWidth = swap - baseHeight * widthSectionCount
val dx = baseWidth * sectionWidth
val dy = baseHeight * sectionHeight
val sx = horizontal * sectionWidth
val sy = vertical * sectionHeight
val srcRect = Rect(sx, sy, sx + sectionWidth, sy + sectionHeight)
val dstRect = Rect(dx, dy, dx + sectionWidth, dy + sectionHeight)
canvas.drawBitmap(bitmap, srcRect, dstRect, null)
i += 1
}
}
if (isBackgroundBlack) {
val invertingPaint = Paint().apply {
colorFilter = ColorMatrixColorFilter(
ColorMatrix(
floatArrayOf(
-1.0f, 0.0f, 0.0f, 0.0f, 255.0f,
0.0f, -1.0f, 0.0f, 0.0f, 255.0f,
0.0f, 0.0f, -1.0f, 0.0f, 255.0f,
0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
),
),
)
}
canvas.drawBitmap(result, 0f, 0f, invertingPaint)
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 90, output)
return output.toByteArray()
}
// Converted from _0x144afb
// This should be called a little bit before the drawImage calls
private fun createDescramblingArray(indexer: Int, size: Int, key: Int, keyAddition: Int, iterations: Int = 2): List<Int> {
var indexerMut = indexer
val returnArray = mutableListOf<Int>()
for (i in 0 until size) {
returnArray.add(i)
}
for (i in 0 until size) {
for (o in 0 until iterations) {
indexerMut = (indexerMut * key + keyAddition) % size
val tmp = returnArray[indexerMut]
returnArray[indexerMut] = returnArray[i]
returnArray[i] = tmp
}
}
return returnArray
}
private fun unfuckJs(jsf: String): String {
// String: ([]+[])
// fontcolor: (![]+[])[+[]]+({}+[])[+!![]]+([][[]]+[])[+!![]]+(!![]+[])[+[]]+({}+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(![]+[])[!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]
// "undefined": []+[][[]]
// Quick hack so QuickJS doesn't complain about function being called with no args
val input = jsf.replace(
"([]+[])[(![]+[])[+[]]+({}+[])[+!![]]+([][[]]+[])[+!![]]+(!![]+[])[+[]]+({}+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(![]+[])[!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]()",
"([]+[])[(![]+[])[+[]]+({}+[])[+!![]]+([][[]]+[])[+!![]]+(!![]+[])[+[]]+({}+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+(![]+[])[!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]]([]+[][[]])",
)
return QuickJs.create().use {
it.execute(jsfBoilerplate)
it.evaluate("get_img_data(${input.removePrefix("[]").removeSuffix("()")}[0])").toString()
}
}
private val jsfBoilerplate: ByteArray by lazy {
QuickJs.create().use {
it.compile(
"""
var _0x56dfa1=_0x217c;(function(_0x458c1d,_0x5a2370){var _0x4d7856=_0x217c,_0x1fa20f=_0x458c1d();while(!![]){try{var _0x34da05=-parseInt(_0x4d7856(0xac))/0x1+-parseInt(_0x4d7856(0xbc))/0x2*(-parseInt(_0x4d7856(0xb3))/0x3)+-parseInt(_0x4d7856(0xb8))/0x4+-parseInt(_0x4d7856(0xbb))/0x5*(parseInt(_0x4d7856(0xbd))/0x6)+parseInt(_0x4d7856(0xba))/0x7*(-parseInt(_0x4d7856(0xae))/0x8)+-parseInt(_0x4d7856(0xb0))/0x9+-parseInt(_0x4d7856(0xb9))/0xa*(-parseInt(_0x4d7856(0xaf))/0xb);if(_0x34da05===_0x5a2370)break;else _0x1fa20f['push'](_0x1fa20f['shift']());}catch(_0x7dd169){_0x1fa20f['push'](_0x1fa20f['shift']());}}}(_0x3c3e,0x4ade6));class Location{constructor(_0x2d256a){var _0x3e4741=_0x217c;this[_0x3e4741(0xaa)]=_0x2d256a;}[_0x56dfa1(0xb7)](){var _0x6040fb=_0x56dfa1;return this[_0x6040fb(0xaa)];}}function _0x3c3e(){var _0x27ebfd=['mtu0nJCYmLfWu2Hpvq','C3rYAw5NAwz5','z2v0x2LTz19KyxrH','mJfyB3f3sxq','Bg9JyxrPB24','Ahr0Chm6lY8','sLnptG','Dg9tDhjPBMC','nta3mZq0rhbJv1jR','mtyYotaWnZb4rxnwAeO','mtK2nJa5qMHUBMTW','mJqYndaWnwjTsu5nAq','ndGWmtjpAgv4sKu','nKrSy1Pcza','AhjLzG','zxzHBa','mte3mdy1ywTgEuLY','z2v0x2LTzW','mty4vLP5D05q','mtfIBgDhyue'];_0x3c3e=function(){return _0x27ebfd;};return _0x3c3e();}function _0x217c(_0x3aa1f3,_0x1793f5){var _0x3c3edb=_0x3c3e();return _0x217c=function(_0x217ce0,_0x3d5a46){_0x217ce0=_0x217ce0-0xaa;var _0x14b4f0=_0x3c3edb[_0x217ce0];if(_0x217c['DsHVWi']===undefined){var _0x59277=function(_0x2d256a){var _0x54ceb8='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x41845c='',_0x15e7f4='';for(var _0x269688=0x0,_0x3cc079,_0x25d416,_0xa002fc=0x0;_0x25d416=_0x2d256a['charAt'](_0xa002fc++);~_0x25d416&&(_0x3cc079=_0x269688%0x4?_0x3cc079*0x40+_0x25d416:_0x25d416,_0x269688++%0x4)?_0x41845c+=String['fromCharCode'](0xff&_0x3cc079>>(-0x2*_0x269688&0x6)):0x0){_0x25d416=_0x54ceb8['indexOf'](_0x25d416);}for(var _0x1d6105=0x0,_0x225416=_0x41845c['length'];_0x1d6105<_0x225416;_0x1d6105++){_0x15e7f4+='%'+('00'+_0x41845c['charCodeAt'](_0x1d6105)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x15e7f4);};_0x217c['fyYiDX']=_0x59277,_0x3aa1f3=arguments,_0x217c['DsHVWi']=!![];}var _0x19df2c=_0x3c3edb[0x0],_0x478efb=_0x217ce0+_0x19df2c,_0x5309ed=_0x3aa1f3[_0x478efb];return!_0x5309ed?(_0x14b4f0=_0x217c['fyYiDX'](_0x14b4f0),_0x3aa1f3[_0x478efb]=_0x14b4f0):_0x14b4f0=_0x5309ed,_0x14b4f0;},_0x217c(_0x3aa1f3,_0x1793f5);}this[_0x56dfa1(0xb4)]=new Location(_0x56dfa1(0xb5)),this[_0x56dfa1(0xad)]=(..._0x54ceb8)=>[..._0x54ceb8],this[_0x56dfa1(0xb2)]=_0x41845c=>this[_0x56dfa1(0xb6)][_0x56dfa1(0xb1)](this[_0x56dfa1(0xab)](_0x41845c));
""".trimIndent(),
"?",
)
}
}
companion object {
const val DESCRAMBLE = "descramble"
}
}