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:
parent
cec1a82ce4
commit
d8c84d3813
|
@ -5,7 +5,7 @@ ext {
|
|||
appName = 'Tachiyomi: JMana'
|
||||
pkgNameSuffix = 'ko.jmana'
|
||||
extClass = '.JMana'
|
||||
extVersionCode = 6
|
||||
extVersionCode = 7
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,7 @@ ext {
|
|||
appName = 'Tachiyomi: MangaShow.Me (ManaMoa)'
|
||||
pkgNameSuffix = 'ko.mangashowme'
|
||||
extClass = '.MangaShowMe'
|
||||
extVersionCode = 9
|
||||
extVersionCode = 10
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue