diff --git a/src/ko/mangashowme/AndroidManifest.xml b/src/ko/mangashowme/AndroidManifest.xml new file mode 100644 index 000000000..6bb3d95eb --- /dev/null +++ b/src/ko/mangashowme/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ko/mangashowme/build.gradle b/src/ko/mangashowme/build.gradle index 3d80f77bf..f5f5bdc4b 100644 --- a/src/ko/mangashowme/build.gradle +++ b/src/ko/mangashowme/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: ManaMoa' pkgNameSuffix = 'ko.mangashowme' extClass = '.ManaMoa' - extVersionCode = 18 + extVersionCode = 19 libVersion = '1.2' } diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MNCDNUrlHandler.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMCDNUrlHandler.kt similarity index 100% rename from src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MNCDNUrlHandler.kt rename to src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMCDNUrlHandler.kt diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt similarity index 54% rename from src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt rename to src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt index 49942106c..2cebe7cb9 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMFilters.kt @@ -13,88 +13,100 @@ 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", + "ㄱ", + "ㄲ", + "ㄴ", + "ㄷ", + "ㄸ", + "ㄹ", + "ㅁ", + "ㅂ", + "ㅃ", + "ㅅ", + "ㅆ", + "ㅇ", + "ㅈ", + "ㅉ", + "ㅊ", + "ㅋ", + "ㅌ", + "ㅍ", + "ㅎ", + "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", - "주간", - "격주", - "월간", - "격월/비정기", - "단편", - "단행본", - "완결" + "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("호러") + 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(), - SearchMatch(), - Filter.Separator(), - TextField("Author/Artist (Exact search)", "author") + SearchNamingList(), + SearchStatusList(), + SearchGenresList(searchGenres()), + Filter.Separator(), + SearchType(), + SearchMatch(), + SearchOrderList() ) fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: String, filters: FilterList): Request { @@ -102,7 +114,8 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St var statusFilter: Int? = null val genresFilter = mutableListOf() var matchFilter = 1 - var authorFilter: String? = null + var orderFilter = 0 + var typeFilter = 0 filters.forEach { filter -> when (filter) { @@ -110,20 +123,14 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St matchFilter = filter.state + 1 } - is TextField -> { - if (filter.key == "author" && filter.state.isNotEmpty()) { - authorFilter = filter.state - } + is SearchOrderList -> { + orderFilter = filter.state } - } - } - if (!authorFilter.isNullOrEmpty()) { - return GET("$baseUrl/bbs/page.php?hid=manga_list&sfl=4&stx=$authorFilter&page=${page - 1}") - } + is SearchType -> { + typeFilter = arrayOf(0, 5)[filter.state] + } - filters.forEach { filter -> - when (filter) { is SearchNamingList -> { if (filter.state > 0) { nameFilter = filter.state - 1 @@ -146,16 +153,26 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St } } - if (query.isEmpty() && nameFilter == null && statusFilter == null && genresFilter.isEmpty()) { - return GET("$baseUrl/bbs/page.php?hid=manga_list") + /* + 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}") } diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt similarity index 95% rename from src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt rename to src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt index e00d7cee6..007d428c2 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMImageDecoder.kt @@ -38,7 +38,12 @@ internal class ImageDecoder(scripts: String) { internal class ImageDecoderInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val req = chain.request() - val response = chain.proceed(req) + val newReq = req.newBuilder()!! + .removeHeader("ImageRequest") + .removeHeader("ImageDecodeRequest") + .removeHeader("SecondUrlToRequest") + .build()!! + val response = chain.proceed(newReq) val decodeHeader = req.header("ImageDecodeRequest") diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageUrlHandler.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMmageUrlHandler.kt similarity index 100% rename from src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageUrlHandler.kt rename to src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MMmageUrlHandler.kt diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt similarity index 78% rename from src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt rename to src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt index 0c241b57d..712743f60 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoa.kt @@ -11,6 +11,7 @@ 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.* import eu.kanade.tachiyomi.source.online.ParsedHttpSource @@ -20,6 +21,7 @@ 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 @@ -44,19 +46,21 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { override val name = "ManaMoa" // This keeps updating: https://twitter.com/manamoa24 - private val defaultBaseUrl = "https://manamoa27.net" + private val defaultBaseUrl = "https://manamoa29.net" override val baseUrl by lazy { getCurrentBaseUrl() } override val lang: String = "ko" // Latest updates currently returns duplicate manga as it separates manga into chapters - override val supportsLatest = false + // 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()!! + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(ImageDecoderInterceptor()) + .addInterceptor(ImageUrlHandlerInterceptor()) + .build()!! override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row" @@ -97,7 +101,22 @@ class ManaMoa : ConfigurableSource, 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 = searchComplexFilterMangaRequestBuilder(baseUrl, page, query, filters) + 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 { @@ -120,7 +139,7 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { } val manga = SManga.create() - manga.title = info.select("div.red").html() + 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 = @@ -259,10 +278,29 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { // Latest not supported - override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!") - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("This method should not be called!") - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("This method should not be called!") - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") + 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 @@ -321,8 +359,26 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { } } + 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) { @@ -364,8 +420,26 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { } } + 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 { @@ -414,6 +488,7 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! + private fun getExperimentLatest(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_PREF, false) override fun getFilterList() = getFilters() @@ -429,6 +504,11 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { "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 @@ -437,5 +517,8 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() { // 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 new file mode 100644 index 000000000..d1e3ef40c --- /dev/null +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/ManaMoaUrlActivity.kt @@ -0,0 +1,35 @@ +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) + } + +}