Update Korean extensions (#1024)

JMana: Temporary pagination fix.
MSM: Improve image decoder(which don't modifying url anymore), Use `s3` and `img_list1` as fallback, Fix description issue, and Update filter and domain
NewToki: Move to Factory, Add Webtoon Source(Not tested), and Update domain.
This commit is contained in:
DitFranXX 2019-04-14 12:15:26 +09:00 committed by Eugene
parent cec1a82ce4
commit d8c84d3813
9 changed files with 175 additions and 78 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: JMana'
pkgNameSuffix = 'ko.jmana'
extClass = '.JMana'
extVersionCode = 6
extVersionCode = 7
libVersion = '1.2'
}

View File

@ -49,12 +49,8 @@ class JMana : ParsedHttpSource() {
popularMangaFromElement(element)
}
val hasNextPage = try {
val page = document.select(popularMangaNextPageSelector())
!page[page.size - 2].getElementsByTag("a").attr("href").isNullOrEmpty()
} catch (_: Exception) {
false
}
// Can not detect what page is last page but max mangas are 40.
val hasNextPage = mangas.size == 40
return MangasPage(mangas, hasNextPage)
}
@ -130,10 +126,20 @@ class JMana : ParsedHttpSource() {
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/frame")
override fun latestUpdatesNextPageSelector() = ""
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(popularMangaSelector()).map { element ->
popularMangaFromElement(element)
}
val hasNextPage = false
return MangasPage(mangas, hasNextPage)
}
//We are able to get the image URL directly from the page list

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: MangaShow.Me (ManaMoa)'
pkgNameSuffix = 'ko.mangashowme'
extClass = '.MangaShowMe'
extVersionCode = 9
extVersionCode = 10
libVersion = '1.2'
}

View File

@ -43,7 +43,6 @@ private fun searchNaming() = arrayOf(
private fun searchStatus() = arrayOf(
"Not Set",
"미분류",
"주간",
"격주",
"월간",
@ -65,7 +64,6 @@ private fun searchGenres() = listOf(
SearchCheckBox(0, "드라마"),
SearchCheckBox(0, "라노벨"),
SearchCheckBox(0, "러브코미디"),
SearchCheckBox(0, "로맨스"),
SearchCheckBox(0, "먹방"),
SearchCheckBox(0, "백합"),
SearchCheckBox(0, "붕탁"),
@ -76,7 +74,6 @@ private fun searchGenres() = listOf(
SearchCheckBox(0, "애니화"),
SearchCheckBox(0, "액션"),
SearchCheckBox(0, "역사"),
SearchCheckBox(0, "요리"),
SearchCheckBox(0, "음악"),
SearchCheckBox(0, "이세계"),
SearchCheckBox(0, "일상"),
@ -94,7 +91,7 @@ fun getFilters() = FilterList(
Filter.Separator(),
SearchMatch(),
Filter.Separator(),
TextField("Author/Artist (Exact Search)", "author")
TextField("Author/Artist (Exact search)", "author")
)
fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: String, filters: FilterList): Request {
@ -107,7 +104,7 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St
filters.forEach { filter ->
when (filter) {
is SearchMatch -> {
matchFilter = filter.state
matchFilter = filter.state + 1
}
is TextField -> {

View File

@ -4,8 +4,10 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Rect
import eu.kanade.tachiyomi.network.GET
import okhttp3.*
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Response
import okhttp3.ResponseBody
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
@ -15,26 +17,15 @@ import java.io.InputStream
* It's not need now, but it remains in this code for sometime.
*/
internal class ImageDecoder(private val version: String, scripts: String) {
private val cnt = substringBetween(scripts, "var view_cnt = ", ";")
internal class ImageDecoder(scripts: String) {
private val cnt = substringBetween(scripts, "var view_cnt = ", ";")
.toIntOrNull() ?: 0
private val chapter = substringBetween(scripts, "var chapter = ", ";")
.toIntOrNull() ?: 0
fun request(url: String): String {
return when (version) {
"v1" -> decodeVersion1ImageUrl(cnt, chapter, url)
else -> url
}
}
private fun decodeVersion1ImageUrl(cnt: Int, chapter: Int, url: String): String {
return HttpUrl.parse(url)!!.newBuilder()
.addQueryParameter("cnt", cnt.toString())
.addQueryParameter("ch", chapter.toString())
.addQueryParameter("ver", "v1")
.addQueryParameter("type", "ImageDecodeRequest")
.build()!!.toString()
if (cnt < 10) return url
return "$url??$chapter;$cnt"
}
}
@ -42,21 +33,18 @@ internal class ImageDecoder(private val version: String, scripts: String) {
internal class ImageDecoderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val req = chain.request()
val url = req.url().toString()
return if (url.contains("ImageDecodeRequest")) {
val response = chain.proceed(req)
val decodeHeader = req.header("ImageDecodeRequest")
return if (decodeHeader != null) {
try {
val reqUrl = HttpUrl.parse(url)!!
val s = decodeHeader.split(";").map { it.toInt() }
val viewCnt = reqUrl.queryParameter("cnt")!!
val version = reqUrl.queryParameter("ver")!!
val chapter = reqUrl.queryParameter("ch")!!
val imageUrl = url.split("?").first()
val response = chain.proceed(GET("$imageUrl?quick"))
if (viewCnt.toInt() < 10) return response // Pass decoder if it's not scrambled.
if (s[1] < 10) return response
val res = response.body()!!.byteStream().use {
decodeImageRequest(version, chapter, viewCnt, it)
decodeImageRequest(it, s[0], s[1])
}
val rb = ResponseBody.create(MediaType.parse("image/png"), res)
@ -66,19 +54,28 @@ internal class ImageDecoderInterceptor : Interceptor {
throw IOException("Image decoder failure.", e)
}
} else {
chain.proceed(req)
response
}
}
private fun decodeImageRequest(img: InputStream, chapter: Int, view_cnt: Int): ByteArray {
val decoded = BitmapFactory.decodeStream(img)
val result = imageDecoder(decoded, chapter, view_cnt)
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.PNG, 100, output)
return output.toByteArray()
}
/*
* `decodeV1ImageNative` is modified version of
* `imageDecoder` is modified version of
* https://github.com/junheah/MangaViewAndroid/blob/b69a4427258fe7fc5fb5363108572bbee0d65e94/app/src/main/java/ml/melun/mangaview/mangaview/Decoder.java#L6-L60
*
* MIT License
*
* Copyright (c) 2019 junheah
*/
private fun decodeV1ImageNative(input: Bitmap, chapter: Int, view_cnt: Int, half: Int = 0, _CX: Int = MangaShowMe.V1_CX, _CY: Int = MangaShowMe.V1_CY): Bitmap {
private fun imageDecoder(input: Bitmap, chapter: Int, view_cnt: Int, half: Int = 0, _CX: Int = MangaShowMe.V1_CX, _CY: Int = MangaShowMe.V1_CY): Bitmap {
if (view_cnt == 0) return input
val viewCnt = view_cnt / 10
var CX = _CX
@ -159,22 +156,6 @@ internal class ImageDecoderInterceptor : Interceptor {
Math.floor(1000 * (n - Math.floor(n))) +
Math.floor(10000 * (a - Math.floor(a)))).toInt()
}
private fun decodeImageRequest(version: String, chapter: String, view_cnt: String, img: InputStream): ByteArray {
return when (version) {
"v1" -> decodeV1Image(chapter, view_cnt, img)
else -> img.readBytes()
}
}
private fun decodeV1Image(chapter: String, view_cnt: String, img: InputStream): ByteArray {
val decoded = BitmapFactory.decodeStream(img)
val result = decodeV1ImageNative(decoded, chapter.toInt(), view_cnt.toInt())
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.PNG, 100, output)
return output.toByteArray()
}
}
private fun substringBetween(target: String, prefix: String, suffix: String): String = {

View File

@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit
**/
class MangaShowMe : ParsedHttpSource() {
override val name = "MangaShow.Me"
override val baseUrl = "https://manamoa.net"
override val baseUrl = "https://manamoa2.net"
override val lang: String = "ko"
// Latest updates currently returns duplicate manga as it separates manga into chapters
@ -33,9 +33,38 @@ class MangaShowMe : ParsedHttpSource() {
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(ImageDecoderInterceptor())
.addInterceptor { chain ->
val req = chain.request()
// only for image Request
if (!req.url().host().contains("filecdn.xyz")) return@addInterceptor chain.proceed(req)
val secondUrl = req.header("SecondUrlToRequest")
fun get(flag: Int = 0): Request {
val url = when (flag) {
1 -> req.url().toString().replace("img.", "s3.")
2 -> secondUrl!!
else -> req.url().toString()
}
return req.newBuilder()!!.url(url)
.removeHeader("ImageDecodeRequest")
.removeHeader("SecondUrlToRequest")
.build()!!
}
val res = chain.proceed(get())
val length = res.header("content-length")
if (length == null || length.toInt() < 50000) {
val s3res = chain.proceed(get(1)) // s3
if (!s3res.isSuccessful && secondUrl != null) {
chain.proceed(get(2)) // secondUrl
} else s3res
} else res
}
.build()!!
//override fun popularMangaSelector() = "div.basic-post-gallery > div > div.post-row"
override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row"
override fun popularMangaFromElement(element: Element): SManga {
@ -84,8 +113,8 @@ class MangaShowMe : ParsedHttpSource() {
val publishTypeText = thumbnailElement.select("a.publish_type").text() ?: ""
val authorText = thumbnailElement.select("a.author").text() ?: ""
val mangaLike = info.select("div.recommend > i.fa").first().text() ?: "0"
val mangaChaptersLike = mangaElementsSum(document.select("div.addedAt i.fa.fa-thumbs-up > span"))
val mangaComments = mangaElementsSum(document.select("div.addedAt i.fa.fa-comment > span"))
val mangaChaptersLike = mangaElementsSum(document.select(".title i.fa.fa-thumbs-up > span"))
val mangaComments = mangaElementsSum(document.select(".title i.fa.fa-comment > span"))
val genres = mutableListOf<String>()
document.select("div.left-info > .manga-tags > a.tag").forEach {
genres.add(it.text())
@ -180,14 +209,30 @@ class MangaShowMe : ParsedHttpSource() {
val pages = mutableListOf<Page>()
try {
val element = document.select("div.col-md-9.at-col.at-main script")
val imageUrl = element.html().substringAfter("var img_list = [").substringBefore("];")
val element = document.select("div.col-md-9.at-col.at-main script").html()
val imageUrl = element.substringAfter("var img_list = [").substringBefore("];")
val imageUrls = JSONArray("[$imageUrl]")
val decoder = ImageDecoder("v1", element.html())
(0 until imageUrls.length())
.map { imageUrls.getString(it) }
.forEach { pages.add(Page(pages.size, "", decoder.request(it))) }
val imageUrl1 = element.substringAfter("var img_list1 = [").substringBefore("];")
val imageUrls1 = JSONArray("[$imageUrl1]")
val decoder = ImageDecoder(element)
if (imageUrls.length() != imageUrls1.length()) {
(0 until imageUrls.length())
.map { imageUrls.getString(it) }
.forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) }
} else {
(0 until imageUrls.length())
.map {
imageUrls.getString(it) + try {
"!!${imageUrls1.getString(it)}"
} catch (_: Exception) {
""
}
}
.forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) }
}
} catch (e: Exception) {
e.printStackTrace()
}
@ -195,6 +240,29 @@ class MangaShowMe : ParsedHttpSource() {
return pages
}
override fun imageRequest(page: Page): Request {
val requestHeaders = try {
val data = page.url.substringAfter("??", "")
val secondUrl = page.url.substringAfter("!!", "").substringBefore("??")
val builder = headers.newBuilder()!!
if (data.isNotBlank()) {
builder.add("ImageDecodeRequest", data)
}
if (secondUrl.isNotBlank()) {
builder.add("SecondUrlToRequest", secondUrl)
}
builder.build()!!
} catch (_: Exception) {
headers
}
return GET(page.imageUrl!!, requestHeaders)
}
// Latest not supported
override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!")

View File

@ -4,8 +4,8 @@ apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: NewToki'
pkgNameSuffix = 'ko.newtoki'
extClass = '.NewToki'
extVersionCode = 6
extClass = '.NewTokiFactory'
extVersionCode = 7
libVersion = '1.2'
}

View File

@ -16,9 +16,7 @@ import java.util.*
/**
* NewToki Source
**/
class NewToki : ParsedHttpSource() {
override val name = "NewToki"
override val baseUrl = "https://newtoki7.net"
open class NewToki(override val name: String, override val baseUrl: String, private val boardName: String) : ParsedHttpSource() {
override val lang: String = "ko"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
@ -39,7 +37,7 @@ class NewToki : ParsedHttpSource() {
override fun popularMangaNextPageSelector() = "ul.pagination > li:not(.disabled)"
// Do not add page parameter if page is 1 to prevent tracking.
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comic" + if (page > 1) "/p$page" else "")
override fun popularMangaRequest(page: Int) = GET("$baseUrl/$boardName" + if (page > 1) "/p$page" else "")
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
@ -61,7 +59,7 @@ class NewToki : ParsedHttpSource() {
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaSelector()
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic" + (if (page > 1) "/p$page" else "") + "?stx=$query")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/$boardName" + (if (page > 1) "/p$page" else "") + "?stx=$query")
override fun mangaDetailsParse(document: Document): SManga {

View File

@ -0,0 +1,47 @@
package eu.kanade.tachiyomi.extension.ko.newtoki
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl
import okhttp3.Request
private const val baseDomain = "newtoki10"
class NewTokiFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
NewTokiManga(),
NewTokiWebtoon()
)
}
class NewTokiManga : NewToki("NewToki", "https://$baseDomain.net", "comic")
class NewTokiWebtoon : NewToki("NewToki (Webtoon)", "https://$baseDomain.com", "webtoon") {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/webtoon" + (if (page > 1) "/p$page" else ""))!!.newBuilder()
filters.forEach { filter ->
when (filter) {
is SearchTypeList -> {
if (filter.state > 0) {
url.addQueryParameter("toon", filter.values[filter.state])
}
}
}
}
if (!query.isBlank()) {
url.addQueryParameter("stx", query)
}
return GET(url.toString())
}
private class SearchTypeList : Filter.Select<String>("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰"))
override fun getFilterList() = FilterList(
SearchTypeList()
)
}