Update korean extension, Initial Support for Naver Comic (#1087)
* Update korean extension, Rename MSM to ManaMoa. (Need to migrate) JMana: Fix errors due to site changes NewToki: Supports baseUrl override. update domain, MSM (ManaMoa) ---- MSM now uses id on manga instead of title name. (It breaks whole things.) I don't want to increase `versionId` so I rename extension. (I renamed while ago and reverted because it wasn't need on that time) So result is need to migrate from MangaShow.Me to ManaMoa. MangaShow.Me is now deprecated and will remain as dummy before 1.2.15 * Fix Image request on old chapters due to site changes + mistake on ManaMoa extension * Refactor ManaMoa ImageUrlHandler * Initial Support for Naver Comic * Update Icons, Support Challenge Webtoons Note that (normal) Challenge webtoons has chapter bug rn. dunno why.
|
@ -5,7 +5,7 @@ ext {
|
|||
appName = 'Tachiyomi: JMana'
|
||||
pkgNameSuffix = 'ko.jmana'
|
||||
extClass = '.JMana'
|
||||
extVersionCode = 7
|
||||
extVersionCode = 8
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class JMana : ParsedHttpSource() {
|
|||
override fun popularMangaNextPageSelector() = "div.page > ul > li"
|
||||
|
||||
// Do not add page parameter if page is 1 to prevent tracking.
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comic_main_frame?page=${page - 1}")
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comic_main_frame?tag=null&keyword=null&chosung=null&page=${page - 1}")
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
@ -59,33 +59,41 @@ class JMana : 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_main_frame?keyword=$query&page=${page - 1}")
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/?tag=null&keyword=$query&chosung=null&page=${page - 1}")
|
||||
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val info = document.select("div.leftM").first()
|
||||
val authorText = info.select("div.comBtnArea a").text()
|
||||
val titleDescription = info.select("li.row")
|
||||
val descriptionElement = document.select(".media > .row > .media-body.col-9 > div")
|
||||
val thumbnailUrl = document.select(".media > .row > .media-body.col-3 img.media-object-list").attr("src")
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.title = titleDescription.first().text()
|
||||
manga.description = titleDescription.last().text()
|
||||
manga.author = authorText
|
||||
descriptionElement
|
||||
.map { it.text() }
|
||||
.forEach { text ->
|
||||
when {
|
||||
DETAIL_TITLE in text -> manga.title = text.substringAfter(DETAIL_TITLE).trim()
|
||||
DETAIL_AUTHOR in text -> manga.author = text.substringAfter(DETAIL_AUTHOR).trim()
|
||||
DETAIL_GENRE in text -> manga.genre = text.substringAfter("장르 : [").substringBefore("]").trim()
|
||||
DETAIL_DESCRIPTION in text -> text.substringAfter(DETAIL_DESCRIPTION).trim()
|
||||
}
|
||||
}
|
||||
manga.thumbnail_url = thumbnailUrl
|
||||
manga.status = SManga.UNKNOWN
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.contents > ul > li"
|
||||
override fun chapterListSelector() = "div.section > .post > .post-content-list"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val linkElement = element.select("a")
|
||||
val linkElement = element.select(".entry-title a")
|
||||
val rawName = linkElement.text()
|
||||
val chapterUrl = "${linkElement.attr("href")}?viewstyle=list".replace("book/", "book_frame/")
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.url = linkElement.attr("href").replace("book/", "book_frame/")
|
||||
chapter.url = chapterUrl
|
||||
chapter.chapter_number = parseChapterNumber(rawName)
|
||||
chapter.name = rawName.trim()
|
||||
chapter.date_upload = parseChapterDate(element.select("ul > li:not(.fcR)").last().text())
|
||||
chapter.date_upload = parseChapterDate(element.select("li.publish-date span").last().text())
|
||||
return chapter
|
||||
}
|
||||
|
||||
|
@ -105,7 +113,7 @@ class JMana : ParsedHttpSource() {
|
|||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return try {
|
||||
SimpleDateFormat("yyyy-MM-dd").parse(date).time
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm").parse(date).time
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
0
|
||||
|
@ -146,4 +154,11 @@ class JMana : ParsedHttpSource() {
|
|||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!")
|
||||
|
||||
override fun getFilterList() = FilterList()
|
||||
|
||||
companion object {
|
||||
const val DETAIL_TITLE = "제목 : "
|
||||
const val DETAIL_GENRE = "장르 : "
|
||||
const val DETAIL_AUTHOR = "작가 : "
|
||||
const val DETAIL_DESCRIPTION = "설명 : "
|
||||
}
|
||||
}
|
|
@ -2,10 +2,10 @@ apply plugin: 'com.android.application'
|
|||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: MangaShow.Me (ManaMoa)'
|
||||
appName = 'Tachiyomi: ManaMoa (MangaShow.Me)'
|
||||
pkgNameSuffix = 'ko.mangashowme'
|
||||
extClass = '.MangaShowMe'
|
||||
extVersionCode = 11
|
||||
extClass = '.MSMFactory'
|
||||
extVersionCode = 12
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,44 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.mangashowme
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import okhttp3.Response
|
||||
|
||||
|
||||
/*
|
||||
* Source Factory for before-Migration
|
||||
*
|
||||
* I will remove this and only use ManaMoa class after 1.2.15.
|
||||
* This is just helper who uses =<1.2.11 before.
|
||||
*
|
||||
*/
|
||||
class MSMFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
ManaMoa(),
|
||||
MSMDeprecated()
|
||||
)
|
||||
}
|
||||
|
||||
class MSMDeprecated : HttpSource() {
|
||||
override val name = "MangaShow.Me"
|
||||
override val baseUrl: String = "https://Depricated._Need.Source.Migration.to.ManaMoa.net"
|
||||
override val lang: String = "ko"
|
||||
override val supportsLatest = false
|
||||
|
||||
override fun chapterListParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun popularMangaRequest(page: Int) = throw Exception(NEED_MIGRATION)
|
||||
override fun popularMangaParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception(NEED_MIGRATION)
|
||||
override fun searchMangaParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun latestUpdatesRequest(page: Int) = throw Exception(NEED_MIGRATION)
|
||||
override fun latestUpdatesParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun mangaDetailsParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun imageUrlParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
override fun pageListParse(response: Response) = throw Exception(NEED_MIGRATION)
|
||||
|
||||
companion object {
|
||||
const val NEED_MIGRATION = "Deprecated: Use 'ManaMoa' instead.\nSource migration is on 'My Library' -> three dots -> 'Source migration'"
|
||||
}
|
||||
}
|
|
@ -73,9 +73,10 @@ internal class ImageDecoderInterceptor : Interceptor {
|
|||
*
|
||||
* MIT License
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2019 junheah
|
||||
*/
|
||||
private fun imageDecoder(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 = ManaMoa.V1_CX, _CY: Int = ManaMoa.V1_CY): Bitmap {
|
||||
if (view_cnt == 0) return input
|
||||
val viewCnt = view_cnt / 10
|
||||
var CX = _CX
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.mangashowme
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
internal class ImageUrlHandlerInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response = RequestHandler(chain).run()
|
||||
}
|
||||
|
||||
private class RequestHandler(val chain: Interceptor.Chain) {
|
||||
val req = chain.request()
|
||||
val origUrl = req.url().toString()
|
||||
|
||||
fun run(): Response {
|
||||
// only for image Request
|
||||
if (req.header("ImageRequest") != "1") return chain.proceed(req)
|
||||
|
||||
val secondUrl = req.header("SecondUrlToRequest")
|
||||
|
||||
val res = getRequest(origUrl)
|
||||
return if (!isSuccess(res) && secondUrl != null) {
|
||||
getRequest(secondUrl)
|
||||
} else res
|
||||
}
|
||||
|
||||
private fun isSuccess(res: Response): Boolean {
|
||||
val length = res.header("content-length")?.toInt() ?: 0
|
||||
return !(!res.isSuccessful || length < 50000)
|
||||
}
|
||||
|
||||
private fun getRequest(url: String): Response = when {
|
||||
"filecdn.xyz" in url || "chickencdn.info" in url
|
||||
-> ownCDNRequestHandler(url)
|
||||
else -> outsideRequestHandler(url)
|
||||
}
|
||||
|
||||
private fun ownCDNRequestHandler(url: String): Response {
|
||||
val res = proceedRequest(url)
|
||||
return if (!isSuccess(res)) {
|
||||
proceedRequest(url.replace("img.", "s3.")) // s3
|
||||
} else res
|
||||
}
|
||||
|
||||
private fun outsideRequestHandler(url: String): Response {
|
||||
val outUrl = url.substringBefore("?quick")
|
||||
return proceedRequest(outUrl)
|
||||
}
|
||||
|
||||
private fun proceedRequest(url: String): Response = chain.proceed(
|
||||
req.newBuilder()!!
|
||||
.url(url)
|
||||
.removeHeader("ImageRequest")
|
||||
.removeHeader("ImageDecodeRequest")
|
||||
.removeHeader("SecondUrlToRequest")
|
||||
.build()!!
|
||||
)
|
||||
}
|
|
@ -26,13 +26,17 @@ import java.util.*
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* MangaShow.Me Source
|
||||
* ManaMoa Source
|
||||
*
|
||||
* Originally it was mangashow.me extension but they changed site structure widely.
|
||||
* so I moved to new name for treating as new source.
|
||||
* Users who uses =<1.2.11 need to migrate sources. starts 1.2.12
|
||||
*
|
||||
* PS. There's no Popular section. It's just a list of manga. Also not latest updates.
|
||||
* `manga_list` returns latest 'added' manga. not a chapter updates.
|
||||
**/
|
||||
class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
||||
override val name = "MangaShow.Me"
|
||||
class ManaMoa : ConfigurableSource, ParsedHttpSource() {
|
||||
override val name = "ManaMoa"
|
||||
private val defaultBaseUrl = "https://manamoa3.net"
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
override val lang: String = "ko"
|
||||
|
@ -43,52 +47,7 @@ class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
|||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.addInterceptor(ImageDecoderInterceptor())
|
||||
.addInterceptor { chain ->
|
||||
val req = chain.request()
|
||||
|
||||
// only for image Request
|
||||
val isFileCdn = !req.url().host().contains(".filecdn.xyz")
|
||||
if (!req.url().toString().endsWith("?quick")) return@addInterceptor chain.proceed(req)
|
||||
|
||||
val secondUrl = req.header("SecondUrlToRequest")
|
||||
|
||||
fun get(flag: Int = 0): Request {
|
||||
val url = if (isFileCdn) {
|
||||
when (flag) {
|
||||
1 -> req.url().toString().replace("img.", "s3.")
|
||||
else -> req.url().toString()
|
||||
}
|
||||
} else {
|
||||
when (flag) {
|
||||
1 -> secondUrl!!
|
||||
2 -> secondUrl!!.replace("img.", "s3.")
|
||||
else -> req.url().toString().substringBefore("?quick")
|
||||
}
|
||||
}
|
||||
|
||||
return req.newBuilder()!!.url(url)
|
||||
.removeHeader("ImageDecodeRequest")
|
||||
.removeHeader("SecondUrlToRequest")
|
||||
.build()!!
|
||||
}
|
||||
|
||||
val res = chain.proceed(get())
|
||||
|
||||
if (isFileCdn) {
|
||||
val length = res.header("content-length")
|
||||
if (length == null || length.toInt() < 50000) {
|
||||
chain.proceed(get(1)) // s3
|
||||
} else res
|
||||
} else {
|
||||
if (!res.isSuccessful && secondUrl != null) {
|
||||
val fallbackRes = chain.proceed(get(1)) // img filecdn
|
||||
val fallbackLength = fallbackRes.header("content-length")
|
||||
if (fallbackLength == null || fallbackLength.toInt() < 50000) {
|
||||
chain.proceed(get(2)) // s3
|
||||
} else fallbackRes
|
||||
} else res
|
||||
}
|
||||
}
|
||||
.addInterceptor(ImageUrlHandlerInterceptor())
|
||||
.build()!!
|
||||
|
||||
override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row"
|
||||
|
@ -98,7 +57,7 @@ class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
|||
val titleElement = element.select(".manga-subject > a").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.url = urlTitleEscape(linkElement.attr("href"))
|
||||
manga.url = linkElement.attr("href")
|
||||
manga.title = titleElement.text().trim()
|
||||
manga.thumbnail_url = urlFinder(element.select(".img-wrap-back").attr("style"))
|
||||
return manga
|
||||
|
@ -244,21 +203,15 @@ class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
|||
|
||||
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 imageUrls1.length())
|
||||
.map {
|
||||
imageUrls1.getString(it) + try {
|
||||
"!!${imageUrls.getString(it)}?quick"
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
(0 until imageUrls.length())
|
||||
.map {
|
||||
imageUrls.getString(it) + try {
|
||||
"!!${imageUrls1.getString(it)}?quick"
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
.forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) }
|
||||
}
|
||||
}
|
||||
.forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
@ -284,7 +237,7 @@ class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
|||
builder.build()!!
|
||||
} catch (_: Exception) {
|
||||
headers
|
||||
}
|
||||
}.newBuilder()!!.add("ImageRequest", "1").build()!!
|
||||
|
||||
return GET(page.imageUrl!!, requestHeaders)
|
||||
}
|
||||
|
@ -306,14 +259,6 @@ class MangaShowMe : ConfigurableSource, ParsedHttpSource() {
|
|||
return style.substringAfter("background-image:url(").substringBefore(")")
|
||||
}
|
||||
|
||||
// Some title contains `&` and `#` which can cause a error.
|
||||
private fun urlTitleEscape(title: String): String {
|
||||
val url = title.split("&manga_name=")
|
||||
return "${url[0]}&manga_name=" +
|
||||
url[1].replace("&", "%26").replace("#", "%23")
|
||||
}
|
||||
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Naver Comic'
|
||||
pkgNameSuffix = 'ko.navercomic'
|
||||
extClass = '.NaverComicFactory'
|
||||
extVersionCode = 1
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 49 KiB |
|
@ -0,0 +1,74 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.navercomic
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class NaverWebtoon : NaverComicBase("webtoon") {
|
||||
override val name = "Naver Webtoon"
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/$mType/weekday.nhn")
|
||||
override fun popularMangaSelector() = ".list_area.daily_all .col ul > li"
|
||||
override fun popularMangaNextPageSelector() = null
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val thumb = element.select("div.thumb img").first().attr("src")
|
||||
val title = element.select("a.title").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.url = title.attr("href").substringBefore("&week")
|
||||
manga.title = title.text().trim()
|
||||
manga.thumbnail_url = thumb
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/$mType/weekday.nhn?order=Update")
|
||||
override fun latestUpdatesSelector() = ".list_area.daily_all .col.col_selected ul > li"
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
class NaverBestChallenge : NaverComicChallengeBase("bestChallenge") {
|
||||
override val name = "Naver Webtoon Best Challenge"
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn")
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn?m=main&order=Update")
|
||||
}
|
||||
|
||||
class NaverChallenge : NaverComicChallengeBase("challenge") {
|
||||
override val name = "Naver Webtoon Challenge"
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn")
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn?m=list&order=Update")
|
||||
|
||||
// Need to override again because there's no mobile page.
|
||||
override fun chapterPagedListRequest(manga: SManga, page: Int): Request {
|
||||
return GET("$baseUrl${manga.url}&page=$page")
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = ".viewList > tbody > tr:not([class])"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val nameElement = element.select("td.title > a").first()
|
||||
val rawName = nameElement.text().trim()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.url = nameElement.attr("src")
|
||||
chapter.chapter_number = parseChapterNumber(rawName)
|
||||
chapter.name = rawName
|
||||
chapter.date_upload = parseChapterDate(element.select("td.num").last().text().trim())
|
||||
return chapter
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return try {
|
||||
SimpleDateFormat("yyyy.MM.dd").parse(date).time
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.navercomic
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
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.ParsedHttpSource
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
abstract class NaverComicBase(protected val mType: String) : ParsedHttpSource() {
|
||||
override val lang: String = "ko"
|
||||
override val baseUrl: String = "https://comic.naver.com"
|
||||
private val mobileUrl = "https://m.comic.naver.com"
|
||||
override val supportsLatest = true
|
||||
override val client: OkHttpClient = network.client
|
||||
|
||||
private val mobileHeaders = super.headersBuilder()
|
||||
.add("Referer", mobileUrl)
|
||||
.build()
|
||||
|
||||
override fun searchMangaSelector() = ".resultList > li h5 > a"
|
||||
override fun searchMangaNextPageSelector() = ".paginate a.next"
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("search.nhn?m=$mType&keyword=$query&type=title&page=$page")
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val url = element.attr("href").substringBefore("&week").substringBefore("&listPage=")
|
||||
val manga = SManga.create()
|
||||
manga.url = url
|
||||
manga.title = element.text().trim()
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "#ct > .toon_lst.lst2 > li > div a"
|
||||
|
||||
// Need to override because the chapter list is paginated.
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = fetchChapterList(manga, 1)
|
||||
|
||||
private fun fetchChapterList(manga: SManga, page: Int,
|
||||
pastChapters: List<SChapter> = emptyList()): Observable<List<SChapter>> {
|
||||
val chapters = pastChapters.toMutableList()
|
||||
fun isSamePage(list: List<SChapter>): Boolean = try {
|
||||
chapters.last().url == list.last().url
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
return fetchChapterListPage(manga, page)
|
||||
.flatMap {
|
||||
if (isSamePage(it)) {
|
||||
Observable.just(chapters)
|
||||
} else {
|
||||
chapters += it
|
||||
fetchChapterList(manga, page + 1, chapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchChapterListPage(manga: SManga, page: Int): Observable<List<SChapter>> {
|
||||
return client.newCall(chapterPagedListRequest(manga, page))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
chapterListParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
return chapterPagedListRequest(manga, 1)
|
||||
}
|
||||
|
||||
open fun chapterPagedListRequest(manga: SManga, page: Int): Request {
|
||||
return GET("$mobileUrl${manga.url}&page=$page", mobileHeaders)
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val rawName = element.select(".toon_name > strong").last().ownText()
|
||||
val url = element.attr("href").substringBefore("&week").substringBefore("&listPage")
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.url = url
|
||||
chapter.chapter_number = parseChapterNumber(rawName)
|
||||
chapter.name = rawName
|
||||
chapter.date_upload = parseChapterDate(element.select(".toon_detail_info .if1").last().text().trim())
|
||||
return chapter
|
||||
}
|
||||
|
||||
protected fun parseChapterNumber(name: String): Float {
|
||||
try {
|
||||
if (name.contains("[단편]")) return 1f
|
||||
// `특별` means `Special`, so It can be buggy. so pad `편`(Chapter) to prevent false return
|
||||
if (name.contains("번외") || name.contains("특별편")) return -2f
|
||||
val regex = Regex("([0-9]+)(?:[-.]([0-9]+))?(?:화)")
|
||||
val (ch_primal, ch_second) = regex.find(name)!!.destructured
|
||||
return (ch_primal + if (ch_second.isBlank()) "" else ".$ch_second").toFloatOrNull() ?: -1f
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return -1f
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return try {
|
||||
SimpleDateFormat("YY.MM.dd").parse(date).time
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val element = document.select(".comicinfo")
|
||||
val titleElement = element.select(".detail > h2")
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.title = titleElement.first().ownText().trim()
|
||||
manga.author = titleElement.select("span").text().trim()
|
||||
manga.description = document.select(".comicinfo > p").text().trim()
|
||||
manga.thumbnail_url = element.select(".thumb > a > img").last().attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
try {
|
||||
document.select(".wt_viewer img")
|
||||
.map {
|
||||
it.attr("src")
|
||||
}
|
||||
.forEach {
|
||||
pages.add(Page(pages.size, "", it))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
//We are able to get the image URL directly from the page list
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!")
|
||||
|
||||
override fun getFilterList() = FilterList()
|
||||
}
|
||||
|
||||
abstract class NaverComicChallengeBase(mType: String) : NaverComicBase(mType) {
|
||||
override fun popularMangaSelector() = ".weekchallengeBox tbody td:not([class])"
|
||||
override fun popularMangaNextPageSelector(): String? = ".paginate .page_wrap a.next"
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val thumb = element.select("a img").first().attr("src")
|
||||
val title = element.select(".challengeTitle a").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.url = title.attr("href").substringBefore("&week")
|
||||
manga.title = title.text().trim()
|
||||
manga.thumbnail_url = thumb
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.navercomic
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class NaverComicFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
NaverWebtoon(),
|
||||
NaverBestChallenge(),
|
||||
NaverChallenge()
|
||||
)
|
||||
}
|
|
@ -5,8 +5,13 @@ ext {
|
|||
appName = 'Tachiyomi: NewToki'
|
||||
pkgNameSuffix = 'ko.newtoki'
|
||||
extClass = '.NewTokiFactory'
|
||||
extVersionCode = 7
|
||||
extVersionCode = 8
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':preference-stub')
|
||||
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
package eu.kanade.tachiyomi.extension.ko.newtoki
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.support.v7.preference.EditTextPreference
|
||||
import android.support.v7.preference.PreferenceScreen
|
||||
import android.widget.Toast
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
|
@ -10,13 +17,16 @@ import okhttp3.Request
|
|||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* NewToki Source
|
||||
**/
|
||||
open class NewToki(override val name: String, override val baseUrl: String, private val boardName: String) : ParsedHttpSource() {
|
||||
open class NewToki(override val name: String, private val defaultBaseUrl: String, private val boardName: String) : ConfigurableSource, ParsedHttpSource() {
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
override val lang: String = "ko"
|
||||
override val supportsLatest = true
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
@ -183,4 +193,41 @@ open class NewToki(override val name: String, override val baseUrl: String, priv
|
|||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!")
|
||||
|
||||
override fun getFilterList() = FilterList()
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val baseUrlPref = EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF_TITLE
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
this.setDefaultValue(defaultBaseUrl)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: $defaultBaseUrl"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit()
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
res
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(baseUrlPref)
|
||||
}
|
||||
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)
|
||||
|
||||
companion object {
|
||||
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_NAME}"
|
||||
private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Update extension will erase this setting."
|
||||
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.source.model.FilterList
|
|||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
private const val baseDomain = "newtoki10"
|
||||
private const val baseDomain = "newtoki12"
|
||||
|
||||
class NewTokiFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
|
|