diff --git a/src/ko/mangashowme/AndroidManifest.xml b/src/ko/mangashowme/AndroidManifest.xml deleted file mode 100644 index 6bb3d95eb..000000000 --- a/src/ko/mangashowme/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ko/mangashowme/build.gradle b/src/ko/mangashowme/build.gradle deleted file mode 100644 index 575279672..000000000 --- a/src/ko/mangashowme/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManaMoa' - pkgNameSuffix = 'ko.mangashowme' - extClass = '.ManaMoa' - extVersionCode = 20 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ko/mangashowme/res/mipmap-hdpi/ic_launcher.png b/src/ko/mangashowme/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9a3757007..000000000 Binary files a/src/ko/mangashowme/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/mangashowme/res/mipmap-mdpi/ic_launcher.png b/src/ko/mangashowme/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 86639e571..000000000 Binary files a/src/ko/mangashowme/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/mangashowme/res/mipmap-xhdpi/ic_launcher.png b/src/ko/mangashowme/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d6f545905..000000000 Binary files a/src/ko/mangashowme/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/mangashowme/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/mangashowme/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index dccac2012..000000000 Binary files a/src/ko/mangashowme/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/mangashowme/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/mangashowme/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d8eaadfc0..000000000 Binary files a/src/ko/mangashowme/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/mangashowme/res/web_hi_res_512.png b/src/ko/mangashowme/res/web_hi_res_512.png deleted file mode 100644 index 9357e66dd..000000000 Binary files a/src/ko/mangashowme/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMCDNUrlHandler.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMCDNUrlHandler.kt deleted file mode 100644 index d9b59fe11..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMCDNUrlHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -import org.json.JSONArray - -internal class MMCDNUrlHandler(scripts: String) { - private val domains = JSONArray("[${scripts.substringBetween("var cdn_domains = [", "];")}]") - private val chapter = scripts.substringBetween("var chapter = ", ";") - .toIntOrNull() ?: 0 - - fun replace(array: JSONArray): List { - return (0 until array.length()) - .map { - val cdn: String = domains.get((chapter + 4 * it) % domains.length()) as String - (array.get(it) as String) - .replace("cdntigermask.xyz", cdn) - .replace("cdnmadmax.xyz", cdn) - .replace("filecdn.xyz", cdn) - } - } -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt deleted file mode 100644 index 370accd0e..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt +++ /dev/null @@ -1,181 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import okhttp3.HttpUrl -import okhttp3.Request - -// TODO: Completely Implement/Update Filters(Genre/Artist). -private class TextField(name: String, val key: String) : Filter.Text(name) - -private class SearchCheckBox(name: String, val id: String = name) : Filter.CheckBox(name) - -private class SearchMatch : Filter.Select("Match", arrayOf("AND", "OR")) -private class SearchType : Filter.Select("Type", arrayOf("Title", "Artist")) -private class SearchGenresList(genres: List) : Filter.Group("Genres", genres) -private class SearchNamingList : Filter.Select("Naming", searchNaming()) -private class SearchStatusList : Filter.Select("Status", searchStatus()) -private class SearchOrderList : Filter.Select("Order", order()) - -// [`"Not Set"`, ...[...document.querySelectorAll(".categories ul[data-type='1'] li")].map((el, i) => `"${el.innerText.trim()}"`)].join(',\n') -private fun searchNaming() = arrayOf( - "Not Set", - "ㄱ", - "ㄲ", - "ㄴ", - "ㄷ", - "ㄸ", - "ㄹ", - "ㅁ", - "ㅂ", - "ㅃ", - "ㅅ", - "ㅆ", - "ㅇ", - "ㅈ", - "ㅉ", - "ㅊ", - "ㅋ", - "ㅌ", - "ㅍ", - "ㅎ", - "A-Z", - "0-9" -) - -// [`"Not Set"`, ...[...document.querySelectorAll(".categories ul[data-type='2'] li")].map((el, i) => `"${el.innerText.trim()}"`)].join(',\n') -private fun searchStatus() = arrayOf( - "Not Set", - "주간", - "격주", - "월간", - "격월/비정기", - "단편", - "단행본", - "완결" -) - -// [...document.querySelectorAll(".categories ul[data-type='2'] li")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') -private fun order() = arrayOf( - "Recent", - "Likes", - "Popular", - "Comments", - "Bookmarks" -) - -// [...document.querySelectorAll(".categories ul[data-type='3'] li")].map((el, i) => `SearchCheckBox("${el.innerText.trim()}")`).join(',\n') -private fun searchGenres() = listOf( - SearchCheckBox("17"), - SearchCheckBox("BL"), - SearchCheckBox("SF"), - SearchCheckBox("TS"), - SearchCheckBox("개그"), - SearchCheckBox("게임"), - SearchCheckBox("공포"), - SearchCheckBox("도박"), - SearchCheckBox("드라마"), - SearchCheckBox("라노벨"), - SearchCheckBox("러브코미디"), - SearchCheckBox("먹방"), - SearchCheckBox("백합"), - SearchCheckBox("붕탁"), - SearchCheckBox("순정"), - SearchCheckBox("스릴러"), - SearchCheckBox("스포츠"), - SearchCheckBox("시대"), - SearchCheckBox("애니화"), - SearchCheckBox("액션"), - SearchCheckBox("음악"), - SearchCheckBox("이세계"), - SearchCheckBox("일상"), - SearchCheckBox("전생"), - SearchCheckBox("추리"), - SearchCheckBox("판타지"), - SearchCheckBox("학원"), - SearchCheckBox("호러") -) - -fun getFilters() = FilterList( - SearchNamingList(), - SearchStatusList(), - SearchGenresList(searchGenres()), - Filter.Separator(), - SearchType(), - SearchMatch(), - SearchOrderList() -) - -fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: String, filters: FilterList): Request { - var nameFilter: Int? = null - var statusFilter: Int? = null - val genresFilter = mutableListOf() - var matchFilter = 1 - var orderFilter = 0 - var typeFilter = 0 - - filters.forEach { filter -> - when (filter) { - is SearchMatch -> { - matchFilter = filter.state + 1 - } - - is SearchOrderList -> { - orderFilter = filter.state - } - - is SearchType -> { - typeFilter = arrayOf(0, 5)[filter.state] - } - - is SearchNamingList -> { - if (filter.state > 0) { - nameFilter = filter.state - 1 - } - } - - is SearchStatusList -> { - if (filter.state > 0) { - statusFilter = filter.state - } - } - - is SearchGenresList -> { - filter.state.forEach { - if (it.state) { - genresFilter.add(it.id) - } - } - } - } - } - - /* - if (!authorFilter.isNullOrEmpty()) { - Log.println(Log.DEBUG, "TACHI REQUEST", "ARTIST REQU") - return GET("$baseUrl/bbs/page.php?hid=manga_list&search_type=1&sfl=5&_0=$authorFilter&_1=&_2=&_3=&_4=$orderFilter") - } - */ - - if (query.isEmpty() && nameFilter == null && statusFilter == null && orderFilter == 0 && matchFilter == 1 && genresFilter.isEmpty()) { - return GET( - "$baseUrl/bbs/page.php?hid=manga_list" + - if (page > 1) "&page=${page - 1}" else "" - ) - } - - val url = HttpUrl.parse("$baseUrl/bbs/page.php?hid=manga_list")!!.newBuilder() - url.addQueryParameter("search_type", matchFilter.toString()) - url.addQueryParameter("sfl", typeFilter.toString()) - url.addQueryParameter("_0", query) - url.addQueryParameter("_1", nameFilter?.toString() ?: "") - url.addQueryParameter("_2", statusFilter?.toString() ?: "") - url.addQueryParameter("_3", genresFilter.joinToString(",")) - url.addQueryParameter("_4", orderFilter.toString()) - if (page > 1) { - url.addQueryParameter("page", "${page - 1}") - } - - return GET(url.toString()) -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt deleted file mode 100644 index 3f251b20a..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt +++ /dev/null @@ -1,171 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.Response -import okhttp3.ResponseBody -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import kotlin.math.cos -import kotlin.math.floor -import kotlin.math.sin -import kotlin.math.tan - -/* - * `v1` means url padding of image host. - * It's not need now, but it remains in this code for sometime. - */ - -internal class ImageDecoder(scripts: String) { - private val cnt = scripts.substringBetween("var view_cnt = ", ";") - .toIntOrNull() ?: 0 - private val chapter = scripts.substringBetween("var chapter = ", ";") - .toIntOrNull() ?: 0 - - fun request(url: String): String { - if (cnt < 10) return url - return "$url??$chapter;$cnt" - } -} - -internal class ImageDecoderInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val req = chain.request() - val newReq = req.newBuilder()!! - .removeHeader("ImageRequest") - .removeHeader("ImageDecodeRequest") - .removeHeader("SecondUrlToRequest") - .build()!! - val response = chain.proceed(newReq) - - val decodeHeader = req.header("ImageDecodeRequest") - - return if (decodeHeader != null) { - try { - val s = decodeHeader.split(";").map { it.toInt() } - - if (s[1] < 10) return response - - val res = response.body()!!.byteStream().use { - decodeImageRequest(it, s[0], s[1]) - } - - val rb = ResponseBody.create(MediaType.parse("image/png"), res) - response.newBuilder().body(rb).build() - } catch (e: Exception) { - e.printStackTrace() - throw IOException("Image decoder failure.", e) - } - } else { - 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() - } - - /* - * `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 imageDecoder(input: Bitmap, chapter: Int, view_cnt: Int, half: Int = 0): Bitmap { - if (view_cnt == 0) return input - val viewCnt = view_cnt / 10 - var cx = ManaMoa.V1_CX - var cy = ManaMoa.V1_CY - - // view_cnt / 10 > 30000 ? (this._CX = 1, this._CY = 6) : view_cnt / 10 > 20000 ? this._CX = 1 : view_cnt / 10 > 10000 && (this._CY = 1) - // DO NOT (AUTOMATICALLY) REPLACE TO when USING IDEA. seems it doesn't detect correct condition - if (viewCnt > 30000) { - cx = 1 - cy = 6 - } else if (viewCnt > 20000) { - cx = 1 - } else if (viewCnt > 10000) { - cy = 1 - } - - // decode image - val order = Array(cx * cy) { IntArray(2) } - val oSize = order.size - 1 - - for (i in 0..oSize) { - order[i][0] = i - order[i][1] = decoderRandom(chapter, viewCnt, i) - } - - java.util.Arrays.sort(order) { a, b -> a[1].toDouble().compareTo(b[1].toDouble()) } - - // create new bitmap - val outputWidth = if (half == 0) input.width else input.width / 2 - val output = Bitmap.createBitmap(outputWidth, input.height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(output) - - val rowWidth = input.width / cx - val rowHeight = input.height / cy - - for (i in 0..oSize) { - val o = order[i] - val ox = i % cx - val oy = i / cx - val tx = o[0] % cx - val ty = o[0] / cx - val sx = if (half == 2) -input.width / 2 else 0 - - val srcX = ox * rowWidth - val srcY = oy * rowHeight - val destX = (tx * rowWidth) + sx - val destY = ty * rowHeight - - canvas.drawBitmap( - input, - Rect(srcX, srcY, srcX + rowWidth, srcY + rowHeight), - Rect(destX, destY, destX + rowWidth, destY + rowHeight), - null - ) - } - - return output - } - - /* - * `decodeRandom` 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 decoderRandom(chapter: Int, view_cnt: Int, index: Int): Int { - if (chapter < 554714) { - val x = 10000 * sin((view_cnt + index).toDouble()) - return floor(100000 * (x - floor(x))).toInt() - } - - val seed = view_cnt + index + 1 - val t = 100 * sin((10 * seed).toDouble()) - val n = 1000 * cos((13 * seed).toDouble()) - val a = 10000 * tan((14 * seed).toDouble()) - - return ( - floor(100 * (t - floor(t))) + - floor(1000 * (n - floor(n))) + - floor(10000 * (a - floor(a))) - ).toInt() - } -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMmageUrlHandler.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMmageUrlHandler.kt deleted file mode 100644 index aec9d7b2f..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMmageUrlHandler.kt +++ /dev/null @@ -1,61 +0,0 @@ -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 < ManaMoa.MINIMUM_IMAGE_SIZE) - } - - private fun getRequest(url: String): Response = when { - ".xyz/" in url -> ownCDNRequestHandler(url) - else -> outsideRequestHandler(url) - } - - private fun ownCDNRequestHandler(url: String): Response { - val res = proceedRequest(url) - return if (!isSuccess(res)) { - val s3url = if (url.contains("img.")) { - url.replace("img.", "s3.") - } else { - url.replace("://", "://s3.") - } - proceedRequest(s3url) // 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()!! - ) -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt deleted file mode 100644 index 7e1130874..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt +++ /dev/null @@ -1,531 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Application -import android.content.SharedPreferences -import android.os.Build -import android.support.v7.preference.CheckBoxPreference -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.network.asObservableSuccess -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.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Call -import okhttp3.Callback -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit - -/** - * 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 source. 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 ManaMoa : ConfigurableSource, ParsedHttpSource() { - - override val name = "ManaMoa" - - // This keeps updating: https://twitter.com/manamoa24 - private val defaultBaseUrl = "https://manamoa34.net" - override val baseUrl by lazy { getCurrentBaseUrl() } - - override val lang: String = "ko" - - // Latest updates currently returns duplicate manga as it separates manga into chapters - // But allowing to fetch from chapters with experimental setting. - override val supportsLatest by lazy { getExperimentLatest() } - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(ImageDecoderInterceptor()) - .addInterceptor(ImageUrlHandlerInterceptor()) - .build()!! - - override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row" - - override fun popularMangaFromElement(element: Element): SManga { - val linkElement = element.select("a") - val titleElement = element.select(".manga-subject > a").first() - - val manga = SManga.create() - manga.url = linkElement.attr("href") - manga.title = titleElement.html().trim() - manga.thumbnail_url = urlFinder(element.select(".img-wrap-back").attr("style")) - return manga - } - - 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/bbs/page.php?hid=manga_list" + - if (page > 1) "&page=${page - 1}" else "" - ) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - val hasNextPage = try { - !document.select(popularMangaNextPageSelector()).last().hasClass("active") - } catch (_: Exception) { - false - } - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaNextPageSelector() = popularMangaSelector() - override fun searchMangaParse(response: Response) = popularMangaParse(response) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - val urlPath = "/bbs/page.php?hid=manga_detail&manga_id=$realQuery" - client.newCall(GET("$baseUrl$urlPath")) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = urlPath - MangasPage(listOf(details), false) - } - } else super.fetchSearchManga(page, query, filters) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - searchComplexFilterMangaRequestBuilder(baseUrl, page, query, filters) - - override fun mangaDetailsParse(document: Document): SManga { - val info = document.select("div.left-info").first() - val thumbnailElement = info.select("div.manga-thumbnail").first() - val publishTypeText = thumbnailElement.select("a.publish_type").trimText("Unknown") - val authorText = thumbnailElement.select("a.author").trimText() - - val mangaStatus = info.select("div.recommend") - val mangaLike = mangaStatus.select(".fa-thumbs-up").trimText("0") - // val mangaViews = trimElementText(mangaStatus.select(".fa-smile-o"), "0") - val mangaComments = mangaStatus.select(".fa-comment").trimText("0") - val mangaBookmarks = info.select(".fa-bookmark").trimText("0") - val mangaChaptersLike = mangaElementsSum(document.select(".title i.fa.fa-thumbs-up > span")) - val mangaChaptersComments = mangaElementsSum(document.select(".title i.fa.fa-comment > span")) - - val genres = mutableListOf() - document.select("div.left-info div.information > .manga-tags > a.tag").forEach { - genres.add(it.text()) - } - - val manga = SManga.create() - manga.title = info.select("div.red.title").html().trim() - // They using background-image style tag for cover. extract url from style attribute. - manga.thumbnail_url = urlFinder(thumbnailElement.attr("style")) - manga.description = - "\uD83D\uDCDD: $publishTypeText\n" + - "👍: $mangaLike ($mangaChaptersLike)\n" + - // "\uD83D\uDD0D: $mangaViews\n" + - "\uD83D\uDCAC: $mangaComments ($mangaChaptersComments)\n" + - "\uD83D\uDD16: $mangaBookmarks" - manga.author = authorText - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(publishTypeText) - return manga - } - - private fun parseStatus(status: String) = when (status.trim()) { - "주간", "격주", "월간", "격월/비정기", "단행본" -> SManga.ONGOING - "단편", "완결" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun mangaElementsSum(element: Elements?): String { - if (element.isNullOrEmpty()) return "0" - return try { - String.format( - "%,d", - element.map { - it.text().toInt() - }.sum() - ) - } catch (_: Exception) { - "0" - } - } - - override fun chapterListSelector() = "div.manga-detail-list > div.chapter-list > .slot" - - override fun chapterFromElement(element: Element): SChapter { - val linkElement = element.select("a") - val rawName = linkElement.select("div.title").last() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(linkElement.attr("href")) - chapter.chapter_number = parseChapterNumber(rawName.text()) - chapter.name = rawName.html().substringBefore(" calendar.get(Calendar.MONTH) + 1) // Before December now, // and Retrieved month is December == 2018. - currYear - 1 else currYear - SimpleDateFormat("yyyy-MM-dd").parse("$year-$date")?.time ?: 0L - } catch (e: Exception) { - e.printStackTrace() - 0 - } - } - - // They are using full url in every links. - // There's possibility to using another domain for serve manga(s). Like marumaru. - // override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - - try { - val element = document.toString() - val cdnHandler = MMCDNUrlHandler(element) - - val imageUrl = element.substringBetween("var img_list = [", "];") - val imageUrls = cdnHandler.replace(JSONArray("[$imageUrl]")) - - val imageUrl1 = element.substringBetween("var img_list1 = [", "];") - val imageUrls1 = cdnHandler.replace(JSONArray("[$imageUrl1]")) - - val decoder = ImageDecoder(element) - - (imageUrls.indices) - .map { - imageUrls[it] + try { - "!!${imageUrls1[it]}" - } catch (_: Exception) { - "" - } - } - .forEach { pages.add(Page(pages.size, decoder.request(it), it.substringBefore("!!"))) } - } catch (e: Exception) { - e.printStackTrace() - } - - 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 - }.newBuilder()!!.add("ImageRequest", "1").build()!! - - return GET(page.imageUrl!!, requestHeaders) - } - - // Latest not supported - override fun latestUpdatesSelector() = ".post-row > div.media.post-list" - - override fun latestUpdatesFromElement(element: Element): SManga { - val linkElement = element.select("a.btn-primary") - val rawTitle = element.select(".post-subject > a").first().ownText() - - // TODO: Make Clear Regex. - val chapterRegex = Regex("""((?:\s+)(?:(?:(?:[0-9]+권)?(?:[0-9]+부)?(?:[0-9]*?시즌[0-9]*?)?)?(?:\s*)(?:(?:[0-9]+)(?:[-.](?:[0-9]+))?)?(?:\s*[~,]\s*)?(?:[0-9]+)(?:[-.](?:[0-9]+))?)(?:화))""") - val title = rawTitle.trim().replace(chapterRegex, "") - // val regexSpecialChapter = Regex("(부록|단편|외전|.+편)") - // val lastTitleWord = excludeChapterTitle.split(" ").last() - // val title = excludeChapterTitle.replace(lastTitleWord, lastTitleWord.replace(regexSpecialChapter, "")) - - val manga = SManga.create() - manga.url = linkElement.attr("href") - manga.title = title - manga.thumbnail_url = element.select(".img-item > img").attr("src") - manga.initialized = false - return manga - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/bbs/board.php?bo_table=manga" + if (page > 1) "&page=$page" else "") - override fun latestUpdatesNextPageSelector() = "ul.pagination > li:not(.disabled)" - - // 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!") - - private fun urlFinder(style: String): String { - // val regex = Regex("(https?:)?//[-a-zA-Z0-9@:%._\\\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\\\+.~#?&/=]*)") - // return regex.find(style)!!.value - return style.substringAfter("background-image:url(").substringBefore(")") - } - - private fun Elements.trimText(fallback: String = ""): String { - return this.text()?.trim()?.takeUnless { it.isBlank() } ?: fallback - } - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val baseUrlPref = androidx.preference.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 - } - } - } - - val autoFetchUrlPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = AUTOFETCH_URL_PREF_TITLE - title = AUTOFETCH_URL_PREF_TITLE - summary = AUTOFETCH_URL_PREF_SUMMARY - this.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(AUTOFETCH_URL_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestExperimentPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_PREF_TITLE - title = EXPERIMENTAL_LATEST_PREF_TITLE - summary = EXPERIMENTAL_LATEST_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - screen.addPreference(autoFetchUrlPref) - screen.addPreference(latestExperimentPref) - } - - 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 - } - } - } - - val autoFetchUrlPref = CheckBoxPreference(screen.context).apply { - key = AUTOFETCH_URL_PREF_TITLE - title = AUTOFETCH_URL_PREF_TITLE - summary = AUTOFETCH_URL_PREF_SUMMARY - this.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(AUTOFETCH_URL_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestExperimentPref = CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_PREF_TITLE - title = EXPERIMENTAL_LATEST_PREF_TITLE - summary = EXPERIMENTAL_LATEST_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - screen.addPreference(autoFetchUrlPref) - screen.addPreference(latestExperimentPref) - } - - private fun getCurrentBaseUrl(): String { - val prefBaseUrl = getPrefBaseUrl() - if (!preferences.getBoolean(AUTOFETCH_URL_PREF, false)) { - return prefBaseUrl - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - @TargetApi(Build.VERSION_CODES.O) - class CallbackFuture : CompletableFuture(), Callback { - override fun onResponse(call: Call?, response: Response?) { - super.complete(response) - } - - override fun onFailure(call: Call?, e: IOException?) { - super.completeExceptionally(e) - } - } - - val request: Request = Request.Builder().get() - // .url("https://mnmnmnmnm.xyz/") - .url("http://52.74.159.59") - .build() - - val future = CallbackFuture() - network.client - // .newBuilder() - // .addInterceptor(DDOSGuardInterceptor()) - // .build()!! - .newCall(request).enqueue(future) - - val response = future.get()!! - return "https://${response.request().url().host()}" - // val code = response.body().toString().substringBetween("manamoa", ".net") - // return "https://manamoa$code.net" - } catch (e: Exception) { - e.printStackTrace() - return prefBaseUrl - } - } else { - return prefBaseUrl - } - } - - private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! - private fun getExperimentLatest(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_PREF, false) - - override fun getFilterList() = getFilters() - - companion object { - // Setting: Override BaseUrl - 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." - // Setting: Fetch Domain - private const val AUTOFETCH_URL_PREF_TITLE = "Automatically fetch new domain" - private const val AUTOFETCH_URL_PREF = "autoFetchNewUrl" - private const val AUTOFETCH_URL_PREF_SUMMARY = - "Experimental, May cause Tachiyomi *very* unstable.\n" + - "Requires Android Oreo or newer." - - // Setting: Experimental Latest Fetcher - private const val EXPERIMENTAL_LATEST_PREF_TITLE = "Enable Latest (Experimental)" - private const val EXPERIMENTAL_LATEST_PREF = "fetchLatestExperiment" - private const val EXPERIMENTAL_LATEST_PREF_SUMMARY = "Fetch Latest Manga using Latest Chapters. May has duplicates or invalid name." - - private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting." - - // Image Decoder - internal const val V1_CX = 5 - internal const val V1_CY = 5 - - // Url Handler - internal const val MINIMUM_IMAGE_SIZE = 10000 - - // Activity Url Handler - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoaUrlActivity.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoaUrlActivity.kt deleted file mode 100644 index b7b354667..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoaUrlActivity.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class ManaMoaUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val titleid = intent?.data?.getQueryParameter("manga_id") - if (titleid != null && titleid.toInt() > 1) { - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${ManaMoa.PREFIX_ID_SEARCH}$titleid") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("ManaMoaUrlActivity", e.toString()) - } - } else { - Log.e("ManaMoaUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/Utils.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/Utils.kt deleted file mode 100644 index e86575c92..000000000 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/Utils.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.mangashowme - -internal fun String.substringBetween(prefix: String, suffix: String): String = { - this.substringAfter(prefix).substringBefore(suffix) -}() diff --git a/src/ko/newtoki/build.gradle b/src/ko/newtoki/build.gradle index 2eb4d5f4f..0b3eb6c93 100644 --- a/src/ko/newtoki/build.gradle +++ b/src/ko/newtoki/build.gradle @@ -2,10 +2,10 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' ext { - extName = 'NewToki' + extName = 'NewToki / ManaToki(ManaMoa)' pkgNameSuffix = 'ko.newtoki' extClass = '.NewTokiFactory' - extVersionCode = 15 + extVersionCode = 16 libVersion = '1.2' } diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt index ea85757c6..289b86f80 100644 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt +++ b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.ko.newtoki import android.annotation.SuppressLint import android.app.Application import android.content.SharedPreferences +import android.support.v7.preference.CheckBoxPreference import android.support.v7.preference.EditTextPreference import android.support.v7.preference.PreferenceScreen import android.widget.Toast @@ -20,11 +21,14 @@ import eu.kanade.tachiyomi.util.asJsoup import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.net.URI +import java.net.URISyntaxException import java.text.SimpleDateFormat import java.util.Calendar @@ -82,14 +86,35 @@ open class NewToki(override val name: String, private val defaultBaseUrl: String client.newCall(GET("$baseUrl$urlPath")) .asObservableSuccess() .map { response -> - // TODO: Fix the error which caused by shares id field with detail and each chapters in the source - val details = mangaDetailsParse(response.asJsoup()) - details.url = urlPath - MangasPage(listOf(details), false) + // the id is matches any of 'post' from their CMS board. + // Includes Manga Details Page, Chapters, Comments, and etcs... + actualMangaParseById(urlPath, response) } } else super.fetchSearchManga(page, query, filters) } + private fun actualMangaParseById(urlPath: String, response: Response): MangasPage { + val document = response.asJsoup() + + // Only exists on detail page. + val firstChapterButton = document.select("tr > th > button.btn-blue").first() + // only exists on chapter with proper manga detail page. + val fullListButton = document.select(".comic-navbar .toon-nav a").last() + + val list: List = if (firstChapterButton?.text()?.contains("첫회보기") ?: false) { // Check this page is detail page + val details = mangaDetailsParse(document) + details.url = urlPath + listOf(details) + } else if (fullListButton?.text()?.contains("전체목록") ?: false) { // Check this page is chapter page + val url = fullListButton.attr("abs:href") + val details = mangaDetailsParse(client.newCall(GET(url)).execute()) + details.url = getUrlPath(url) + listOf(details) + } else emptyList() + + return MangasPage(list, false) + } + override fun mangaDetailsParse(document: Document): SManga { val info = document.select("div.view-title > .view-content").first() val title = document.select("div.view-content > span > b").text() @@ -181,11 +206,18 @@ open class NewToki(override val name: String, private val defaultBaseUrl: String } } + private val htmlDataRegex = Regex("""html_data\+='([^']+)'""") + override fun pageListParse(document: Document): List { - //
-
- optional
-
- optional

- - return document.select("article > div div img") - .filterNot { !it.hasAttr("data-original") || it.attr("data-original").contains("blank.gif") } - .mapIndexed { i, img -> Page(i, "", img.attr("abs:data-original")) } + val script = document.select("script:containsData(html_data)").firstOrNull()?.data() ?: throw Exception("script not found") + + return htmlDataRegex.findAll(script).map { it.groupValues[1] } + .asIterable() + .flatMap { it.split(".") } + .joinToString("") { it.toIntOrNull(16)?.toChar()?.toString() ?: "" } + .let { Jsoup.parse(it) } + .select("img[alt]") + .mapIndexed { i, img -> Page(i, "", if (img.hasAttr("abs:data-original")) img.attr("abs:data-original") else img.attr("abs:content")) } } override fun latestUpdatesSelector() = popularMangaSelector() @@ -224,7 +256,27 @@ open class NewToki(override val name: String, private val defaultBaseUrl: String } } + val latestExperimentPref = androidx.preference.CheckBoxPreference(screen.context).apply { + key = EXPERIMENTAL_LATEST_PREF_TITLE + title = EXPERIMENTAL_LATEST_PREF_TITLE + summary = EXPERIMENTAL_LATEST_PREF_SUMMARY + + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + } + screen.addPreference(baseUrlPref) + if (name == "ManaToki") { + screen.addPreference(latestExperimentPref) + } } override fun setupPreferenceScreen(screen: PreferenceScreen) { @@ -248,10 +300,39 @@ open class NewToki(override val name: String, private val defaultBaseUrl: String } } + val latestExperimentPref = CheckBoxPreference(screen.context).apply { + key = EXPERIMENTAL_LATEST_PREF_TITLE + title = EXPERIMENTAL_LATEST_PREF_TITLE + summary = EXPERIMENTAL_LATEST_PREF_SUMMARY + + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + } + screen.addPreference(baseUrlPref) + if (name == "ManaToki") { + screen.addPreference(latestExperimentPref) + } + } + + protected fun getUrlPath(orig: String): String { + return try { + URI(orig).path + } catch (e: URISyntaxException) { + orig + } } private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! + protected fun getExperimentLatest(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_PREF, false) companion object { private const val BASE_URL_PREF_TITLE = "Override BaseUrl" @@ -259,6 +340,11 @@ open class NewToki(override val name: String, private val defaultBaseUrl: String 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." + // Setting: Experimental Latest Fetcher + private const val EXPERIMENTAL_LATEST_PREF_TITLE = "Enable Latest (Experimental)" + private const val EXPERIMENTAL_LATEST_PREF = "fetchLatestExperiment" + private const val EXPERIMENTAL_LATEST_PREF_SUMMARY = "Fetch Latest Manga using Latest Chapters. May has duplicates, Also requires LOTS OF requests (70 per page)" + const val PREFIX_ID_SEARCH = "id:" } } diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt index 9e1d03fcb..e09c7d36a 100644 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt +++ b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt @@ -5,15 +5,17 @@ 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 eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.CacheControl import okhttp3.HttpUrl import okhttp3.Request -import org.jsoup.Jsoup -import org.jsoup.nodes.Document +import okhttp3.Response import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.concurrent.TimeUnit.DAYS /** * Source changes domain names every few days (e.g. newtoki31.net to newtoki32.net) @@ -35,6 +37,32 @@ class NewTokiFactory : SourceFactory { class NewTokiManga : NewToki("ManaToki", "https://manatoki$domainNumber.net", "comic") { // / ! DO NOT CHANGE THIS ! Only the site name changed from newtoki. override val id by lazy { generateSourceId("NewToki", lang, versionId) } + override val supportsLatest by lazy { getExperimentLatest() } + + // this does 70 request per page.... + override fun latestUpdatesSelector() = ".media.post-list p > a" + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/update?hid=update&page=$page") + override fun latestUpdatesNextPageSelector() = "nav.pg_wrap > .pg > strong" + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + // given cache time to prevent repeated lots of request in latest. + val cacheControl = CacheControl.Builder().maxAge(14, DAYS).maxStale(14, DAYS).build() + val mangas = document.select(latestUpdatesSelector()).map { element -> + val url = element.attr("abs:href") + val manga = mangaDetailsParse(client.newCall(GET(url, cache = cacheControl)).execute()) + manga.url = getUrlPath(url) + manga + } + + val hasNextPage = try { + !document.select(popularMangaNextPageSelector()).text().contains("10") + } catch (_: Exception) { + false + } + + return MangasPage(mangas, hasNextPage) + } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = HttpUrl.parse("$baseUrl/comic" + (if (page > 1) "/p$page" else ""))!!.newBuilder() @@ -151,6 +179,7 @@ class NewTokiManga : NewToki("ManaToki", "https://manatoki$domainNumber.net", "c ) override fun getFilterList() = FilterList( + Filter.Header("Filter can't use with query"), SearchPublishTypeList(), SearchJaumTypeList(), SearchGenreTypeList() @@ -165,7 +194,7 @@ class NewTokiWebtoon : NewToki("NewToki", "https://newtoki$domainNumber.com", "w val url = HttpUrl.parse("$baseUrl/webtoon" + (if (page > 1) "/p$page" else ""))!!.newBuilder() filters.forEach { filter -> when (filter) { - is SearchTypeList -> { + is SearchTargetTypeList -> { if (filter.state > 0) { url.addQueryParameter("toon", filter.values[filter.state]) } @@ -173,31 +202,103 @@ class NewTokiWebtoon : NewToki("NewToki", "https://newtoki$domainNumber.com", "w } } + // Imcompatible with Other Search Parametor if (!query.isBlank()) { url.addQueryParameter("stx", query) + } else { + filters.forEach { filter -> + when (filter) { + is SearchYoilTypeList -> { + if (filter.state > 0) { + url.addQueryParameter("yoil", filter.values[filter.state]) + } + } + + is SearchJaumTypeList -> { + if (filter.state > 0) { + url.addQueryParameter("jaum", filter.values[filter.state]) + } + } + + is SearchGenreTypeList -> { + if (filter.state > 0) { + url.addQueryParameter("tag", filter.values[filter.state]) + } + } + } + } } return GET(url.toString()) } - private val htmlDataRegex = Regex("""html_data\+='([^']+)'""") + private class SearchTargetTypeList : Filter.Select("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰")) - override fun pageListParse(document: Document): List { - val script = document.select("script:containsData(html_data)").firstOrNull()?.data() ?: throw Exception("script not found") + // [...document.querySelectorAll("form.form td")[1].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') + private class SearchYoilTypeList : Filter.Select( + "Day of the Week", + arrayOf( + "전체", + "월", + "화", + "수", + "목", + "금", + "토", + "일", + "열흘" + ) + ) - return htmlDataRegex.findAll(script).map { it.groupValues[1] } - .asIterable() - .flatMap { it.split(".") } - .joinToString("") { it.toIntOrNull(16)?.toChar()?.toString() ?: "" } - .let { Jsoup.parse(it) } - .select("img[alt]") - .mapIndexed { i, img -> Page(i, "", img.attr("abs:data-original")) } - } - - private class SearchTypeList : Filter.Select("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰")) + // [...document.querySelectorAll("form.form td")[2].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') + private class SearchJaumTypeList : Filter.Select( + "Jaum", + arrayOf( + "전체", + "ㄱ", + "ㄴ", + "ㄷ", + "ㄹ", + "ㅁ", + "ㅂ", + "ㅅ", + "ㅇ", + "ㅈ", + "ㅊ", + "ㅋ", + "ㅌ", + "ㅍ", + "ㅎ", + "a-z", + "0-9" + ) + ) + // [...document.querySelectorAll("form.form td")[3].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') + private class SearchGenreTypeList : Filter.Select( + "Genre", + arrayOf( + "전체", + "판타지", + "액션", + "개그", + "미스터리", + "로맨스", + "드라마", + "무협", + "스포츠", + "일상", + "학원", + "성인" + ) + ) override fun getFilterList() = FilterList( - SearchTypeList() + SearchTargetTypeList(), + Filter.Separator(), + Filter.Header("Under 3 Filters can't use with query"), + SearchYoilTypeList(), + SearchJaumTypeList(), + SearchGenreTypeList() ) }