From 30d34de6531255fb1d2fe5f303fbe4c5b06cabc3 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:21:10 +0700 Subject: [PATCH 1/8] Update CuuTruyenParser.kt --- .../parsers/site/vi/CuuTruyenParser.kt | 435 +++++++++++------- 1 file changed, 262 insertions(+), 173 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt index f06c970f..430edcdd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/CuuTruyenParser.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Response +import okhttp3.HttpUrl import okhttp3.ResponseBody.Companion.toResponseBody import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser @@ -14,185 +15,273 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.json.* +import java.awt.Color +import java.awt.Graphics2D +import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.* -import java.util.zip.Inflater +import javax.imageio.ImageIO @MangaSourceParser("CUUTRUYEN", "CuuTruyen", "vi") internal class CuuTruyenParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.CUUTRUYEN, 20), Interceptor { - override val configKeyDomain = - ConfigKey.Domain("cuutruyen.net", "nettrom.com", "hetcuutruyen.net", "cuutruyent9sv7.xyz") - - override val availableSortOrders: Set = EnumSet.of( - SortOrder.UPDATED, - SortOrder.POPULARITY, - SortOrder.NEWEST, - ) - - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, - ) - - override suspend fun getFilterOptions() = MangaListFilterOptions() - - override fun getRequestHeaders(): Headers = Headers.Builder() - .add("User-Agent", UserAgents.KOTATSU) - .build() - - private val decryptionKey = "3141592653589793" - - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - val url = buildString { - append("https://") - append(domain) - when { - !filter.query.isNullOrEmpty() -> { - append("/api/v2/mangas/search?q=") - append(filter.query.urlEncoded()) - append("&page=") - append(page.toString()) - } - - else -> { - val tag = filter.tags.oneOrThrowIfMany() - if (tag != null) { - append("/api/v2/tags/") - append(tag.key) - } else { - append("/api/v2/mangas") - when (order) { - SortOrder.UPDATED -> append("/recently_updated") - SortOrder.POPULARITY -> append("/top") - SortOrder.NEWEST -> append("/recently_updated") - else -> append("/recently_updated") - } - } - append("?page=") - append(page.toString()) - } - } - - append("&per_page=") - append(pageSize) - } - - val json = webClient.httpGet(url).parseJson() - val data = json.getJSONArray("data") - - return data.mapJSON { jo -> - Manga( - id = generateUid(jo.getLong("id")), - url = "/api/v2/mangas/${jo.getLong("id")}", - publicUrl = "https://$domain/manga/${jo.getLong("id")}", - title = jo.getString("name"), - altTitle = null, - coverUrl = jo.getString("cover_url"), - largeCoverUrl = jo.getString("cover_mobile_url"), - author = jo.getStringOrNull("author_name"), - tags = emptySet(), - state = null, - description = null, - isNsfw = isNsfwSource, - source = source, - rating = RATING_UNKNOWN, - ) - } - } - - override suspend fun getDetails(manga: Manga): Manga = coroutineScope { - val url = "https://" + domain + manga.url - val chapters = async { - webClient.httpGet("$url/chapters").parseJson().getJSONArray("data") - } - val json = webClient.httpGet(url).parseJson().getJSONObject("data") - val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - - manga.copy( - title = json.getStringOrNull("name") ?: manga.title, - isNsfw = json.getBooleanOrDefault("is_nsfw", manga.isNsfw), - author = json.optJSONObject("author")?.getStringOrNull("name")?.substringBefore(','), - description = json.getString("full_description"), - tags = json.optJSONArray("tags")?.mapJSONToSet { jo -> - MangaTag( - title = jo.getString("name").toTitleCase(sourceLocale), - key = jo.getString("slug"), - source = source, - ) - }.orEmpty(), - chapters = chapters.await().mapJSON { jo -> - val chapterId = jo.getLong("id") - val number = jo.getFloatOrDefault("number", 0f) - MangaChapter( - id = generateUid(chapterId), - name = jo.getStringOrNull("name") ?: number.formatSimple(), - number = number, - volume = 0, - url = "/api/v2/chapters/$chapterId", - scanlator = jo.optString("group_name"), - uploadDate = chapterDateFormat.tryParse(jo.getStringOrNull("created_at")), - branch = null, - source = source, - ) - }.reversed(), - ) - } - - override suspend fun getPages(chapter: MangaChapter): List { - val url = "https://$domain${chapter.url}" - val json = webClient.httpGet(url).parseJson().getJSONObject("data") - - return json.getJSONArray("pages").mapJSON { jo -> - val imageUrl = jo.getString("image_url") - MangaPage( - id = generateUid(jo.getLong("id")), - url = imageUrl, - preview = null, - source = source, - ) - } - } - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val response = chain.proceed(request) - - if (!request.url.host.contains(domain, ignoreCase = true)) { - return response - } - - val body = response.body ?: return response - val contentType = body.contentType() - val bytes = body.bytes() - - val decrypted = try { - decompress(decrypt(bytes)) - } catch (e: Exception) { - bytes - } - val newBody = decrypted.toResponseBody(contentType) - return response.newBuilder().body(newBody).build() - } - - private fun decrypt(input: ByteArray): ByteArray { - val key = decryptionKey.toByteArray() - return input.mapIndexed { index, byte -> - (byte.toInt() xor key[index % key.size].toInt()).toByte() - }.toByteArray() - } - - private fun decompress(input: ByteArray): ByteArray { - val inflater = Inflater() - inflater.setInput(input, 0, input.size) - val outputStream = ByteArrayOutputStream(input.size) - val buffer = ByteArray(1024) - while (!inflater.finished()) { - val count = inflater.inflate(buffer) - outputStream.write(buffer, 0, count) - } - return outputStream.toByteArray() - } + override val configKeyDomain = + ConfigKey.Domain("cuutruyen.net", "nettrom.com", "hetcuutruyen.net", "cuutruyent9sv7.xyz") + + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions() + + override fun getRequestHeaders(): Headers = Headers.Builder() + .add("User-Agent", UserAgents.KOTATSU) + .build() + + private val decryptionKey = "3141592653589793".toByteArray() + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + val url = buildString { + append("https://") + append(domain) + when { + !filter.query.isNullOrEmpty() -> { + append("/api/v2/mangas/search?q=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) + } + + else -> { + val tag = filter.tags.oneOrThrowIfMany() + if (tag != null) { + append("/api/v2/tags/") + append(tag.key) + } else { + append("/api/v2/mangas") + when (order) { + SortOrder.UPDATED -> append("/recently_updated") + SortOrder.POPULARITY -> append("/top") + SortOrder.NEWEST -> append("/recently_updated") + else -> append("/recently_updated") + } + } + append("?page=") + append(page.toString()) + } + } + + append("&per_page=") + append(pageSize) + } + + val json = webClient.httpGet(url).parseJson() + val data = json.getJSONArray("data") + + return data.mapJSON { jo -> + Manga( + id = generateUid(jo.getLong("id")), + url = "/api/v2/mangas/${jo.getLong("id")}", + publicUrl = "https://$domain/manga/${jo.getLong("id")}", + title = jo.getString("name"), + altTitle = null, + coverUrl = jo.getString("cover_url"), + largeCoverUrl = jo.getString("cover_mobile_url"), + author = jo.getStringOrNull("author_name"), + tags = emptySet(), + state = null, + description = null, + isNsfw = isNsfwSource, + source = source, + rating = RATING_UNKNOWN, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { + val url = "https://" + domain + manga.url + val chapters = async { + webClient.httpGet("$url/chapters").parseJson().getJSONArray("data") + } + val json = webClient.httpGet(url).parseJson().getJSONObject("data") + val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + + manga.copy( + title = json.getStringOrNull("name") ?: manga.title, + isNsfw = json.getBooleanOrDefault("is_nsfw", manga.isNsfw), + author = json.optJSONObject("author")?.getStringOrNull("name")?.substringBefore(','), + description = json.getString("full_description"), + tags = json.optJSONArray("tags")?.mapJSONToSet { jo -> + MangaTag( + title = jo.getString("name").toTitleCase(sourceLocale), + key = jo.getString("slug"), + source = source, + ) + }.orEmpty(), + chapters = chapters.await().mapJSON { jo -> + val chapterId = jo.getLong("id") + val number = jo.getFloatOrDefault("number", 0f) + MangaChapter( + id = generateUid(chapterId), + name = jo.getStringOrNull("name") ?: number.formatSimple(), + number = number, + volume = 0, + url = "/api/v2/chapters/$chapterId", + scanlator = jo.optString("group_name"), + uploadDate = chapterDateFormat.tryParse(jo.getStringOrNull("created_at")), + branch = null, + source = source, + ) + }.reversed(), + ) + } + + private val pageSizesMap = mutableMapOf>() + + override suspend fun getPages(chapter: MangaChapter): List { + val url = "https://$domain${chapter.url}" + val json = webClient.httpGet(url).parseJson().getJSONObject("data") + + return json.getJSONArray("pages").mapJSON { jo -> + val imageUrl = jo.getString("image_url") + val id = jo.getLong("id") + pageSizesMap[id] = jo.getInt("width") to jo.getInt("height") + MangaPage( + id = generateUid(id), + url = imageUrl, + preview = null, + source = source, + ) + } + } + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + + if (!request.url.host.contains(domain, ignoreCase = true)) { + return response + } + + val body = response.body ?: return response + val contentType = body.contentType() + val bytes = body.bytes() + + val pageId = getPageIdFromUrl(request.url) + val (originalWidth, originalHeight) = pageSizesMap[pageId] ?: (0 to 0) + + val decrypted = decryptDRM(bytes, decryptionKey) + val reconstructed = decrypted?.let { + reconstructImage(it, originalWidth, originalHeight) + } ?: bytes + + val newBody = reconstructed.toResponseBody(contentType) + return response.newBuilder().body(newBody).build() + } + + private fun getPageIdFromUrl(url: HttpUrl): Long { + return url.pathSegments.lastOrNull()?.toLongOrNull() ?: 0L + } + + private fun getOriginalWidthFromRequest(request: okhttp3.Request): Int { + val width = request.url.queryParameter("width")?.toIntOrNull() ?: 0 + return width + } + + private fun getOriginalHeightFromRequest(request: okhttp3.Request): Int { + val height = request.url.queryParameter("height")?.toIntOrNull() ?: 0 + return height + } + + private fun decryptDRM(drmData: ByteArray, key: ByteArray): ByteArray? { + return try { + drmData.mapIndexed { index, byte -> + (byte.toInt() xor key[index % key.size].toInt()).toByte() + }.toByteArray() + } catch (e: Exception) { + null + } + } + + private fun reconstructImage(decrypted: ByteArray, originalWidth: Int, originalHeight: Int): ByteArray? { + return try { + val delimiter = "#v".toByteArray() + val delimiterIndex = decrypted.indexOfFirst { + decrypted.sliceArray(it until (it + delimiter.size)).contentEquals(delimiter) + } + if (delimiterIndex == -1) { + return null + } + + val segmentsInfoStart = delimiterIndex + delimiter.size + val segmentsData = decrypted.sliceArray(segmentsInfoStart until decrypted.size) + val segments = String(segmentsData).split("|").filter { it.contains("-") } + + if (segments.isEmpty()) { + return null + } + + val segmentInfo = segments.mapNotNull { seg -> + try { + val (dyStr, heightStr) = seg.split("-") + val dy = if (dyStr.startsWith("dy")) dyStr.substring(2).trim() else dyStr.trim() + val dyInt = dy.toInt() + val height = heightStr.trim().toInt() + dyInt to height + } catch (e: Exception) { + null + } + } + + if (segmentInfo.isEmpty()) { + return null + } + + var finalSegmentInfo = segmentInfo + val totalHeight = finalSegmentInfo.sumOf { it.second } + if (totalHeight != originalHeight) { + val remainingHeight = originalHeight - totalHeight + if (remainingHeight > 0) { + finalSegmentInfo = finalSegmentInfo.toMutableList().apply { add(0 to remainingHeight) } + } + } + + val originalImage = ImageIO.read(decrypted.inputStream()) ?: return null + val newImage = BufferedImage(originalWidth, originalHeight, BufferedImage.TYPE_INT_RGB) + val graphics: Graphics2D = newImage.createGraphics() + + var sy = 0 + for ((dy, segHeight) in finalSegmentInfo) { + if (sy + segHeight > originalHeight) { + break + } + val subImage = originalImage.getSubimage(0, sy, originalWidth, segHeight) + graphics.drawImage(subImage, 0, dy, null) + sy += segHeight + } + graphics.dispose() + + val outputStream = ByteArrayOutputStream() + ImageIO.write(newImage, "JPEG", outputStream) + outputStream.toByteArray() + } catch (e: Exception) { + null + } + } + + private fun ByteArray.indexOfFirst(predicate: (Int) -> Boolean): Int { + for (i in indices) { + if (predicate(i)) return i + } + return -1 + } } From 9f340b2dfb0db5f9b6e1796ba9db56fc3b173cd5 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:28:50 +0700 Subject: [PATCH 2/8] Update LxManga.kt --- src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt index 27cf622d..415cc152 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt @@ -21,7 +21,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.POPULARITY, ) - override val configKeyDomain = ConfigKey.Domain("lxmanga.life") + override val configKeyDomain = ConfigKey.Domain("lxmanga.click") override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) From 6532f392b5eaf69a6d4270c1c7b4c3a03ef35a03 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:29:39 +0700 Subject: [PATCH 3/8] Update Truyenqq.kt --- .../kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt index 7dc352a8..75c75430 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt @@ -28,10 +28,7 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = when { From 3f502de1f765099242d9fa2c11ea9433b7b1d178 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:30:05 +0700 Subject: [PATCH 4/8] Update Truyenqq.kt --- .../kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt index 75c75430..7528d1d3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt @@ -30,6 +30,11 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = when { !filter.query.isNullOrEmpty() -> { From 844e5fdb503a502c0e276b69e044a4c25283ec79 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:30:34 +0700 Subject: [PATCH 5/8] Update Truyenqq.kt --- src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt index 7528d1d3..60774c2b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt @@ -4,6 +4,7 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat From 8da3b4b7acfd4a78535bab4c8910b8891bbdde35 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:31:49 +0700 Subject: [PATCH 6/8] Update NetTruyen.kt --- .../org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt index 4dc10761..2660cf18 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt @@ -10,6 +10,6 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser internal class NetTruyen(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.NETTRUYEN, "www.nettruyenupp.com", 44) { override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( - "www.nettruyenupp.com", "nettruyenww.com", "nettruyenx.com", + "nettruyenupp.com", "nettruyenww.com", "nettruyenx.com", ) } From edac81f51a76b9dcb27c0bdafb78ab218c0c0790 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:32:26 +0700 Subject: [PATCH 7/8] Update NetTruyenLL.kt --- .../koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt index 0529e4c6..52c3ee51 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenLL.kt @@ -9,9 +9,7 @@ import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.util.* import java.util.EnumSet -import org.koitharu.kotatsu.parsers.Broken -@Broken @MangaSourceParser("NETTRUYENLL", "NetTruyenLL", "vi") internal class NetTruyenLL(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.NETTRUYENLL, "nettruyenll.com", 20) { From c593798d8315f2fb4f813a87359c663457887569 Mon Sep 17 00:00:00 2001 From: Draken <131387159+dragonx943@users.noreply.github.com> Date: Wed, 25 Sep 2024 19:32:55 +0700 Subject: [PATCH 8/8] Update NetTruyenSSR.kt --- .../koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt index 23f75bb4..4cb121dc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyenSSR.kt @@ -8,10 +8,8 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.util.* -import org.koitharu.kotatsu.parsers.Broken import java.util.EnumSet -@Broken @MangaSourceParser("NETTRUYENSSR", "NetTruyenSSR", "vi") internal class NetTruyenSSR(context: MangaLoaderContext) : WpComicsParser(context, MangaParserSource.NETTRUYENSSR, "nettruyenssr.com", 20) {