Update Manamoa Extension (#2289)

* Search Update

* Update BaseUrl

* Manga ID Access + Url Activity

* Fix Url Activity

* Fix Filter Request

* Bump up version [*Not ready to release]

* Fix details parser bug by previous site changes.

Didn't find cuz title is keep used from searched result

* Remove Log.d

* Experiment latest fetch

* Rename filename from MSM(MangaShowMe) to MM(ManaMoa)

* Enable Latest Pagination

Site error is render failure from server, which is quite interesting. But If fails, there's no pagination shows so enabled.

* Update BaseUrl
This commit is contained in:
DitFranXX 2020-02-27 20:52:15 +09:00 committed by GitHub
parent a780a18058
commit 33749811cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 272 additions and 94 deletions

View File

@ -0,0 +1,38 @@
<?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

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: ManaMoa' appName = 'Tachiyomi: ManaMoa'
pkgNameSuffix = 'ko.mangashowme' pkgNameSuffix = 'ko.mangashowme'
extClass = '.ManaMoa' extClass = '.ManaMoa'
extVersionCode = 18 extVersionCode = 19
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -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 SearchCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
private class SearchMatch : Filter.Select<String>("Match", arrayOf("AND", "OR")) 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 SearchGenresList(genres: List<SearchCheckBox>) : Filter.Group<SearchCheckBox>("Genres", genres)
private class SearchNamingList : Filter.Select<String>("Naming", searchNaming()) private class SearchNamingList : Filter.Select<String>("Naming", searchNaming())
private class SearchStatusList : Filter.Select<String>("Status", searchStatus()) 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') // [`"Not Set"`, ...[...document.querySelectorAll(".categories ul[data-type='1'] li")].map((el, i) => `"${el.innerText.trim()}"`)].join(',\n')
private fun searchNaming() = arrayOf( private fun searchNaming() = arrayOf(
"Not Set", "Not Set",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"", "",
"A-Z", "A-Z",
"0-9" "0-9"
) )
// [`"Not Set"`, ...[...document.querySelectorAll(".categories ul[data-type='2'] li")].map((el, i) => `"${el.innerText.trim()}"`)].join(',\n') // [`"Not Set"`, ...[...document.querySelectorAll(".categories ul[data-type='2'] li")].map((el, i) => `"${el.innerText.trim()}"`)].join(',\n')
private fun searchStatus() = arrayOf( 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') // [...document.querySelectorAll(".categories ul[data-type='3'] li")].map((el, i) => `SearchCheckBox("${el.innerText.trim()}")`).join(',\n')
private fun searchGenres() = listOf( private fun searchGenres() = listOf(
SearchCheckBox("17"), SearchCheckBox("17"),
SearchCheckBox("BL"), SearchCheckBox("BL"),
SearchCheckBox("SF"), SearchCheckBox("SF"),
SearchCheckBox("TS"), 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("스포츠"), 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( fun getFilters() = FilterList(
SearchNamingList(), SearchNamingList(),
SearchStatusList(), SearchStatusList(),
SearchGenresList(searchGenres()), SearchGenresList(searchGenres()),
Filter.Separator(), Filter.Separator(),
SearchMatch(), SearchType(),
Filter.Separator(), SearchMatch(),
TextField("Author/Artist (Exact search)", "author") SearchOrderList()
) )
fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: String, filters: FilterList): Request { 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 var statusFilter: Int? = null
val genresFilter = mutableListOf<String>() val genresFilter = mutableListOf<String>()
var matchFilter = 1 var matchFilter = 1
var authorFilter: String? = null var orderFilter = 0
var typeFilter = 0
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
@ -110,20 +123,14 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St
matchFilter = filter.state + 1 matchFilter = filter.state + 1
} }
is TextField -> { is SearchOrderList -> {
if (filter.key == "author" && filter.state.isNotEmpty()) { orderFilter = filter.state
authorFilter = filter.state
}
} }
}
}
if (!authorFilter.isNullOrEmpty()) { is SearchType -> {
return GET("$baseUrl/bbs/page.php?hid=manga_list&sfl=4&stx=$authorFilter&page=${page - 1}") typeFilter = arrayOf(0, 5)[filter.state]
} }
filters.forEach { filter ->
when (filter) {
is SearchNamingList -> { is SearchNamingList -> {
if (filter.state > 0) { if (filter.state > 0) {
nameFilter = filter.state - 1 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() val url = HttpUrl.parse("$baseUrl/bbs/page.php?hid=manga_list")!!.newBuilder()
url.addQueryParameter("search_type", matchFilter.toString()) url.addQueryParameter("search_type", matchFilter.toString())
url.addQueryParameter("sfl", typeFilter.toString())
url.addQueryParameter("_0", query) url.addQueryParameter("_0", query)
url.addQueryParameter("_1", nameFilter?.toString() ?: "") url.addQueryParameter("_1", nameFilter?.toString() ?: "")
url.addQueryParameter("_2", statusFilter?.toString() ?: "") url.addQueryParameter("_2", statusFilter?.toString() ?: "")
url.addQueryParameter("_3", genresFilter.joinToString(",")) url.addQueryParameter("_3", genresFilter.joinToString(","))
url.addQueryParameter("_4", orderFilter.toString())
if (page > 1) { if (page > 1) {
url.addQueryParameter("page", "${page - 1}") url.addQueryParameter("page", "${page - 1}")
} }

View File

@ -38,7 +38,12 @@ internal class ImageDecoder(scripts: String) {
internal class ImageDecoderInterceptor : Interceptor { internal class ImageDecoderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val req = chain.request() 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") val decodeHeader = req.header("ImageDecodeRequest")

View File

@ -11,6 +11,7 @@ import android.support.v7.preference.PreferenceScreen
import android.widget.Toast import android.widget.Toast
import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
@ -20,6 +21,7 @@ import org.json.JSONArray
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.select.Elements import org.jsoup.select.Elements
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.IOException import java.io.IOException
@ -44,19 +46,21 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
override val name = "ManaMoa" override val name = "ManaMoa"
// This keeps updating: https://twitter.com/manamoa24 // 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 baseUrl by lazy { getCurrentBaseUrl() }
override val lang: String = "ko" override val lang: String = "ko"
// Latest updates currently returns duplicate manga as it separates manga into chapters // 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() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(ImageDecoderInterceptor()) .addInterceptor(ImageDecoderInterceptor())
.addInterceptor(ImageUrlHandlerInterceptor()) .addInterceptor(ImageUrlHandlerInterceptor())
.build()!! .build()!!
override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row" 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 searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaSelector() override fun searchMangaNextPageSelector() = popularMangaSelector()
override fun searchMangaParse(response: Response) = popularMangaParse(response) 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<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 { override fun mangaDetailsParse(document: Document): SManga {
@ -120,7 +139,7 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
} }
val manga = SManga.create() 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. // They using background-image style tag for cover. extract url from style attribute.
manga.thumbnail_url = urlFinder(thumbnailElement.attr("style")) manga.thumbnail_url = urlFinder(thumbnailElement.attr("style"))
manga.description = manga.description =
@ -259,10 +278,29 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
// Latest not supported // Latest not supported
override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!") override fun latestUpdatesSelector() = ".post-row > div.media.post-list"
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 latestUpdatesFromElement(element: Element): SManga {
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") 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 //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(baseUrlPref)
screen.addPreference(autoFetchUrlPref) screen.addPreference(autoFetchUrlPref)
screen.addPreference(latestExperimentPref)
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { 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(baseUrlPref)
screen.addPreference(autoFetchUrlPref) screen.addPreference(autoFetchUrlPref)
screen.addPreference(latestExperimentPref)
} }
private fun getCurrentBaseUrl(): String { private fun getCurrentBaseUrl(): String {
@ -414,6 +488,7 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
private fun getExperimentLatest(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_PREF, false)
override fun getFilterList() = getFilters() override fun getFilterList() = getFilters()
@ -429,6 +504,11 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
"Experimental, May cause Tachiyomi *very* unstable.\n" + "Experimental, May cause Tachiyomi *very* unstable.\n" +
"Requires Android Oreo or newer." "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." private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
// Image Decoder // Image Decoder
@ -437,5 +517,8 @@ class ManaMoa : ConfigurableSource, ParsedHttpSource() {
// Url Handler // Url Handler
internal const val MINIMUM_IMAGE_SIZE = 10000 internal const val MINIMUM_IMAGE_SIZE = 10000
// Activity Url Handler
const val PREFIX_ID_SEARCH = "id:"
} }
} }

View File

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