Update NewToki extension v1.2.16 / Remove ManaMoa Extension (#4389)

Update NewToki extension v1.2.16 / Remove ManaMoa Extension
This commit is contained in:
DitFranXX 2020-09-20 07:50:30 +09:00 committed by GitHub
parent 9bfbba7d9d
commit 8143b34bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 215 additions and 1081 deletions

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".ManaMoaUrlActivity"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- Current domain -->
<data
android:scheme="https"
android:host="manamoa28.net"
android:pathPattern="/bbs/page.php"/>
<!-- Future domains -->
<data
android:scheme="https"
android:host="manamoa29.net"
android:pathPattern="/bbs/page.php"/>
<data
android:scheme="https"
android:host="manamoa30.net"
android:pathPattern="/bbs/page.php"/>
<data
android:scheme="https"
android:host="manamoa31.net"
android:pathPattern="/bbs/page.php"/>
<data
android:scheme="https"
android:host="manamoa32.net"
android:pathPattern="/bbs/page.php"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -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<String> {
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)
}
}
}

View File

@ -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<String>("Match", arrayOf("AND", "OR"))
private class SearchType : Filter.Select<String>("Type", arrayOf("Title", "Artist"))
private class SearchGenresList(genres: List<SearchCheckBox>) : Filter.Group<SearchCheckBox>("Genres", genres)
private class SearchNamingList : Filter.Select<String>("Naming", searchNaming())
private class SearchStatusList : Filter.Select<String>("Status", searchStatus())
private class SearchOrderList : Filter.Select<String>("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<String>()
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())
}

View File

@ -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()
}
}

View File

@ -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()!!
)
}

View File

@ -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<MangasPage> {
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<String>()
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("<span").trim()
chapter.date_upload = parseChapterDate(element.select("div.addedAt").text().split(" ").first())
return chapter
}
private fun parseChapterNumber(name: String): Float {
try {
if (name.endsWith("단편")) 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 {
val calendar = Calendar.getInstance()
// MangaShow.Me doesn't provide uploaded year now(18/12/15).
// If received month is bigger then current month, set last year.
// TODO: Fix years due to lack of info.
return try {
val month = date.trim().split('-').first().toInt()
val currYear = calendar.get(Calendar.YEAR)
val year = if (month > 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<Page> {
val pages = mutableListOf<Page>()
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<Application>().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<Response?>(), 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:"
}
}

View File

@ -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)
}
}

View File

@ -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)
}()

View File

@ -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'
}

View File

@ -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<SManga> = 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<Page> {
// <article> - <div> - optional <div> - <div> - optional <p> - <img>
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:"
}
}

View File

@ -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<String>("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰"))
override fun pageListParse(document: Document): List<Page> {
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<String>(
"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<String>("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<String>(
"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<String>(
"Genre",
arrayOf(
"전체",
"판타지",
"액션",
"개그",
"미스터리",
"로맨스",
"드라마",
"무협",
"스포츠",
"일상",
"학원",
"성인"
)
)
override fun getFilterList() = FilterList(
SearchTypeList()
SearchTargetTypeList(),
Filter.Separator(),
Filter.Header("Under 3 Filters can't use with query"),
SearchYoilTypeList(),
SearchJaumTypeList(),
SearchGenreTypeList()
)
}