diff --git a/src/zh/dmzj/API.md b/src/zh/dmzj/API.md deleted file mode 100644 index 9e844a338..000000000 --- a/src/zh/dmzj/API.md +++ /dev/null @@ -1,140 +0,0 @@ -# API v4 - -## Manga details - -Mostly taken from [here](https://github.com/xiaoyaocz/dmzj_flutter/blob/aac6ba3/lib/protobuf/comic/detail_response.proto). - -```protobuf -syntax = "proto3"; - -package dmzj.comic; - - -message ComicDetailResponse { - int32 Errno = 1; - string Errmsg = 2; - ComicDetailInfoResponse Data= 3; -} - -message ComicDetailInfoResponse { - int32 Id = 1; - string Title = 2; - int32 Direction=3; - int32 Islong=4; - int32 IsDmzj=5; - string Cover=6; - string Description=7; - int64 LastUpdatetime=8; - string LastUpdateChapterName=9; - int32 Copyright=10; - string FirstLetter=11; - string ComicPy=12; - int32 Hidden=13; - int32 HotNum=14; - int32 HitNum=15; - int32 Uid=16; - int32 IsLock=17; - int32 LastUpdateChapterId=18; - repeated ComicDetailTypeItemResponse Types=19; - repeated ComicDetailTypeItemResponse Status=20; - repeated ComicDetailTypeItemResponse Authors=21; - int32 SubscribeNum=22; - repeated ComicDetailChapterResponse Chapters=23; - int32 IsNeedLogin=24; - //object UrlLinks=25; { string name = 1; repeated object links = 2; } - // link { int32 = 1; string name = 2; string uriOrApk = 3; string icon = 4; string packageName = 5; string apk = 6; int32 = 7; } - int32 IsHideChapter=26; - //repeated object DhUrlLinks=27; { string name = 1; } - -} - -message ComicDetailTypeItemResponse { - int32 TagId = 1; - string TagName = 2; -} - -message ComicDetailChapterResponse { - string Title = 1; - repeated ComicDetailChapterInfoResponse Data=2; -} -message ComicDetailChapterInfoResponse { - int32 ChapterId = 1; - string ChapterTitle = 2; - int64 Updatetime=3; - int32 Filesize=4; - int32 ChapterOrder=5; -} -``` - -## Ranking - -Taken from [here](https://github.com/xiaoyaocz/dmzj_flutter/blob/e7f1b1e/lib/protobuf/comic/rank_list_response.proto). - -```protobuf -syntax = "proto3"; - -package dmzj.comic; - - -message ComicRankListResponse { - int32 Errno = 1; - string Errmsg = 2; - repeated ComicRankListItemResponse Data= 3; -} - -message ComicRankListItemResponse { - int32 ComicId = 1; - string Title = 2; - string Authors=3; - string Status=4; - string Cover=5; - string Types=6; - int64 LastUpdatetime=7; - string LastUpdateChapterName=8; - string ComicPy=9; - int32 Num=10; - int32 TagId=11; - string ChapterName=12; - int32 ChapterId=13; -} -``` - -## Chapter images - -```kotlin -@Serializable -class ResponseDto( - @ProtoNumber(1) val code: Int?, - @ProtoNumber(2) val message: String?, - @ProtoNumber(3) val data: T?, -) - -@Serializable -class ChapterImagesDto( - @ProtoNumber(1) val id: Int, - @ProtoNumber(2) val mangaId: Int, - @ProtoNumber(3) val name: String, - @ProtoNumber(4) val order: Int, - @ProtoNumber(5) val direction: Int, - // initial letter is sometimes different from that in original URLs, see manga ID 56649 - @ProtoNumber(6) val lowResImages: List, - // page count of low-res images - @ProtoNumber(7) val pageCount: Int?, - @ProtoNumber(8) val images: List, - @ProtoNumber(9) val commentCount: Int, -) -``` - -# Unused legacy API - -## Chapter images - -```kotlin -val webviewPageListApiUrl = "https://m.dmzj.com/chapinfo" -GET("$webviewPageListApiUrl/${chapter.url}.html") -``` - -```kotlin -val oldPageListApiUrl = "http://api.m.dmzj.com" // this domain has an expired certificate -GET("$oldPageListApiUrl/comic/chapter/${chapter.url}.html") -``` diff --git a/src/zh/dmzj/AndroidManifest.xml b/src/zh/dmzj/AndroidManifest.xml deleted file mode 100644 index dfea2cf99..000000000 --- a/src/zh/dmzj/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/zh/dmzj/build.gradle b/src/zh/dmzj/build.gradle deleted file mode 100644 index a10b02998..000000000 --- a/src/zh/dmzj/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -ext { - extName = 'DMZJ' - extClass = '.Dmzj' - extVersionCode = 46 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 48efce517..000000000 Binary files a/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 46c20b2ec..000000000 Binary files a/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 49b015793..000000000 Binary files a/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1edcb656e..000000000 Binary files a/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5e053fbeb..000000000 Binary files a/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiSearch.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiSearch.kt deleted file mode 100644 index 7fa804bf0..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiSearch.kt +++ /dev/null @@ -1,64 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.Serializable -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Response - -object ApiSearch { - - fun searchUrlV1(page: Int, query: String) = - "https://manhua.idmzj.com/api/v1/comic2/search".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("size", "30") - .addQueryParameter("keyword", query) - .toString() - - fun parsePageV1(response: Response): MangasPage { - if (!response.isSuccessful) { - response.close() - return MangasPage(emptyList(), false) - } - val result = response.parseAs>() - if (result.errmsg.isNotBlank()) { - throw Exception(result.errmsg) - } else { - val url = response.request.url - val page = url.queryParameter("page")?.toInt() - val size = url.queryParameter("size")?.toInt() - return if (result.data != null) { - MangasPage(result.data.comicList.map { it.toSManga() }, page!! * size!! < result.data.totalNum) - } else { - MangasPage(emptyList(), false) - } - } - } - - @Serializable - class MangaDtoV1( - private val id: Int, - private val name: String, - private val authors: String, - private val cover: String, - ) { - fun toSManga() = SManga.create().apply { - url = getMangaUrl(id.toString()) - title = name - author = authors - thumbnail_url = cover - } - } - - @Serializable - class SearchResultDto( - val comicList: List, - val totalNum: Int, - ) - - @Serializable - class ResponseDto( - val errmsg: String = "", - val data: T, - ) -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt deleted file mode 100644 index d94240071..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV3.kt +++ /dev/null @@ -1,135 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.content.SharedPreferences -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonPrimitive -import okhttp3.Response -import org.jsoup.parser.Parser - -object ApiV3 { - lateinit var preferences: SharedPreferences - private val v3apiUrl: String - get() = if (preferences.isOlderV3API == true) { - "https://v3api.idmzj.com" - } else { - "https://nnv3api.dmzj.com" - } - - private const val apiUrl = "https://api.dmzj.com" - - fun popularMangaUrl(page: Int) = "$v3apiUrl/classify/0/0/${page - 1}.json" - - fun latestUpdatesUrl(page: Int) = "$v3apiUrl/classify/0/1/${page - 1}.json" - - fun pageUrl(page: Int, filters: FilterList) = "$v3apiUrl/classify/${parseFilters(filters)}/${page - 1}.json" - - fun parsePage(response: Response): MangasPage { - val data: List = response.parseAs() - return MangasPage(data.map { it.toSManga() }, data.isNotEmpty()) - } - - fun mangaInfoUrlV1(id: String) = "$apiUrl/dynamic/comicinfo/$id.json" - - private fun parseMangaInfoV1(response: Response): ResponseDto = try { - response.parseAs() - } catch (_: Throwable) { - throw Exception("获取漫画信息失败") - } - - fun parseMangaDetailsV1(response: Response): SManga { - return parseMangaInfoV1(response).data.info.toSManga() - } - - fun parseChapterListV1(response: Response): List { - val data = parseMangaInfoV1(response).data - return buildList(data.list.size + data.alone.size) { - data.list.mapTo(this) { - it.toSChapter() - } - data.alone.mapTo(this) { - it.toSChapter().apply { name = "单行本: $name" } - } - } - } - - fun chapterImagesUrlV1(path: String) = "https://m.idmzj.com/chapinfo/$path.html" - - fun parseChapterImagesV1(response: Response) = - response.parseAs().toPageList() - - fun chapterCommentsUrl(path: String) = "$v3apiUrl/viewPoint/0/$path.json" - - fun parseChapterComments(response: Response, count: Int): List { - val result: List = response.parseAs() - if (result.isEmpty()) return listOf("没有吐槽") - val aggregated = result.groupBy({ it.content }, { it.num }).map { (content, likes) -> - ChapterCommentDto(Parser.unescapeEntities(content, false), likes.sum()) - } as ArrayList - aggregated.sort() - return aggregated.take(count).map { it.toString() } - } - - @Serializable - class MangaDto( - private val id: JsonPrimitive, // can be int or string - private val title: String, - private val authors: String?, - private val status: String, - private val cover: String, - private val types: String, - private val description: String? = null, - ) { - fun toSManga() = SManga.create().apply { - url = getMangaUrl(id.content) - title = this@MangaDto.title - author = authors?.formatList() - genre = types.formatList() - status = parseStatus(this@MangaDto.status) - thumbnail_url = cover - - val desc = this@MangaDto.description ?: return@apply - description = "$desc\n\n漫画 ID (2): ${id.content}" // hidden - initialized = true - } - } - - @Serializable - class ChapterDto( - private val id: String, - private val comic_id: String, - private val chapter_name: String, - private val updatetime: String, - ) { - fun toSChapter() = SChapter.create().apply { - url = "$comic_id/$id" - name = chapter_name.formatChapterName() - date_upload = updatetime.toLong() * 1000 - } - } - - @Serializable - class ChapterImagesDto( - private val page_url: List, - ) { - fun toPageList() = parsePageList(page_url) - } - - @Serializable - class ChapterCommentDto( - val content: String, - val num: Int, - ) : Comparable { - override fun toString() = if (num > 0) "$content [+$num]" else content - override fun compareTo(other: ChapterCommentDto) = other.num.compareTo(num) // descending - } - - @Serializable - class DataDto(val info: MangaDto, val list: List, val alone: List) - - @Serializable - class ResponseDto(val data: DataDto) -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt deleted file mode 100644 index 15eb39165..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ApiV4.kt +++ /dev/null @@ -1,174 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import eu.kanade.tachiyomi.extension.zh.dmzj.utils.RSA -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 kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.protobuf.ProtoBuf -import kotlinx.serialization.protobuf.ProtoNumber -import kotlinx.serialization.serializer -import okhttp3.Response -import kotlin.reflect.KType -import kotlin.reflect.typeOf - -object ApiV4 { - - private const val v4apiUrl = "https://nnv4api.dmzj.com" - - fun mangaInfoUrl(id: String) = "$v4apiUrl/comic/detail/$id?uid=2665531" - - fun parseMangaInfo(response: Response): MangaDto? { - val result: ResponseDto = response.decrypt() - return result.data - } - - // path = "mangaId/chapterId" - fun chapterImagesUrl(path: String) = "$v4apiUrl/comic/chapter/$path" - - fun parseChapterImages(response: Response, isLowRes: Boolean): ArrayList { - val result: ResponseDto = response.decrypt() - return result.data!!.toPageList(isLowRes) - } - - fun rankingUrl(page: Int, filters: RankingGroup) = - "$v4apiUrl/comic/rank/list?${filters.parse()}&uid=2665531&page=$page" - - fun parseRanking(response: Response): MangasPage { - val result: ResponseDto> = response.decrypt() - val data = result.data ?: return MangasPage(emptyList(), false) - return MangasPage(data.map { it.toSManga() }, data.isNotEmpty()) - } - - private inline fun Response.decrypt(): T = decrypt(typeOf()) - - @Suppress("UNCHECKED_CAST") - private fun Response.decrypt(type: KType): T { - val bytes = RSA.decrypt(body.string(), cipher) - val deserializer = serializer(type) as KSerializer - return ProtoBuf.decodeFromByteArray(deserializer, bytes) - } - - @Serializable - class MangaDto( - @ProtoNumber(1) private val id: Int, - @ProtoNumber(2) private val title: String, - @ProtoNumber(6) private val cover: String, - @ProtoNumber(7) private val description: String, - @ProtoNumber(19) private val genres: List, - @ProtoNumber(20) private val status: List, - @ProtoNumber(21) private val authors: List, - @ProtoNumber(23) private val chapterGroups: List, - ) { - val isLicensed get() = chapterGroups.isEmpty() - - fun toSManga() = SManga.create().apply { - url = getMangaUrl(id.toString()) - title = this@MangaDto.title - author = authors.joinToString { it.name } - description = if (isLicensed) { - "${this@MangaDto.description}\n\n漫画 ID (1): $id" - } else { - this@MangaDto.description - } - genre = genres.joinToString { it.name } - status = parseStatus(this@MangaDto.status[0].name) - thumbnail_url = cover - initialized = true - } - - fun parseChapterList(): List { - val mangaId = id.toString() - val size = chapterGroups.sumOf { it.size } - return chapterGroups.flatMapTo(ArrayList(size)) { - it.toSChapterList(mangaId) - } - } - } - - @Serializable - class TagDto(@ProtoNumber(2) val name: String) - - @Serializable - class ChapterGroupDto( - @ProtoNumber(1) private val name: String, - @ProtoNumber(2) private val chapters: List, - ) { - fun toSChapterList(mangaId: String): List { - val groupName = name - val isDefaultGroup = groupName == "连载" - return chapters.map { - it.toSChapterInternal().apply { - url = "$mangaId/$url" - if (!isDefaultGroup) name = "$groupName: $name" - } - } - } - - val size get() = chapters.size - } - - @Serializable - class ChapterDto( - @ProtoNumber(1) private val id: Int, - @ProtoNumber(2) private val name: String, - @ProtoNumber(3) private val updateTime: Long, - ) { - fun toSChapterInternal() = SChapter.create().apply { - url = id.toString() - name = this@ChapterDto.name.formatChapterName() - date_upload = updateTime * 1000 - } - } - - @Serializable - class ChapterImagesDto( - @ProtoNumber(6) private val lowResImages: List, - @ProtoNumber(8) private val images: List, - ) { - fun toPageList(isLowRes: Boolean) = - // page count can be messy, see manga ID 55847 chapters 107-109 - if (images.size == lowResImages.size) { - parsePageList(images, lowResImages) - } else if (isLowRes) { - parsePageList(lowResImages, lowResImages) - } else { - parsePageList(images) - } - } - - // same as ApiV3.MangaDto - @Serializable - class RankingItemDto( - @ProtoNumber(1) private val id: Int?, - @ProtoNumber(2) private val title: String, - @ProtoNumber(3) private val authors: String, - @ProtoNumber(4) private val status: String, - @ProtoNumber(5) private val cover: String, - @ProtoNumber(6) private val genres: String, - @ProtoNumber(9) private val slug: String?, - ) { - fun toSManga() = SManga.create().apply { - url = when { - id != null -> getMangaUrl(id.toString()) - slug != null -> PREFIX_ID_SEARCH + slug - else -> throw Exception("无法解析") - } - title = this@RankingItemDto.title - author = authors.formatList() - genre = genres.formatList() - status = parseStatus(this@RankingItemDto.status) - thumbnail_url = cover - } - } - - @Serializable - class ResponseDto( - @ProtoNumber(2) val message: String?, - @ProtoNumber(3) val data: T?, - ) - - private val cipher by lazy { RSA.getPrivateKey("MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK8nNR1lTnIfIes6oRWJNj3mB6OssDGx0uGMpgpbVCpf6+VwnuI2stmhZNoQcM417Iz7WqlPzbUmu9R4dEKmLGEEqOhOdVaeh9Xk2IPPjqIu5TbkLZRxkY3dJM1htbz57d/roesJLkZXqssfG5EJauNc+RcABTfLb4IiFjSMlTsnAgMBAAECgYEAiz/pi2hKOJKlvcTL4jpHJGjn8+lL3wZX+LeAHkXDoTjHa47g0knYYQteCbv+YwMeAGupBWiLy5RyyhXFoGNKbbnvftMYK56hH+iqxjtDLnjSDKWnhcB7089sNKaEM9Ilil6uxWMrMMBH9v2PLdYsqMBHqPutKu/SigeGPeiB7VECQQDizVlNv67go99QAIv2n/ga4e0wLizVuaNBXE88AdOnaZ0LOTeniVEqvPtgUk63zbjl0P/pzQzyjitwe6HoCAIpAkEAxbOtnCm1uKEp5HsNaXEJTwE7WQf7PrLD4+BpGtNKkgja6f6F4ld4QZ2TQ6qvsCizSGJrjOpNdjVGJ7bgYMcczwJBALvJWPLmDi7ToFfGTB0EsNHZVKE66kZ/8Stx+ezueke4S556XplqOflQBjbnj2PigwBN/0afT+QZUOBOjWzoDJkCQClzo+oDQMvGVs9GEajS/32mJ3hiWQZrWvEzgzYRqSf3XVcEe7PaXSd8z3y3lACeeACsShqQoc8wGlaHXIJOHTcCQQCZw5127ZGs8ZDTSrogrH73Kw/HvX55wGAeirKYcv28eauveCG7iyFR0PFB/P/EDZnyb+ifvyEFlucPUI0+Y87F") } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt deleted file mode 100644 index 4508db3fa..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/CommentsInterceptor.kt +++ /dev/null @@ -1,69 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.text.Layout -import android.text.StaticLayout -import android.text.TextPaint -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import java.io.ByteArrayOutputStream - -object CommentsInterceptor : Interceptor { - - class Tag - - private const val MAX_HEIGHT = 1920 - private const val WIDTH = 1080 - private const val UNIT = 32 - private const val UNIT_F = UNIT.toFloat() - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val response = chain.proceed(request) - if (request.tag(Tag::class) == null) return response - - val comments = ApiV3.parseChapterComments(response, MAX_HEIGHT / (UNIT * 2)) - - val paint = TextPaint().apply { - color = Color.BLACK - textSize = UNIT_F - isAntiAlias = true - } - - var height = UNIT - val layouts = comments.map { - @Suppress("DEPRECATION") - StaticLayout(it, paint, WIDTH - 2 * UNIT, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false) - }.takeWhile { - val lineHeight = it.height + UNIT - if (height + lineHeight <= MAX_HEIGHT) { - height += lineHeight - true - } else { - false - } - } - - val bitmap = Bitmap.createBitmap(WIDTH, height, Bitmap.Config.ARGB_8888) - bitmap.eraseColor(Color.WHITE) - val canvas = Canvas(bitmap) - - var y = UNIT - for (layout in layouts) { - canvas.save() - canvas.translate(UNIT_F, y.toFloat()) - layout.draw(canvas) - canvas.restore() - y += layout.height + UNIT - } - - val output = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 0, output) - val body = output.toByteArray().toResponseBody("image/png".toMediaType()) - return response.newBuilder().body(body).build() - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt deleted file mode 100644 index e067604ad..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Common.kt +++ /dev/null @@ -1,57 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Response -import uy.kohesive.injekt.injectLazy -import java.net.URLDecoder - -const val PREFIX_ID_SEARCH = "id:" - -val json: Json by injectLazy() - -inline fun Response.parseAs(): T { - return json.decodeFromString(body.string()) -} - -fun getMangaUrl(id: String) = "/comic/comic_$id.json?version=2.7.019" - -fun String.extractMangaId(): String { - val start = 13 // length of "/comic/comic_" - return substring(start, indexOf('.', start)) -} - -fun String.formatList() = replace("/", ", ") - -fun parseStatus(status: String): Int = when (status) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN -} - -private val chapterNameRegex = Regex("""(?:连载版?)?(\d[.\d]*)([话卷])?""") - -fun String.formatChapterName(): String { - val match = chapterNameRegex.matchEntire(this) ?: return this - val (number, optionalType) = match.destructured - val type = optionalType.ifEmpty { "话" } - return "第$number$type" -} - -fun parsePageList( - images: List, - lowResImages: List = List(images.size) { "" }, -): ArrayList { - val pageCount = images.size - val list = ArrayList(pageCount + 1) // for comments page - for (i in 0 until pageCount) { - list.add(Page(i, lowResImages[i], images[i])) - } - return list -} - -fun String.decodePath(): String = URLDecoder.decode(this, "UTF-8") - -const val COMMENTS_FLAG = "COMMENTS" diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt deleted file mode 100644 index 16b89e9dc..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt +++ /dev/null @@ -1,224 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.content.SharedPreferences -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.ConfigurableSource -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 keiyoushi.utils.getPreferences -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable - -/** - * Dmzj source - */ - -class Dmzj : ConfigurableSource, HttpSource() { - override val lang = "zh" - override val supportsLatest = true - override val name = "动漫之家" - override val baseUrl = "https://m.idmzj.com" - - private val preferences: SharedPreferences = getPreferences() - init { - ApiV3.preferences = preferences - } - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(ImageUrlInterceptor) - .addInterceptor(CommentsInterceptor) - .rateLimit(4) - .apply { - val interceptors = interceptors() - val index = interceptors.indexOfFirst { "Brotli" in it.javaClass.simpleName } - if (index >= 0) { - interceptors.add(interceptors.removeAt(index)) - } - } - .build() - - // API v4 randomly fails - private val retryClient = network.cloudflareClient.newBuilder() - .addInterceptor(RetryInterceptor) - .rateLimit(2) - .build() - - private fun fetchIdBySlug(slug: String): String { - val request = GET("https://manhua.dmzj.com/$slug/", headers) - val html = client.newCall(request).execute().body.string() - val start = "g_comic_id = \"" - val startIndex = html.indexOf(start) + start.length - val endIndex = html.indexOf('"', startIndex) - return html.substring(startIndex, endIndex) - } - - private fun fetchMangaInfoV4(id: String): ApiV4.MangaDto? { - val response = retryClient.newCall(GET(ApiV4.mangaInfoUrl(id), headers)).execute() - return ApiV4.parseMangaInfo(response) - } - - override fun popularMangaRequest(page: Int) = GET(ApiV3.popularMangaUrl(page), headers) - - override fun popularMangaParse(response: Response) = ApiV3.parsePage(response) - - override fun latestUpdatesRequest(page: Int) = GET(ApiV3.latestUpdatesUrl(page), headers) - - override fun latestUpdatesParse(response: Response) = ApiV3.parsePage(response) - - private fun searchMangaById(id: String): MangasPage { - val idNumber = if (id.all { it.isDigit() }) { - id - } else { - // Chinese Pinyin ID - fetchIdBySlug(id) - } - - val sManga = fetchMangaDetails(idNumber) - - return MangasPage(listOf(sManga), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.isEmpty()) { - val ranking = filters.filterIsInstance().firstOrNull() - if (ranking != null && ranking.isEnabled) { - val call = retryClient.newCall(GET(ApiV4.rankingUrl(page, ranking), headers)) - return Observable.fromCallable { - val result = ApiV4.parseRanking(call.execute()) - // result has no manga ID if filtered by certain genres; this can be slow - for (manga in result.mangas) if (manga.url.startsWith(PREFIX_ID_SEARCH)) { - manga.url = getMangaUrl(fetchIdBySlug(manga.url.removePrefix(PREFIX_ID_SEARCH))) - } - result - } - } - val call = client.newCall(GET(ApiV3.pageUrl(page, filters), headers)) - Observable.fromCallable { ApiV3.parsePage(call.execute()) } - } else if (query.startsWith(PREFIX_ID_SEARCH)) { - // ID may be numbers or Chinese pinyin - val id = query.removePrefix(PREFIX_ID_SEARCH).removeSuffix(".html") - Observable.fromCallable { searchMangaById(id) } - } else { - val request = GET(ApiSearch.searchUrlV1(page, query), headers) - Observable.fromCallable { - // this API fails randomly, and might return empty list - repeat(5) { - val result = ApiSearch.parsePageV1(client.newCall(request).execute()) - if (result.mangas.isNotEmpty()) return@fromCallable result - } - throw Exception("搜索出错或无结果") - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - throw UnsupportedOperationException() - } - - override fun searchMangaParse(response: Response): MangasPage { - throw UnsupportedOperationException() - } - - override fun fetchMangaDetails(manga: SManga): Observable { - val id = manga.url.extractMangaId() - return Observable.fromCallable { fetchMangaDetails(id) } - } - - private fun fetchMangaDetails(id: String): SManga { - fetchMangaInfoV4(id)?.run { return toSManga() } - val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute() - return ApiV3.parseMangaDetailsV1(response) - } - - override fun mangaDetailsRequest(manga: SManga): Request { - throw UnsupportedOperationException() - } - - override fun getMangaUrl(manga: SManga): String { - val cid = manga.url.extractMangaId() - return "$baseUrl/info/$cid.html" - } - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - throw UnsupportedOperationException() - } - - override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException() - - override fun fetchChapterList(manga: SManga): Observable> { - return Observable.fromCallable { - val id = manga.url.extractMangaId() - val result = fetchMangaInfoV4(id) - if (result != null && !result.isLicensed) { - return@fromCallable result.parseChapterList() - } - val response = client.newCall(GET(ApiV3.mangaInfoUrlV1(id), headers)).execute() - ApiV3.parseChapterListV1(response) - } - } - - override fun chapterListParse(response: Response): List { - throw UnsupportedOperationException() - } - - override fun getChapterUrl(chapter: SChapter) = "$baseUrl/view/${chapter.url}.html" - - override fun fetchPageList(chapter: SChapter): Observable> { - val path = chapter.url - return Observable.fromCallable { - val response = retryClient.newCall(GET(ApiV4.chapterImagesUrl(path), headers)).execute() - val result = try { - ApiV4.parseChapterImages(response, preferences.imageQuality == LOW_RES) - } catch (_: Throwable) { - client.newCall(GET(ApiV3.chapterImagesUrlV1(path), headers)).execute() - .let(ApiV3::parseChapterImagesV1) - } - if (preferences.showChapterComments) { - result.add(Page(result.size, COMMENTS_FLAG, ApiV3.chapterCommentsUrl(path))) - } - result - } - } - - override fun pageListParse(response: Response): List { - throw UnsupportedOperationException() - } - - // see https://github.com/tachiyomiorg/tachiyomi-extensions/issues/10475 - override fun imageRequest(page: Page): Request { - val url = page.url.takeIf { it.isNotEmpty() } - val imageUrl = page.imageUrl!! - if (url == COMMENTS_FLAG) { - return GET(imageUrl, headers).newBuilder() - .tag(CommentsInterceptor.Tag::class, CommentsInterceptor.Tag()) - .build() - } - val fallbackUrl = when (preferences.imageQuality) { - AUTO_RES -> url - ORIGINAL_RES -> null - LOW_RES -> if (url == null) null else return GET(url, headers) - else -> url - } - return GET(imageUrl, headers).newBuilder() - .tag(ImageUrlInterceptor.Tag::class, ImageUrlInterceptor.Tag(fallbackUrl)) - .build() - } - - // Unused, we can get image urls directly from the chapter page - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException() - - override fun getFilterList() = getFilterListInternal(preferences.isMultiGenreFilter) - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - getPreferencesInternal(screen.context).forEach(screen::addPreference) - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt deleted file mode 100644 index d0fed1ccb..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://www.dmzj.com/info/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class DmzjUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 0) { - val titleId = if (pathSegments.size > 1) { - pathSegments[1] // [m,www].dmzj.com/info/{titleId} - } else { - pathSegments[0] // manhua.dmzj.com/{titleId} - } - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "$PREFIX_ID_SEARCH$titleId") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("DmzjUrlActivity", e.toString()) - } - } else { - Log.e("DmzjUrlActivity", "Could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Filters.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Filters.kt deleted file mode 100644 index f437390d8..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Filters.kt +++ /dev/null @@ -1,201 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList - -fun getFilterListInternal(isMultiGenre: Boolean) = FilterList( - RankingGroup(), - Filter.Separator(), - Filter.Header("分类筛选(查看排行榜、搜索文本时无效)"), - if (isMultiGenre) GenreGroup() else GenreSelectFilter(), - StatusFilter(), - ReaderFilter(), - RegionFilter(), - SortFilter(), -) - -// region Ranking filters - -class RankingGroup : Filter.Group>( - "排行榜(搜索文本时无效)", - listOf>( - EnabledFilter(), - TimeFilter(), - SortFilter(), - GenreFilter(), - ), -) { - val isEnabled get() = (state[0] as EnabledFilter).state - - fun parse() = state.filterIsInstance().joinToString("&") { it.uriPart } - - private class EnabledFilter : CheckBox("查看排行榜") - - private class TimeFilter : QueryFilter( - "榜单", - "by_time", - arrayOf( - Pair("日排行", "0"), - Pair("周排行", "1"), - Pair("月排行", "2"), - Pair("总排行", "3"), - ), - ) - - private class SortFilter : QueryFilter( - "排序", - "rank_type", - arrayOf( - Pair("人气", "0"), - Pair("吐槽", "1"), - Pair("订阅", "2"), - ), - ) - - private class GenreFilter : QueryFilter("题材(慎用/易出错)", "tag_id", genres) - - private open class QueryFilter( - name: String, - private val query: String, - values: Array>, - ) : SelectFilter(name, values) { - override val uriPart get() = query + '=' + super.uriPart - } -} - -// endregion - -// region Normal filters - -fun parseFilters(filters: FilterList): String { - val tags = filters.filterIsInstance().mapNotNull { - it.uriPart.takeUnless(String::isEmpty) - }.joinToString("-").ifEmpty { "0" } - val sort = filters.filterIsInstance().firstOrNull()?.uriPart ?: "0" - return "$tags/$sort" -} - -private interface TagFilter : UriPartFilter - -private class GenreSelectFilter : TagFilter, SelectFilter("题材", genres) - -private class GenreGroup : TagFilter, Filter.Group( - "题材(作品需包含勾选的所有项目)", - genres.drop(1).map { GenreFilter(it.first, it.second) }, -) { - override val uriPart get() = state.filter { it.state }.joinToString("-") { it.value } -} - -private class GenreFilter(name: String, val value: String) : Filter.CheckBox(name) - -private class StatusFilter : TagFilter, SelectFilter( - "状态", - arrayOf( - Pair("全部", ""), - Pair("连载中", "2309"), - Pair("已完结", "2310"), - ), -) - -private class ReaderFilter : TagFilter, SelectFilter( - "受众", - arrayOf( - Pair("全部", ""), - Pair("少年漫画", "3262"), - Pair("少女漫画", "3263"), - Pair("青年漫画", "3264"), - Pair("女青漫画", "13626"), - ), -) - -private class RegionFilter : TagFilter, SelectFilter( - "地域", - arrayOf( - Pair("全部", ""), - Pair("日本", "2304"), - Pair("韩国", "2305"), - Pair("欧美", "2306"), - Pair("港台", "2307"), - Pair("内地", "2308"), - Pair("其他", "8453"), - ), -) - -private class SortFilter : SelectFilter( - "排序", - arrayOf( - Pair("人气", "0"), - Pair("更新", "1"), - ), -) - -// endregion - -private val genres - get() = arrayOf( - Pair("全部", ""), - Pair("冒险", "4"), - Pair("欢乐向", "5"), - Pair("格斗", "6"), - Pair("科幻", "7"), - Pair("爱情", "8"), - Pair("侦探", "9"), - Pair("竞技", "10"), - Pair("魔法", "11"), - Pair("神鬼", "12"), - Pair("校园", "13"), - Pair("惊悚", "14"), - Pair("其他", "16"), - Pair("四格", "17"), - Pair("生活", "3242"), - Pair("ゆり", "3243"), - Pair("秀吉", "3244"), - Pair("悬疑", "3245"), - Pair("纯爱", "3246"), - Pair("热血", "3248"), - Pair("泛爱", "3249"), - Pair("历史", "3250"), - Pair("战争", "3251"), - Pair("萌系", "3252"), - Pair("宅系", "3253"), - Pair("治愈", "3254"), - Pair("励志", "3255"), - Pair("武侠", "3324"), - Pair("机战", "3325"), - Pair("音乐舞蹈", "3326"), - Pair("美食", "3327"), - Pair("职场", "3328"), - Pair("西方魔幻", "3365"), - Pair("高清单行", "4459"), - Pair("TS", "4518"), - Pair("东方", "5077"), - Pair("魔幻", "5806"), - Pair("奇幻", "5848"), - Pair("节操", "6219"), - Pair("轻小说", "6316"), - Pair("颜艺", "6437"), - Pair("搞笑", "7568"), - Pair("仙侠", "7900"), - Pair("舰娘", "13627"), - Pair("动画", "17192"), - Pair("AA", "18522"), - Pair("福瑞", "23323"), - Pair("生存", "23388"), - Pair("2021大赛", "23399"), - Pair("未来漫画家", "25011"), - ) - -interface UriPartFilter { - val uriPart: String -} - -private open class SelectFilter( - name: String, - values: Array>, -) : UriPartFilter, Filter.Select( - name = name, - values = Array(values.size) { values[it].first }, -) { - private val uriParts = Array(values.size) { values[it].second } - override val uriPart get() = uriParts[state] -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt deleted file mode 100644 index caaf0035b..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/ImageUrlInterceptor.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.util.Log -import okhttp3.Interceptor -import okhttp3.Response -import java.io.IOException - -object ImageUrlInterceptor : Interceptor { - - class Tag(val url: String?) - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val tag = request.tag(Tag::class) ?: return chain.proceed(request) - - try { - val response = chain.proceed(request) - if (response.isSuccessful) return response - response.close() - Log.e("DMZJ", "failed to fetch '${request.url}': HTTP ${response.code}") - } catch (e: IOException) { - Log.e("DMZJ", "failed to fetch '${request.url}'", e) - } - - // this can sometimes bypass encoding issues by decoding '+' to ' ' - val decodedUrl = request.url.toString().decodePath() - val newRequest = request.newBuilder().url(decodedUrl).build() - try { - val response = chain.proceed(newRequest) - if (response.isSuccessful) return response - response.close() - Log.e("DMZJ", "failed to fetch '$decodedUrl': HTTP ${response.code}") - } catch (e: IOException) { - Log.e("DMZJ", "failed to fetch '$decodedUrl'", e) - } - - val url = tag.url ?: throw IOException() - val fallbackRequest = request.newBuilder().url(url).build() - return chain.proceed(fallbackRequest) - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt deleted file mode 100644 index b68c2a105..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Preferences.kt +++ /dev/null @@ -1,63 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.content.Context -import android.content.SharedPreferences -import androidx.preference.ListPreference -import androidx.preference.SwitchPreferenceCompat - -// Legacy preferences: -// "apiRatelimitPreference" -> 1..10 default "5" -// "imgCDNRatelimitPreference" -> 1..10 default "5" -// "licensedList" -> StringSet of manga ID -// "hiddenList" -> StringSet of manga ID - -fun getPreferencesInternal(context: Context) = arrayOf( - - ListPreference(context).apply { - key = IMAGE_QUALITY_PREF - title = "图片质量" - summary = "%s\n修改后,已加载的章节需要清除章节缓存才能生效。" - entries = arrayOf("优先原图", "只用原图 (加载出错概率更高)", "优先低清") - entryValues = arrayOf(AUTO_RES, ORIGINAL_RES, LOW_RES) - setDefaultValue(AUTO_RES) - }, - - SwitchPreferenceCompat(context).apply { - key = CHAPTER_COMMENTS_PREF - title = "章末吐槽页" - summary = "修改后,已加载的章节需要清除章节缓存才能生效。" - setDefaultValue(false) - }, - - SwitchPreferenceCompat(context).apply { - key = MULTI_GENRE_FILTER_PREF - title = "分类筛选时允许勾选多个题材" - summary = "可以更精细地筛选出同时符合多个题材的作品。" - setDefaultValue(false) - }, - - SwitchPreferenceCompat(context).apply { - key = DMZJ_V3API_PREF - title = "V3API选择" - summary = "是否使用旧版v3API(默认nnv3api)" - setDefaultValue(false) - }, -) - -val SharedPreferences.imageQuality get() = getString(IMAGE_QUALITY_PREF, AUTO_RES)!! - -val SharedPreferences.showChapterComments get() = getBoolean(CHAPTER_COMMENTS_PREF, false) - -val SharedPreferences.isMultiGenreFilter get() = getBoolean(MULTI_GENRE_FILTER_PREF, false) - -val SharedPreferences.isOlderV3API get() = getBoolean(DMZJ_V3API_PREF, false) - -private const val IMAGE_QUALITY_PREF = "imageSourcePreference" -const val AUTO_RES = "PREFER_ORIG_RES" -const val ORIGINAL_RES = "ORIG_RES_ONLY" -const val LOW_RES = "LOW_RES_ONLY" - -private const val CHAPTER_COMMENTS_PREF = "chapterComments" -private const val MULTI_GENRE_FILTER_PREF = "multiGenreFilter" - -private const val DMZJ_V3API_PREF = "v3APIVersion" diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/RetryInterceptor.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/RetryInterceptor.kt deleted file mode 100644 index f6e45754a..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/RetryInterceptor.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.util.Log -import okhttp3.Interceptor -import okhttp3.Response - -object RetryInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - repeat(2) { - val response = chain.proceed(request) - if (response.isSuccessful) return response - response.close() - Log.e("DMZJ", "failed to fetch '${request.url}': HTTP ${response.code}") - } - return chain.proceed(request) - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/utils/RSA.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/utils/RSA.kt deleted file mode 100644 index 4ec3595f7..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/utils/RSA.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj.utils - -import android.util.Base64 -import java.security.KeyFactory -import java.security.PrivateKey -import java.security.spec.PKCS8EncodedKeySpec -import javax.crypto.Cipher -import kotlin.math.min - -object RSA { - private val cipher by lazy(LazyThreadSafetyMode.NONE) { - Cipher.getInstance("RSA/ECB/PKCS1Padding") - } - - private const val MAX_DECRYPT_BLOCK = 128 - - fun getPrivateKey(privateKey: String): PrivateKey { - val keyBytes = Base64.decode(privateKey, Base64.DEFAULT) - val pkcs8KeySpec = PKCS8EncodedKeySpec(keyBytes) - val keyFactory = KeyFactory.getInstance("RSA") - val privateK = keyFactory.generatePrivate(pkcs8KeySpec) - return privateK - } - - @Synchronized // because Cipher is not thread-safe - fun decrypt(encrypted: String, key: PrivateKey): ByteArray { - val cipher = this.cipher - cipher.init(Cipher.DECRYPT_MODE, key) // always reset in case of illegal state - val encryptedData = Base64.decode(encrypted, Base64.DEFAULT) - val inputLen = encryptedData.size - - val result = ByteArray(inputLen) - var resultSize = 0 - - for (offset in 0 until inputLen step MAX_DECRYPT_BLOCK) { - val blockLen = min(MAX_DECRYPT_BLOCK, inputLen - offset) - resultSize += cipher.doFinal(encryptedData, offset, blockLen, result, resultSize) - } - - return result.copyOf(resultSize) - } -}