|
|
|
|
@ -14,11 +14,40 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
|
|
|
|
import org.koitharu.kotatsu.parsers.bitmap.Rect
|
|
|
|
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
|
|
|
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.*
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.ContentRating
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaState
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
|
|
|
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.attrAsAbsoluteUrl
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.generateUid
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.getCookies
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.mapChapters
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.ownTextOrNull
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.parseFailed
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.parseHtml
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.parseJson
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.parseSafe
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.splitByWhitespace
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
import java.util.*
|
|
|
|
|
import java.util.Base64
|
|
|
|
|
import java.util.EnumSet
|
|
|
|
|
import java.util.Locale
|
|
|
|
|
import kotlin.math.min
|
|
|
|
|
|
|
|
|
|
private const val PIECE_SIZE = 200
|
|
|
|
|
@ -41,6 +70,10 @@ internal abstract class MangaFireParser(
|
|
|
|
|
SortOrder.RELEVANCE,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
override fun getRequestHeaders() = super.getRequestHeaders().newBuilder()
|
|
|
|
|
.add("Referer", "https://$domain/")
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
|
|
|
|
super.onCreateConfig(keys)
|
|
|
|
|
keys.add(userAgentKey)
|
|
|
|
|
@ -95,25 +128,30 @@ internal abstract class MangaFireParser(
|
|
|
|
|
addQueryParameter("page", page.toString())
|
|
|
|
|
addQueryParameter("language[]", siteLang)
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
!filter.query.isNullOrEmpty() -> {
|
|
|
|
|
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
|
|
|
|
|
part.urlEncoded()
|
|
|
|
|
}
|
|
|
|
|
addEncodedQueryParameter("keyword", encodedQuery)
|
|
|
|
|
addQueryParameter(
|
|
|
|
|
name = "sort",
|
|
|
|
|
value = when (order) {
|
|
|
|
|
SortOrder.UPDATED -> "recently_updated"
|
|
|
|
|
SortOrder.POPULARITY -> "most_viewed"
|
|
|
|
|
SortOrder.RATING -> "scores"
|
|
|
|
|
SortOrder.NEWEST -> "release_date"
|
|
|
|
|
SortOrder.ALPHABETICAL -> "title_az"
|
|
|
|
|
SortOrder.RELEVANCE -> "most_relevance"
|
|
|
|
|
else -> ""
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
when {
|
|
|
|
|
!filter.query.isNullOrEmpty() -> {
|
|
|
|
|
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
|
|
|
|
|
part.urlEncoded()
|
|
|
|
|
}
|
|
|
|
|
addEncodedQueryParameter("keyword", encodedQuery)
|
|
|
|
|
|
|
|
|
|
// Generate VRF for search query
|
|
|
|
|
val searchVrf = VrfGenerator.generate(filter.query.trim())
|
|
|
|
|
addQueryParameter("vrf", searchVrf)
|
|
|
|
|
|
|
|
|
|
addQueryParameter(
|
|
|
|
|
name = "sort",
|
|
|
|
|
value = when (order) {
|
|
|
|
|
SortOrder.UPDATED -> "recently_updated"
|
|
|
|
|
SortOrder.POPULARITY -> "most_viewed"
|
|
|
|
|
SortOrder.RATING -> "scores"
|
|
|
|
|
SortOrder.NEWEST -> "release_date"
|
|
|
|
|
SortOrder.ALPHABETICAL -> "title_az"
|
|
|
|
|
SortOrder.RELEVANCE -> "most_relevance"
|
|
|
|
|
else -> ""
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
filter.tagsExclude.forEach { tag ->
|
|
|
|
|
@ -260,14 +298,18 @@ internal abstract class MangaFireParser(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> {
|
|
|
|
|
val chapterElements = webClient
|
|
|
|
|
.httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}")
|
|
|
|
|
.parseJson()
|
|
|
|
|
.getJSONObject("result")
|
|
|
|
|
.getString("html")
|
|
|
|
|
.let(Jsoup::parseBodyFragment)
|
|
|
|
|
.select("ul li a")
|
|
|
|
|
private suspend fun getChaptersBranch(mangaId: String, branch: ChapterBranch): List<MangaChapter> {
|
|
|
|
|
val readVrfInput = "$mangaId@${branch.type}@${branch.langCode}"
|
|
|
|
|
val readVrf = VrfGenerator.generate(readVrfInput)
|
|
|
|
|
|
|
|
|
|
val response = webClient
|
|
|
|
|
.httpGet("https://$domain/ajax/read/$mangaId/${branch.type}/${branch.langCode}?vrf=$readVrf")
|
|
|
|
|
|
|
|
|
|
val chapterElements = response.parseJson()
|
|
|
|
|
.getJSONObject("result")
|
|
|
|
|
.getString("html")
|
|
|
|
|
.let(Jsoup::parseBodyFragment)
|
|
|
|
|
.select("ul li a")
|
|
|
|
|
|
|
|
|
|
if (branch.type == "chapter") {
|
|
|
|
|
val doc = webClient
|
|
|
|
|
@ -276,31 +318,32 @@ internal abstract class MangaFireParser(
|
|
|
|
|
.getString("result")
|
|
|
|
|
.let(Jsoup::parseBodyFragment)
|
|
|
|
|
|
|
|
|
|
doc.select("ul li a").withIndex().forEach { (i, it) ->
|
|
|
|
|
val date = it.select("span")[1].ownText()
|
|
|
|
|
chapterElements[i].attr("upload-date", date)
|
|
|
|
|
chapterElements[i].attr("other-title", it.attr("title"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return chapterElements.mapChapters(reversed = true) { _, it ->
|
|
|
|
|
MangaChapter(
|
|
|
|
|
id = generateUid(it.attr("href")),
|
|
|
|
|
title = it.attr("title").ifBlank {
|
|
|
|
|
"${branch.type.toTitleCase()} ${it.attr("data-number")}"
|
|
|
|
|
},
|
|
|
|
|
number = it.attr("data-number").toFloat(),
|
|
|
|
|
volume = it.attr("other-title").let {
|
|
|
|
|
volumeNumRegex.find(it)?.groupValues?.getOrNull(2)?.toInt() ?: 0
|
|
|
|
|
},
|
|
|
|
|
url = "${branch.type}/${it.attr("data-id")}",
|
|
|
|
|
scanlator = null,
|
|
|
|
|
uploadDate = dateFormat.parseSafe(it.attr("upload-date")),
|
|
|
|
|
branch = "${branch.langTitle} ${branch.type.toTitleCase()}",
|
|
|
|
|
source = source,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
doc.select("ul li a").withIndex().forEach { (i, it) ->
|
|
|
|
|
val date = it.select("span").getOrNull(1)?.ownText() ?: ""
|
|
|
|
|
chapterElements[i].attr("upload-date", date)
|
|
|
|
|
chapterElements[i].attr("other-title", it.attr("title"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return chapterElements.mapChapters(reversed = true) { _, it ->
|
|
|
|
|
val chapterId = it.attr("data-id")
|
|
|
|
|
MangaChapter(
|
|
|
|
|
id = generateUid(it.attr("href")),
|
|
|
|
|
title = it.attr("title").ifBlank {
|
|
|
|
|
"${branch.type.toTitleCase()} ${it.attr("data-number")}"
|
|
|
|
|
},
|
|
|
|
|
number = it.attr("data-number").toFloatOrNull() ?: -1f,
|
|
|
|
|
volume = it.attr("other-title").let { title ->
|
|
|
|
|
volumeNumRegex.find(title)?.groupValues?.getOrNull(2)?.toInt() ?: 0
|
|
|
|
|
},
|
|
|
|
|
url = "$mangaId/${branch.type}/${branch.langCode}/$chapterId",
|
|
|
|
|
scanlator = null,
|
|
|
|
|
uploadDate = dateFormat.parseSafe(it.attr("upload-date")),
|
|
|
|
|
branch = "${branch.langTitle} ${branch.type.toTitleCase()}",
|
|
|
|
|
source = source,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)
|
|
|
|
|
private val volumeNumRegex = Regex("""vol(ume)?\s*(\d+)""", RegexOption.IGNORE_CASE)
|
|
|
|
|
@ -387,12 +430,15 @@ internal abstract class MangaFireParser(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
|
|
|
|
val images = webClient
|
|
|
|
|
.httpGet("https://$domain/ajax/read/${chapter.url}")
|
|
|
|
|
.parseJson()
|
|
|
|
|
.getJSONObject("result")
|
|
|
|
|
.getJSONArray("images")
|
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
|
|
|
|
val chapterId = chapter.url.substringAfterLast('/')
|
|
|
|
|
val vrf = VrfGenerator.generate("chapter@$chapterId")
|
|
|
|
|
|
|
|
|
|
val images = webClient
|
|
|
|
|
.httpGet("https://$domain/ajax/read/chapter/$chapterId?vrf=$vrf")
|
|
|
|
|
.parseJson()
|
|
|
|
|
.getJSONObject("result")
|
|
|
|
|
.getJSONArray("images")
|
|
|
|
|
|
|
|
|
|
val pages = ArrayList<MangaPage>(images.length())
|
|
|
|
|
|
|
|
|
|
@ -491,3 +537,186 @@ internal abstract class MangaFireParser(
|
|
|
|
|
class PortugueseBR(context: MangaLoaderContext) :
|
|
|
|
|
MangaFireParser(context, MangaParserSource.MANGAFIRE_PTBR, "pt-br")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private object VrfGenerator {
|
|
|
|
|
private fun atob(data: String): ByteArray = Base64.getDecoder().decode(data)
|
|
|
|
|
|
|
|
|
|
private fun btoa(data: ByteArray): String = Base64.getEncoder().encodeToString(data)
|
|
|
|
|
|
|
|
|
|
private fun rc4(key: ByteArray, input: ByteArray): ByteArray {
|
|
|
|
|
val s = IntArray(256) { it }
|
|
|
|
|
var j = 0
|
|
|
|
|
|
|
|
|
|
// KSA
|
|
|
|
|
for (i in 0..255) {
|
|
|
|
|
j = (j + s[i] + key[i % key.size].toInt().and(0xFF)) and 0xFF
|
|
|
|
|
val temp = s[i]
|
|
|
|
|
s[i] = s[j]
|
|
|
|
|
s[j] = temp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PRGA
|
|
|
|
|
val output = ByteArray(input.size)
|
|
|
|
|
var i = 0
|
|
|
|
|
j = 0
|
|
|
|
|
for (y in input.indices) {
|
|
|
|
|
i = (i + 1) and 0xFF
|
|
|
|
|
j = (j + s[i]) and 0xFF
|
|
|
|
|
val temp = s[i]
|
|
|
|
|
s[i] = s[j]
|
|
|
|
|
s[j] = temp
|
|
|
|
|
val k = s[(s[i] + s[j]) and 0xFF]
|
|
|
|
|
output[y] = (input[y].toInt() xor k).toByte()
|
|
|
|
|
}
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun transform(
|
|
|
|
|
input: ByteArray,
|
|
|
|
|
initSeedBytes: ByteArray,
|
|
|
|
|
prefixKeyBytes: ByteArray,
|
|
|
|
|
prefixLen: Int,
|
|
|
|
|
schedule: List<(Int) -> Int>,
|
|
|
|
|
): ByteArray {
|
|
|
|
|
val out = mutableListOf<Byte>()
|
|
|
|
|
for (i in input.indices) {
|
|
|
|
|
if (i < prefixLen) {
|
|
|
|
|
out.add(prefixKeyBytes[i])
|
|
|
|
|
}
|
|
|
|
|
val transformed = schedule[i % 10](
|
|
|
|
|
(input[i].toInt() xor initSeedBytes[i % 32].toInt()) and 0xFF,
|
|
|
|
|
) and 0xFF
|
|
|
|
|
out.add(transformed.toByte())
|
|
|
|
|
}
|
|
|
|
|
return out.toByteArray()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val scheduleC = listOf<(Int) -> Int>(
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 241) and 0xFF },
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 8) and 0xFF },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val scheduleY = listOf<(Int) -> Int>(
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
{ c -> (c xor 163) and 0xFF },
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 83) and 0xFF },
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val scheduleB = listOf<(Int) -> Int>(
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
{ c -> (c - 48 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 8) and 0xFF },
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c xor 163) and 0xFF },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val scheduleJ = listOf<(Int) -> Int>(
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> (c xor 83) and 0xFF },
|
|
|
|
|
{ c -> (c - 19 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c + 223) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 83) and 0xFF },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val scheduleE = listOf<(Int) -> Int>(
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c xor 83) and 0xFF },
|
|
|
|
|
{ c -> (c xor 163) and 0xFF },
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c - 170 + 256) and 0xFF },
|
|
|
|
|
{ c -> (c xor 8) and 0xFF },
|
|
|
|
|
{ c -> (c xor 241) and 0xFF },
|
|
|
|
|
{ c -> (c + 82) and 0xFF },
|
|
|
|
|
{ c -> (c + 176) and 0xFF },
|
|
|
|
|
{ c -> ((c shl 4) or (c ushr 4)) and 0xFF },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val rc4Keys = mapOf(
|
|
|
|
|
"l" to "u8cBwTi1CM4XE3BkwG5Ble3AxWgnhKiXD9Cr279yNW0=",
|
|
|
|
|
"g" to "t00NOJ/Fl3wZtez1xU6/YvcWDoXzjrDHJLL2r/IWgcY=",
|
|
|
|
|
"B" to "S7I+968ZY4Fo3sLVNH/ExCNq7gjuOHjSRgSqh6SsPJc=",
|
|
|
|
|
"m" to "7D4Q8i8dApRj6UWxXbIBEa1UqvjI+8W0UvPH9talJK8=",
|
|
|
|
|
"F" to "0JsmfWZA1kwZeWLk5gfV5g41lwLL72wHbam5ZPfnOVE=",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val seeds32 = mapOf(
|
|
|
|
|
"A" to "pGjzSCtS4izckNAOhrY5unJnO2E1VbrU+tXRYG24vTo=",
|
|
|
|
|
"V" to "dFcKX9Qpu7mt/AD6mb1QF4w+KqHTKmdiqp7penubAKI=",
|
|
|
|
|
"N" to "owp1QIY/kBiRWrRn9TLN2CdZsLeejzHhfJwdiQMjg3w=",
|
|
|
|
|
"P" to "H1XbRvXOvZAhyyPaO68vgIUgdAHn68Y6mrwkpIpEue8=",
|
|
|
|
|
"k" to "2Nmobf/mpQ7+Dxq1/olPSDj3xV8PZkPbKaucJvVckL0=",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val prefixKeys = mapOf(
|
|
|
|
|
"O" to "Rowe+rg/0g==",
|
|
|
|
|
"v" to "8cULcnOMJVY8AA==",
|
|
|
|
|
"L" to "n2+Og2Gth8Hh",
|
|
|
|
|
"p" to "aRpvzH+yoA==",
|
|
|
|
|
"W" to "ZB4oBi0=",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fun generate(input: String): String {
|
|
|
|
|
var bytes = input.toByteArray()
|
|
|
|
|
// RC4 1
|
|
|
|
|
bytes = rc4(atob(rc4Keys["l"]!!), bytes)
|
|
|
|
|
|
|
|
|
|
// Step C1
|
|
|
|
|
bytes = transform(bytes, atob(seeds32["A"]!!), atob(prefixKeys["O"]!!), 7, scheduleC)
|
|
|
|
|
|
|
|
|
|
// RC4 2
|
|
|
|
|
bytes = rc4(atob(rc4Keys["g"]!!), bytes)
|
|
|
|
|
|
|
|
|
|
// Step Y
|
|
|
|
|
bytes = transform(bytes, atob(seeds32["V"]!!), atob(prefixKeys["v"]!!), 10, scheduleY)
|
|
|
|
|
|
|
|
|
|
// RC4 3
|
|
|
|
|
bytes = rc4(atob(rc4Keys["B"]!!), bytes)
|
|
|
|
|
|
|
|
|
|
// Step B
|
|
|
|
|
bytes = transform(bytes, atob(seeds32["N"]!!), atob(prefixKeys["L"]!!), 9, scheduleB)
|
|
|
|
|
|
|
|
|
|
// RC4 4
|
|
|
|
|
bytes = rc4(atob(rc4Keys["m"]!!), bytes)
|
|
|
|
|
|
|
|
|
|
// Step J
|
|
|
|
|
bytes = transform(bytes, atob(seeds32["P"]!!), atob(prefixKeys["p"]!!), 7, scheduleJ)
|
|
|
|
|
|
|
|
|
|
// RC4 5
|
|
|
|
|
bytes = rc4(atob(rc4Keys["F"]!!), bytes)
|
|
|
|
|
|
|
|
|
|
// Step E
|
|
|
|
|
bytes = transform(bytes, atob(seeds32["k"]!!), atob(prefixKeys["W"]!!), 5, scheduleE)
|
|
|
|
|
|
|
|
|
|
// Base64URL encode
|
|
|
|
|
return btoa(bytes)
|
|
|
|
|
.replace("+", "-")
|
|
|
|
|
.replace("/", "_")
|
|
|
|
|
.replace("=", "")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|