From 3c14867a5e8483d5531a81602fea8eb08c5addc8 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Thu, 26 Dec 2024 21:07:26 +0100 Subject: [PATCH 01/10] Add mangafre From c51e13fbeffcdd837a5bace34ef351ed19a498c9 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Fri, 27 Dec 2024 00:15:33 +0100 Subject: [PATCH 02/10] First step --- src/en/mangafre/build.gradle | 8 ++ .../extension/en/mangafre/MangafreFactory.kt | 22 ++++ .../extension/en/mangafre/MangafreGlobal.kt | 116 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/en/mangafre/build.gradle create mode 100644 src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt create mode 100644 src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt diff --git a/src/en/mangafre/build.gradle b/src/en/mangafre/build.gradle new file mode 100644 index 0000000000..c4dde232b6 --- /dev/null +++ b/src/en/mangafre/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Mangafre' + extClass = '.Mangafre' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt new file mode 100644 index 0000000000..20e73bdcc5 --- /dev/null +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.en.mangafre + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class MangafreFactory : SourceFactory { + /** + * Create a new copy of the sources + * @return The created sources + */ + override fun createSources(): List { + return listOf( + Mangafre(), + MangafreId(), + MangafreZh(), + ) + } +} + +class Mangafre : MangafreGlobal("en") +class MangafreId : MangafreGlobal("id") +class MangafreZh : MangafreGlobal("zh", supportsSearch = false) diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt new file mode 100644 index 0000000000..67ee82d0ce --- /dev/null +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt @@ -0,0 +1,116 @@ +package eu.kanade.tachiyomi.extension.en.mangafre + +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.select.Evaluator +import rx.Observable + +abstract class MangafreGlobal( + override val lang: String, + val supportsSearch: Boolean = true, +) : ParsedHttpSource() { + override val baseUrl = "https://mangafre.com/" + + override val name = "Mangafre" + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder().build() + + + // ============================== Popular =============================== + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/comic/bookclass.html?type=hot_novel&page_num=$page&language=$lang") + } + + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } + // Mangafre always has a next page button. So we give MangasPage.mangas empty. + return MangasPage(mangas, true) + } + + override fun popularMangaSelector(): String { + return ".list .row" + } + + override fun popularMangaFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.select("a").attr("alt") + thumbnail_url = element.selectFirst("img")!!.imgAttr() + setUrlWithoutDomain(element.select("a").attr("href")) + } + } + + override fun popularMangaNextPageSelector(): String? { + return ".pagination > .next" + } + + override fun fetchPopularManga(page: Int): Observable { + return client.newCall(popularMangaRequest(page)) + .asObservableSuccess() + .map { response -> + popularMangaParse(response) + } + } + + + // =============================== Latest =============================== + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/comic/bookclass.html?type=last_release&page_num=$page&language=$lang") + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select(latestUpdatesSelector()).map { latestUpdatesFromElement(it) } + return MangasPage(mangas, true) + } + + override fun latestUpdatesSelector(): String = popularMangaSelector() + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() + + override fun fetchLatestUpdates(page: Int): Observable { + return client.newCall(latestUpdatesRequest(page)) + .asObservableSuccess() + .map { response -> + popularMangaParse(response) + } + } + + +} + +// ============================= Utilities ============================== + +private fun Element.imgAttr(): String = when { + hasAttr("data-lazy-src") -> attr("abs:data-lazy-src") + hasAttr("data-src") -> attr("abs:data-src") + else -> attr("abs:src") +} + +private fun parseChapterDate(date: String): Long { + // Uppercase the first letter of the string + val formattedDate = date.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.FRANCE) else it.toString() } + return SimpleDateFormat("MMMM d, yyyy", Locale.FRANCE).parse(formattedDate)?.time ?: 0 +} From a6943faeb6f17111dc5bcbee92f00e9724f39d40 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Sat, 28 Dec 2024 21:11:57 +0100 Subject: [PATCH 03/10] More advencements --- .../extension/en/mangafre/MangafreFilters.kt | 31 ++++++ .../extension/en/mangafre/MangafreGlobal.kt | 105 ++++++++++++++++-- 2 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt new file mode 100644 index 0000000000..9585b861bf --- /dev/null +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt @@ -0,0 +1,31 @@ +package eu.kanade.tachiyomi.extension.en.mangafre + +import eu.kanade.tachiyomi.source.model.Filter + +object Note : Filter.Header("NOTE: Ignored if using text search!") + +class SelectFilterOption(val name: String, val value: String) + +abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { + val selected: String + get() = options[state].value +} + +class GenreFilter( + options: List, + default: Int, +) : SelectFilter("Genre", options, default) + +fun getGenreFilter(lang: String): List { + val allGenres: Map> = mapOf( + "en" to listOf( + SelectFilterOption("All", ""), + SelectFilterOption("Romance", "169"), + SelectFilterOption("Action", "170"), + SelectFilterOption("Comedy", "171"), + ), + "id" to emptyList(), + "zh" to emptyList(), + ) + return allGenres[lang] ?: emptyList() +} diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt index 67ee82d0ce..ad57cd21fa 100644 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt @@ -1,25 +1,24 @@ package eu.kanade.tachiyomi.extension.en.mangafre +import android.util.Log +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.model.Filter 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.Request +import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Response -import org.jsoup.Jsoup import org.jsoup.select.Evaluator import rx.Observable +import java.text.SimpleDateFormat +import java.util.Locale abstract class MangafreGlobal( override val lang: String, @@ -48,7 +47,7 @@ abstract class MangafreGlobal( } override fun popularMangaSelector(): String { - return ".list .row" + return "#list-page > .col-truyen-main .row" } override fun popularMangaFromElement(element: Element): SManga { @@ -98,7 +97,91 @@ abstract class MangafreGlobal( } } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + return GET("$baseUrl/comic/search.html?keyword=$query&page_num=$page") + } + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + val filterId = filter.selected + return GET("$baseUrl/comic/bookclass.html?type=category_novel&id=$filterId&page_num=$page&language=$lang") + } + else -> { } + } + } + TODO("Need to implement empty search somewhere") + } + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) } + return MangasPage(mangas, false) + } + override fun searchMangaSelector(): String = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() + + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { + if (!supportsSearch) { + return Observable.just(MangasPage(emptyList(), false)) + } + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + // =============================== Filters ============================== + + // Can accept only one Genre at the time + override fun getFilterList() = FilterList( + Note, + Filter.Separator(), + GenreFilter(getGenreFilter(lang), 0), + ) + + // =========================== Manga Details ============================ + + override fun mangaDetailsParse(document: Document): SManga { + TODO("Not yet implemented") + } + + private fun Element.parseAuthorsTo(manga: SManga) { + TODO("Not yet implemented") + } + + // =============================== Chapters ============================== + + override fun chapterFromElement(element: Element): SChapter { + TODO("Not yet implemented") + } + + + // =============================== Pages ================================ + + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(GET(baseUrl + chapter.url)) + .asObservableSuccess() + .map { response -> + pageListParse(response.asJsoup()) + } + } + + override fun pageListParse(document: Document): List { + TODO("Not yet implemented") + } + + override fun imageUrlParse(document: Document): String { + TODO("Not yet implemented") + } } // ============================= Utilities ============================== From 8119e4a814d8529fa1f121ee262cd9000d991c57 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Sat, 28 Dec 2024 21:12:41 +0100 Subject: [PATCH 04/10] More code --- .../tachiyomi/extension/en/mangafre/MangafreGlobal.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt index ad57cd21fa..9b2fc09025 100644 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt @@ -32,7 +32,6 @@ abstract class MangafreGlobal( override val client = network.cloudflareClient.newBuilder().build() - // ============================== Popular =============================== override fun popularMangaRequest(page: Int): Request { @@ -52,9 +51,9 @@ abstract class MangafreGlobal( override fun popularMangaFromElement(element: Element): SManga { return SManga.create().apply { - title = element.select("a").attr("alt") - thumbnail_url = element.selectFirst("img")!!.imgAttr() - setUrlWithoutDomain(element.select("a").attr("href")) + title = element.select(Evaluator.Tag("a")).attr("alt") + thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.imgAttr() + setUrlWithoutDomain(element.select(Evaluator.Tag("a")).attr("href")) } } @@ -70,7 +69,6 @@ abstract class MangafreGlobal( } } - // =============================== Latest =============================== override fun latestUpdatesRequest(page: Int): Request { From 73b0950238b69bcc1ae125d2bd534dacb8c4d782 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Sun, 29 Dec 2024 14:22:31 +0100 Subject: [PATCH 05/10] Arghhhhhhh (sound of pain) --- .../extension/en/mangafre/MangafreGlobal.kt | 48 +------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt index 9b2fc09025..b6dd722d73 100644 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt +++ b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt @@ -38,13 +38,6 @@ abstract class MangafreGlobal( return GET("$baseUrl/comic/bookclass.html?type=hot_novel&page_num=$page&language=$lang") } - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } - // Mangafre always has a next page button. So we give MangasPage.mangas empty. - return MangasPage(mangas, true) - } - override fun popularMangaSelector(): String { return "#list-page > .col-truyen-main .row" } @@ -61,39 +54,19 @@ abstract class MangafreGlobal( return ".pagination > .next" } - override fun fetchPopularManga(page: Int): Observable { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - popularMangaParse(response) - } - } - // =============================== Latest =============================== override fun latestUpdatesRequest(page: Int): Request { return GET("$baseUrl/comic/bookclass.html?type=last_release&page_num=$page&language=$lang") } - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()).map { latestUpdatesFromElement(it) } - return MangasPage(mangas, true) - } - override fun latestUpdatesSelector(): String = popularMangaSelector() override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun fetchLatestUpdates(page: Int): Observable { - return client.newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .map { response -> - popularMangaParse(response) - } - } + // =============================== Search =============================== override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { if (query.isNotEmpty()) { @@ -111,11 +84,6 @@ abstract class MangafreGlobal( TODO("Need to implement empty search somewhere") } - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) } - return MangasPage(mangas, false) - } override fun searchMangaSelector(): String = popularMangaSelector() override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) @@ -130,11 +98,7 @@ abstract class MangafreGlobal( if (!supportsSearch) { return Observable.just(MangasPage(emptyList(), false)) } - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } + return super.fetchSearchManga(page, query, filters) } // =============================== Filters ============================== @@ -165,14 +129,6 @@ abstract class MangafreGlobal( // =============================== Pages ================================ - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(GET(baseUrl + chapter.url)) - .asObservableSuccess() - .map { response -> - pageListParse(response.asJsoup()) - } - } - override fun pageListParse(document: Document): List { TODO("Not yet implemented") } From edba02e887dad6a91a1cc1828020796779dbd469 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Sun, 29 Dec 2024 18:00:12 +0100 Subject: [PATCH 06/10] More code --- .../mangafre/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3948 bytes .../mangafre/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2115 bytes .../mangafre/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6040 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11411 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 17073 bytes .../extension/en/mangafre/MangafreGlobal.kt | 61 ++++++++++++++---- 6 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/en/mangafre/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/en/mangafre/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/en/mangafre/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/en/mangafre/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/en/mangafre/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/en/mangafre/res/mipmap-hdpi/ic_launcher.png b/src/en/mangafre/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..835b583d39773867657b30f95fda7a76aa3c6d15 GIT binary patch literal 3948 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 literal 0 HcmV?d00001 diff --git a/src/en/mangafre/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangafre/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..fc432bd142e882c879cd38a6137cba59fa298ba5 GIT binary patch literal 6040 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 S4-HoU0000P)LE z*nMN_`?|3_w2XLCrVsU)j7uL_7Rc2z`VR>Utq7m;5E zr1@%&Kn)`hsG)7MX>$am2~ZP)nns`rP))$v{mb&;+Qa;cGT-0@P%nrV(fY zRMYS^8#e)JGEmb9Gy$q<_?nHI05ut?X#`Fapwm27O@s0)Xk0n1-{AM89@n4W$2zSs zI*Z4#1IX@s$G_RHN!9&W-w%G~_@tT@eH{S}i}js<`vOs|038H|{|}<@-C8&zwI0jY zQT+qx>>xO06B>TbzxmSdo?)tGQgvGmO!(5JOC9a)?IWR3=pR)omA?}R1V(X^BRo_5 z|Ij&e`}t85@1`F&b&mt>uzuY3eN_F!j_dSz)_VAR7V|KN@@=tL{4eEl`SsJMPdCti z(FCd{h`a)1g(rj)3EjGN>ncw2&vUulAMft&w&PuJ!cr=gj)ccgu$&HVBOTKXY;NjW ze2QM~{9$y^>ixL*xaZgYblZ``h3CUBL1m78Vvp z^Z9)6;E#3t?wYB}*_4W0C0trN0=oc?HgQpw2ZR1CMKl(UoLf!Q3rmk0j*bZu~(%ISh zUxUHmpUln8tyv_ID-X>rKvjbo2aKLSfBq)G`5&-(QuTtzk4dX#BCNmL&E&hOds63@ zN~N+;W&d=3e*UjbDMU$R_0kTT)=xh30uZH71SNL)^5uWCva<5ePg-@k9l(>W?`HDd z)b;DU-5e9D>_0*R{V9%}JD|#J;b8?g0O=SH0g9r;CnhHTS0f+($=L7-)*2{0kQ+fPBqzm@5ey?D-mfOwT=P6pe00W?zmTF#|m0kQ+fZldkJ_X1um1MO6*x;ciMx_(K+ zgU4(*fSiD12Z!DFe!}C|#MG;wS|MyGfEpDZKPT7EzT8Z{o4Rh!u^|BR3miMi<7Qx- z*ehQ8JE@7RmnT>^fUqUfu@~k93@3HGs4hPt@w2a!1kp|34G)lC(#dal&I$}CeVz>s zkP~=nt>ZUXUix!$#h)|~Zu@UYfSf?#L}l64u{)02iFG^pUeC|%dHGTQs6HABAiD{5 zYS`Ktly(#61q8c3{g^~A=XKJ@Z3uwefMYjNCkc<22|TSfCk;x&NuZ974p&~7Q*fNr z^J1si_3L&5y@2KR7^hJm$EeSC4z~a~ky5qRsTWwdTNtl^1n~!N9aaUORCBCU%2F453*uhcj_iiV|dOS)RzCr{ih{9AV5tL|D zLij)XX2}%_lFjEOmoFgY4WtnylJT8?xs%-f4qd+BN z;<2cLrL8qBt?5>jq@+?T2?dja1UJa+?CeTr7bUYJo7>y6y`7Q0{jB5*Mc$VMpniED zpI0Cbsw%sF>JE_I1RkY(5BuEdk9R7){DwyzmjFTXBohf~Pq#>WTdNEX_RH|#fb{kE zs{gmOwn#J@0T_ma+1S{YwY7CwSzVLaxp|qLotM@1b=lq9m)&euNhRycZql8Olaqex z1`yKOx%R@GNE9z{AWey3gYX@NDuu5=fbSL99Z(yXO}_JPTG}E1c12_V%7p5`nsih9iYXZvr2-wY zzrQd0xYGA__mwKy-P@CrY0Mr}ABP>^bg}?WMB376C1r*N`(*sw2+FVw^mn6lN>5jt zBoa|o!eJvDPNfpS@$!<{$;!s&uFTCX$;|A6%*@QoyO~*;`7jHJwq!4x14y}FNF%!o zs#e$<8XzYyIJuIJt3!B_@u*TMT^$|L+ubGID2aF+6PZv~_U7iMY+{mDp+=UWMpidA zq);pYB11CSO_GvkC@rvWhWh)UuKH!_{DfTo?1EgJo`l4SOKVFQ;L`glLjsl{{n*NY zs8k6fXJioYw zrW6Q5odx2si6W4IMagUzpu$$<(W7So=$X8E|3Rs=h2<65S5nAu_wB&;V&ffb(@+4p zg~qK-qyu&Yhn`PIE9{WoZW$gLl#!tUfCDws4VXI8m~^<+5xCNr$G!bL)JPGNvh7LFN}1WnpPWsSg^lltzcW!r>5%<+K8H7{|DJd0IZZI3?#sd!@fO4Lc~MzzGKm zkUmUq!F~V|13>D80Z|ZHaRO0%6Ncv%f;%6Swe>xDKeHrn-!00Emv7|JlNa*p&0E>Z zWT0+y%H{V8kQ=pDSAe_#V>c*=O`<*yV=I&hR80c_XQ;1F#$bm`Uzk)XrKg()Y#eq< z6jy#3^T#y9$Rb$^LuG~K!|W0!>zzD*@kX9w(w@J1BXf&O%9YkO(ZS=>RUho@l@Y3~ z$#J>)`4zc#?V|JpoMbF7@hHGSPJ%#%1pw)sCpwU5LL>o5B9scU<5U6(*~VV|19?(K9;4GHMsg2NXKk7j&vy5_|Od~M_sIS0|?ozPSgw2KUgmdFv?3i zhbpV9y&V!@P{v1xWo&c+5TOkBK`O;0l?Xz8Da!*ROi2KlP>JGyQCVKuk@vG}@^)q! z>f^cGd-N32Xa;I!PpOU~>>_RNFpfj}sueEzBs{E(=O^Xjg)zA_eNHY+4MQp+;vCq6 zOkrjeNDHO9sJ4`nHQ(`(21s=bX%yng#idw|0+OKY>;>e->ksnt&ySTldpk2HALi#F zp*E|M@i0*QfX5u;`mBX{0mzNS(slLv65}d8U0pJWSjz}h*3|iPkO*Tk+}|!;Z6WDw z4Iv6#l1Qi^k*X@8I^mK6)CW|^PBtj(nIMelqTGG(Oz!;rP@X=2Eh})d*EY747o_GH zlb(pjBn?<;S6#buNp4)f42VW$pf3sO6NmIENwBgHsY8Hp932eQE}IG=sLl+aOn@Xn zbrwd8s1(WxEOEqSZebIWaTbzsMjk(ZDUTjMgQqsH)K~$_AP4O;KWrb1CTLuM+yHZ! zYU8le2BACN0@r+Wcu1yTz@DF=6gVf-n9!cin8ZUn5({K7p#T6Spp01aWlDew6iq=| zplmv1w~&&b??0D2Kk=%6A@AoGVO%dl8W09pEl`uRdwRP&am+!v1-11@x31&Z1CoyC zB!vl+ubu|TQ3thP4gSrMFz7bc)mh9G%bWT>Y_T2pYF z%NYq*c9i6hAYwmEb_!T3tuPdIK>c=D;*wK1&~V z**Fr&Ei^|Bin>tg0Ap2~ah-D`!!n_aAZ}MbV+M$U(%6Xi3WBtk$Vy>W01pX zqyR^1mBHJZQFubx*3nWoeW)}D5-Rzz`eZ4F{-FqZI-hH5AM1+>> zTjyyoLv3CCYziQC$@oZ{bhjhUQ{I+P39!PxQoxd)0I<`>vw}#;F%zIqAu=GNhsN)z z&LUE*Bqax^w|9f`F!f`0Ll0o_Sstm8);PA0aSxcR3Cs^TU$LD(RKsD@3|c! z8VySV4+$B83b`~jC6^$1E+FP|VQNqYdy*0-<7Yvy(3G@t4d|2V5~Dp_}kyy zlp9yi%TRAjdOJkY38=>64#GU>6LFdV00=ESir}$b_dtfD~h_ zjPC(;CEO|5Vq9LoL3{`hJ$mv+9zJ<4kDfh;@xN{?!%*7!^yDK;KW7V|g<4h(*dmr3a)c;Q-b+*86;%BElLr!;$|EZAbs3QrU5d=k^&8w1_=ky*xcCU)(q^(~~`t3hzj`yaro~ zsRUIAJg5l}AtE?BGaTM2VHCemQx&Qz!~QWoLTG_?gC^h#BETO?!di>T_I^}0ccb!t zZd<mYCyJqA}j4z)#t`O?%R)Yf^Z zts%KE-YcVnX~457q5M1~51=R+UXU=Z67NAV)mMrV2$HC6fLgAg=P)^6i~xvb~c-#JLq~X&qRy=$6keAW%7&0e1H3aeD}jWd4^B} zmpK*@WXvPzSqnVs_kXGe8xJ5i;ON1i=hF_=!64xn0)$tuT$F2AHW(d9NpEXex?1uQ zDQ`#+5+zXFGVB=!?xJmwJWyGx!1jrw(2$0DlPo5Ee;#WYFxPgW#)<$yO;}J$(QcF; z+1QIA_OdJwo-81kSiv$#t8{gxmEAMi2P3{6R!#-r(Qu|zvjAi~^??BCIFN=%OCZzp z(%u*WQt(hpee~WGU{I|UNTO1(O$vcdS;J4f|L~36g;)0Y>1zZV-^!b}@71ckg5Z}R zcm=C50P+Hj{>4;V^n8W~`qY}%G?pc9-uO&zV9B7jtt^T1yhKY&5`;Yy$YW%wiqz5o zuG|=;Px`!J`^5VVLz?Prf59kwvncP4Qf3}W!?>-);Hd$$Od%<6KV;?A`#nU55s>U` z0YnMuOy#9Bu_Ni|E))?ajqniIDnLxyPk`{QH$U?ab#?27P#%jAnVy*Pz5tGbh@P8i zbSnYHx(Gpp+ob}yGubGhS(Rrm5pH<=T7J6!Kps4PDm#dpbFB4dwNI!q|Kn1n)d!$- zI$e_&rbQ_u%yGceirsw|0M0a2*EGDX>G3`}5BIq(wkwg`JXF^r%nYg}z@xk=sICy6 zG?f4}>1hKZ47@}EPY~bj0+`(yfb#*8Xbv!;&?Zt7g&B*rqi%=nSJJYy8I#2gs1VpI zX>^%RRHP-cBgqhqY6K_)e8eb^sQ}T2F=I>sNX42=*UNnSIH{%tvfPJJp^>c$V?m}l zr59e=7wx%lhwL#zy_A=k`F(g|GYBsJBzNxKmvuzU8KJf=wjPA&R}YZ&2d1$BtPSw= z^>oYiYnQQDc@5z7%0PQbdea3gJwk0|7a)07G47NET(HC>a-gGtfT~LY1mih^{}U`u zXa;uAHp)Ig$}tXvLMdW;KHBgugxY1d+$%e!UbXov7R&&PO^H@EC4v$zLv`urk0{|{ zKJ`IMKo!fO3i@a)f3kd`6^^-){-dX6a#wv+p9yw2sVVngeDaar4rA;`HPUw`|9 zeEY+Xvbek^+t^#pD772uqc7M)+MFE;^hvZC6G_!YSG^xA>*wHg-MV>Qe)C5+WCB6E z7Fm)cR97gEiO;UWrhz+7DWIawusci@WB_ylsm~#S!Y#^{P&Uc-Gm%Xc0#v|*ifI$6 zB@R7N5H*pMR3fm265~=zLIpC^2x%A05m?$<8!(n2=&DC`ygur4#qymVn zqhE$ft0YKpbl+(M6*1T>N)4F+DG7!E9&D&`VoWN@ z34C9az}`EQ8Gr+Lcw9!fH56)c0Unni=z6PtK9)eC_O`0)V&)8+d>FSB(%>dETn0hk zl&b2y%>76TNNmW*8d)*iC%HhcyqnL+?Vp}t?d}QeqW3B~{b3&VokM_Z?9;{r$j+eV zhA{54Ny25ndgU|u;`19S)-&1{#rhWFKoKmA!gdL&iG&1DSFf?vLIHp)RA6jM1mWQb zyf769A`tk0tP9^k4KQ$+L4o>H?l;%NbRU!!gpD2?jP6Q`(Q5$2(7+C3MsEyHk6{9} z$H&+Z##oKScuX8Sh`*Jzqaj0~qCGF3BR{Yh(2O`6>oX=O_$`2^Qp6HPfnaNh_!|vX zSV8}l*npHGeX_7tgaQ2l8(|czJ zw_&uYZ4$q^brWk&lhU2qmG&46*Yb)4b|E=-;exB!i+<^efak+?uP`9sq;evaS|dR4 z$_NwSlYJBh5|!;_teP7P(uhXgI3-x083Mplk0^1rp8`BcF9a$TJe(LVd?H4pq#FUH zv96?$=?Suqa*H%P`g~2hNCBj6BuXGk9{`PLdW8~}l1LqkD%xxdM<9T6t3>;;>=Kk$ z@3!Rm+cm7#a~sM%xsSa}#-488sQBsKu2#Y1U|#AYf$+dqS6-O1Ak15Cx?%T>jgG3j z8m?TLf)tvTsj+@(sVqvOI3tk)f`7Do7{{R$sET*0FmsWTo|f92#mOZ=^k67e07e*U zCP%voMS);G*;-$vlt2lnz69W*7f1C+kXUz%06_|A$;a6sKm;_1exn2k$56*M_V%b( zRRU>AqewN1TJthL*V2^GP|BfHlmz02G%aOOol-VpsOjxoQsyxfAC^m4-gqKke|K9g zapZ8fYnFS4aNG1@F9-0j*%<){;}^j_RUzzE9vK|OBIOmie)TdIN3msMVpxXz(h}Z% zDZ$Mrut8|I&}F9$!uSg>ZF7*cDo|IyWy=KrrZmEul6q&Sl9ciBF|0G)kk4;?E~6v;xW}poTP5Pk=83S_wdNR z!yf9DwGCOuejx@I55642)7TQo`Xd-D9O&)B59gx7tCmHb%Z&gsM89!y$$iB z6aZ456zc7-0AScZJJ8N**+bhdrn8E*Z6BrM|p3LpYN z`v(xwXpb^%K!52?Z9g5X>m5vpK2(`ICiGMR3z%~UFApILOKpH+Xd{DuX zO9hL>h4`pEf4zbYXD_e`?j@EvUL(l(3X80(j#9?_f->WS`r1HFMJ3(#;l3V}Zm6vh z8Jn0ynUFNHS|s3=aUV`R7E$R@_#VfGvUr@aBJAD?7I6oJ2~(a@2;fj!H zsq&}}X!lh0nt4^5FmU3-P*)wFiF95AK(K-GXwUw*c9wY$kj!Nd71yDiWllQx?GPdw z>-?^25S#Xk4i>67kB&~$Rx`adWsI9rl2VG>LzJo{{Q7~(rommcqZo4NkV?y#l(10z z7E2q?UgEBj7xQxO{v-MM!9xTW=c@Pq8xaMogMS1zA3v@SK)46pl^4dlJG-z|q6_MZ zURY;WH&(s708kqHo>Ky^4jZ|UA_e<#aK{JKTDy8@x)e&N7@)!?46KJ?BRdCOOCs7! zdf}83iUC8aI`uLGj~XVm51AnV*fL#sTzco{e1JLBCi);W3>&D`dq^Tmr7A#r`NG(C zp;RFIR0gpnvTU8W>LRLX=xb?I4L-6@4Gqsz8~G;qRt6ZaN)1a1aiPV{puAtq%j=mP z*g9VWns5)^3|d#{?4P&h3C#e z73DP}yo5=?11Su<2c@eL%> zOSNN>TRo1t!}iPr#CeF}_8b;Qq9a&^=l$)GC~nIE7;)H0NkEl^#7Vb63IUpSNFYk7 zu5Q4D&GmS^0s@d7Sh-K34HiBlxc>rPTA(V87(b#E!Mpy9(OxAfaJ7eF$1unkhb!Iz z_0@ILaN+57A$*`fBs^KDGo?~YLs-FMh7FVvY{s3m(XLbzBh{vKQYueF!gouoN*UAI zuqJF4NUIQ8f|3M?{hBWQP9Y{+`w>~*EXr43eI;Lg`4#q1KUEv=*S9i9#)QrsKnL|O z$A(*0gS>of9rvVv7@NopCe_&8r zl~ih#7~)DwtxL26*#vwA+g=Ou_3yuwufO_I9z1x29gM509JkG*p^WImY#^7$+b@2PTI#@n z@wVaw4QZ^Eb#%1hp7(AfQGfwHG^79;7#xzGzJBTH?Sst(BOJiRA<;r5Y_!L=jX-wE zaK$qydyq&pwkeUKnCJwSArwIHz}l}E>S6r>@~-@3090V;0EW0;v{g0_V?d@S#(0s2 zdVuaN00E1(5AP000b5NklWx-bcAJA1HqO@U9QG04yy)lsj zJvB@`ewivzjsj3QvgUpvowknZElDFB26q=0VkgGOaEIy`qS_O3 z4k3Ya~&@v$-K8|a55>Q!D`1c?t=LIr@t<%un{^8ne8OK?g zpH<~8-1z%XU!eSoC6HHe)WDh}%#CQg-I>8g#*Wf({b?VKVmBjI+Qj6fOyVxqaY&;i z@)RbKp)4w}X_P91i_QR}a^>}Q5Wqw+L3Njbs^j0hqeDp??lqN)Gq6=kqx!5HRK;B4E73RBgkcwxpqK z3I2VewzMSv_)aH;&IlXtX0}kaaZC0p?4Nm*xlit+Ka&!u7Er7|*iOA^n7A3A$)*O7 z_GNl{TCQA18kS3!q_evh1vdpI!nltN`>MIaP(`TCxRCOOxTJvp>oqtgX5bD*xb_vS zyq93xl%UdzrHZANHcS++RLLIFRKLN-?V!Sd!7=wni1dhVoQ*4lxn+39=eEyRhBd3g;{MN zzo4;78@sDV4=6n~Zp!aKgquqwlh__R0lP@qMku{lyp4y;5=YD^RK4xkTzF*+h5&Kl zw6MpVW&j3TNhKls&`tZuin+U&gJFtI?AXYj0W{l4hPi{Qp5W~Aa}-LRT_&m8-%~gK zQTpVuS0@iUMM0xf84`+{i9HFAiWn0fi-fJJGV>1|!=xpo9gHeWY^MM$<5&eGX4sQ0 zNWo%J2Ttuqu=;&{Y#^kWtJ#QlB5{ry1`a3(-`HP`0HsPm#SQv!_YpI?iiO&%SFfq9 zBPu9~5`q0>^>vKp2u_5tnoh}(&qF=si%9jdDeK(44)w5%yF!cCi>X5R)J&zUEoH&(5i&nUg$v=o$y0T0yDBCbEL2Q{!Kp)CSU|FGQ(aNwWOiU2!>dx#<%l~Uoc!hnl|1za$jRV2QNn(Kqx(@o zN5qP_+RrTZ$jSb-LazF34i}V*rUWN3bBvh1n(8IkQkz^GGz# zJ;f|^+bM0Tduom`h@G7C8Gz?(0_0@ykCR+(+Vd7_sxlU4du8@9uHf^bZ)k4y^VqIF0O2XdzI$P{46fU@Zt6R!>sK9*5Y*D_ z>;vRv@Q)fHw{6|jcUsp;JtuW)p`eDgXA2-d2GUC(ZrV7l>!hBOIyFqbA59w{AioCD zOFwSf_+8gce}0hQv5&?B$nSyk+Mk|*j_TUrq!fKryx9#23cT=|xFnBrSuLGc3C&o*AH*MV1^)icfY;&prF`pZ9Bw4V1-s9#ImH@Al0cME60#EN_fE1#`pd3@sW=VoLJarQD3 zC9CILD}a#R<-Z~07LqMHd-*vLUi)&}&TV~XBhfguu%qHN@)-R+3qZ`kb)o<FKtfj$DeG+S==1@FSFU~|HfhXz%c@3KH5>|0X+T$+^8@O z5L0~d!R&;l7im=f72f|oikrTUJu5#Z&}|=X>!0kro?7q!2S@rd6l7(u0%Upmi~V`H znRM2r9YlaAg$Pj!0R0Z{{~Be;{%}Xv@M8kK_TjZ%Ex)$`+9G|w`~}MYL(v8_)fWNM zj~}uTUpj@Y<;lOt{}8tTS=E@fg{aDskVJnQ@BbK+^0xpJk}n&tlbR&2{d#TJ zxIDNzGp4Zr7Ue&i?*xagyEdL(sV^Sc3qblISd>Cqm1$|z4UjH@?0*P|@FX|EqX3;t zBqu{r3jnpWb#iVET8GjEk1LXUkUIYpbAv|`37%(FwRtFcSTsD2)t6U**v@=vY}Fp3 zSEdW0qMc+uA4#b_FUM-o`8YGDrL)u!2o?guLN&#rrH&OIt-9dIX0LNJB?Z;khRZ)eOdqS=W%Ku&OeME4gPj~s!fWN5-s(+D&Hs%iL|jhg^98K`LlngG=_e9gv9fSL@{Gy+Y4Y8t*~ h<0e2&25K6C{|^}YTSVn~2vGn4002ovPDHLkV1kIVTHgQw literal 0 HcmV?d00001 diff --git a/src/en/mangafre/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangafre/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd8cfb934ec73f186ada7c901bb9443d4438676 GIT binary patch literal 17073 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 .book > img")!!.imgAttr() + document.selectFirst(Evaluator.Class("info"))!!.children().forEach { + when (it.children().first()!!.text()) { + "Author:" -> it.parseAuthorsTo(this) + "Genre:" -> it.parseGenresTo(this) + "Status:" -> it.parseStatusTo(this) + } + } + } } private fun Element.parseAuthorsTo(manga: SManga) { - TODO("Not yet implemented") + val authors = this.select("a").map { it.text() } + manga.author = authors.joinToString(", ") + } + + private fun Element.parseGenresTo(manga: SManga) { + val genres = this.select("a").map { it.text() } + manga.genre = genres.joinToString(", ") + } + + private fun Element.parseStatusTo(manga: SManga) { + this.selectFirst("h3:last-child").let { + it!! + when { + it.text().contains("ongoing", ignoreCase = true) -> manga.status = SManga.ONGOING + it.text().contains("completed", ignoreCase = true) -> manga.status = SManga.COMPLETED + else -> { + Log.e("MangafreGlobal", "Unknown status: ${it.text()}") + manga.status = SManga.UNKNOWN + } + } + } } // =============================== Chapters ============================== override fun chapterFromElement(element: Element): SChapter { - TODO("Not yet implemented") + return SChapter.create().apply { + name = element.attr("title").trim() + setUrlWithoutDomain(element.attr("href")) + } } + override fun chapterListSelector(): String { + return "a:has(> span.chapter-text)" // + } + + // TODO: We need to get multiple pages for the chapters. Check the android app for hidden API ? // =============================== Pages ================================ override fun pageListParse(document: Document): List { - TODO("Not yet implemented") + return document.select(imageListSelector()).mapIndexed { i, element -> + Page(i, "", element.imgAttr()) + } } override fun imageUrlParse(document: Document): String { - TODO("Not yet implemented") + throw UnsupportedOperationException("Not used") } + + private fun imageListSelector() = "#chapter-content .row.top-item img" } // ============================= Utilities ============================== From 0e657b357d89af2583aa88c79217a9f48dbb3a0c Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Sun, 29 Dec 2024 21:36:32 +0100 Subject: [PATCH 07/10] Almost finished --- .../mangafre => all/novelfull}/build.gradle | 4 +- .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../all/novelfull/NovelFullFactory.kt | 22 ++ .../all/novelfull/NovelFullFilters.kt | 196 ++++++++++++++++++ .../all/novelfull/NovelFullGlobal.kt} | 75 +++++-- .../extension/en/mangafre/MangafreFactory.kt | 22 -- .../extension/en/mangafre/MangafreFilters.kt | 31 --- 11 files changed, 282 insertions(+), 68 deletions(-) rename src/{en/mangafre => all/novelfull}/build.gradle (58%) rename src/{en/mangafre => all/novelfull}/res/mipmap-hdpi/ic_launcher.png (100%) rename src/{en/mangafre => all/novelfull}/res/mipmap-mdpi/ic_launcher.png (100%) rename src/{en/mangafre => all/novelfull}/res/mipmap-xhdpi/ic_launcher.png (100%) rename src/{en/mangafre => all/novelfull}/res/mipmap-xxhdpi/ic_launcher.png (100%) rename src/{en/mangafre => all/novelfull}/res/mipmap-xxxhdpi/ic_launcher.png (100%) create mode 100644 src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFactory.kt create mode 100644 src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt rename src/{en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt => all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt} (69%) delete mode 100644 src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt delete mode 100644 src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt diff --git a/src/en/mangafre/build.gradle b/src/all/novelfull/build.gradle similarity index 58% rename from src/en/mangafre/build.gradle rename to src/all/novelfull/build.gradle index c4dde232b6..97c0ef78bc 100644 --- a/src/en/mangafre/build.gradle +++ b/src/all/novelfull/build.gradle @@ -1,6 +1,6 @@ ext { - extName = 'Mangafre' - extClass = '.Mangafre' + extName = 'NovelFull' + extClass = '.NovelFullFactory' extVersionCode = 1 isNsfw = true } diff --git a/src/en/mangafre/res/mipmap-hdpi/ic_launcher.png b/src/all/novelfull/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/en/mangafre/res/mipmap-hdpi/ic_launcher.png rename to src/all/novelfull/res/mipmap-hdpi/ic_launcher.png diff --git a/src/en/mangafre/res/mipmap-mdpi/ic_launcher.png b/src/all/novelfull/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/en/mangafre/res/mipmap-mdpi/ic_launcher.png rename to src/all/novelfull/res/mipmap-mdpi/ic_launcher.png diff --git a/src/en/mangafre/res/mipmap-xhdpi/ic_launcher.png b/src/all/novelfull/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/en/mangafre/res/mipmap-xhdpi/ic_launcher.png rename to src/all/novelfull/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/en/mangafre/res/mipmap-xxhdpi/ic_launcher.png b/src/all/novelfull/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/en/mangafre/res/mipmap-xxhdpi/ic_launcher.png rename to src/all/novelfull/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/en/mangafre/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/novelfull/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/en/mangafre/res/mipmap-xxxhdpi/ic_launcher.png rename to src/all/novelfull/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFactory.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFactory.kt new file mode 100644 index 0000000000..192ef0c8d9 --- /dev/null +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFactory.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.all.novelfull + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class NovelFullFactory : SourceFactory { + /** + * Create a new copy of the sources + * @return The created sources + */ + override fun createSources(): List { + return listOf( + NovelFull(), + NovelFullId(), + NovelFullZh(), + ) + } +} + +class NovelFull : NovelFullGlobal("en") +class NovelFullId : NovelFullGlobal("id", supportsSearch = false) +class NovelFullZh : NovelFullGlobal("zh", supportsSearch = false) diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt new file mode 100644 index 0000000000..6c64726ccf --- /dev/null +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt @@ -0,0 +1,196 @@ +package eu.kanade.tachiyomi.extension.all.novelfull + +import eu.kanade.tachiyomi.source.model.Filter + +object Note : Filter.Header("NOTE: Ignored if using text search!") + +class SelectFilterOption(val name: String, val value: String) + +abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { + val selected: String + get() = options[state].value +} + +class GenreFilter( + options: List, + default: Int, +) : SelectFilter("Genre", options, default) + +fun getGenreFilter(lang: String): List { + val allGenres: Map> = mapOf( + "en" to listOf( + SelectFilterOption("All", ""), + SelectFilterOption("Romance", "169"), + SelectFilterOption("Fantasy", "168"), + SelectFilterOption("Modern", "185"), + SelectFilterOption("Adventure", "164"), + SelectFilterOption("Harem", "174"), + SelectFilterOption("Sci-Fi", "189"), + SelectFilterOption("Action", "202"), + SelectFilterOption("Historical", "175"), + SelectFilterOption("Xuanhuan", "201"), + SelectFilterOption("Urban", "203"), + SelectFilterOption("Mystery", "163"), + SelectFilterOption("LGBT+", "195"), + SelectFilterOption("Magic", "345"), + SelectFilterOption("Comedy", "211"), + SelectFilterOption("School Life", "196"), + SelectFilterOption("Drama", "224"), + SelectFilterOption("Adult", "302"), + SelectFilterOption("Time Travel", "284"), + SelectFilterOption("Psychological", "293"), + SelectFilterOption("Violence", "362"), + SelectFilterOption("History", "225"), + SelectFilterOption("Virtual Reality", "524"), + SelectFilterOption("Full Color", "260"), + SelectFilterOption("Smut", "272"), + SelectFilterOption("Shounen ai", "289"), + SelectFilterOption("Vampires", "298"), + SelectFilterOption("Seinen(M)", "350"), + SelectFilterOption("Magical Girls", "521"), + SelectFilterOption("Webtoon", "256"), + SelectFilterOption("Josei(W)", "265"), + SelectFilterOption("Mature", "282"), + SelectFilterOption("Shounen(B)", "294"), + SelectFilterOption("Manhua", "312"), + SelectFilterOption("Gore", "364"), + SelectFilterOption("Shoujo(G)", "252"), + SelectFilterOption("Isekai", "262"), + SelectFilterOption("Demons", "274"), + SelectFilterOption("Supernatural", "291"), + SelectFilterOption("Gender Bender", "300"), + SelectFilterOption("Sports", "352"), + SelectFilterOption("Reverse Isekai ", "523"), + SelectFilterOption("Adaptation", "258"), + SelectFilterOption("Slice of Life", "270"), + SelectFilterOption("Ecchi", "295"), + SelectFilterOption("parody", "520"), + SelectFilterOption("Manhwa", "254"), + SelectFilterOption("Medical", "264"), + SelectFilterOption("Yaoi(BL)", "278"), + ), + "id" to listOf( + SelectFilterOption("Semua", ""), + SelectFilterOption("Percintaan", "173"), + SelectFilterOption("Fantasi", "176"), + SelectFilterOption("Sejarah", "182"), + SelectFilterOption("Tindakan", "205"), + SelectFilterOption("Harem", "177"), + SelectFilterOption("Sci-Fi", "178"), + SelectFilterOption("Xuanhuan", "209"), + SelectFilterOption("Perkotaan", "181"), + SelectFilterOption("Kampus", "180"), + SelectFilterOption("Petualangan", "206"), + SelectFilterOption("Misteri", "208"), + SelectFilterOption("Sihir", "235"), + SelectFilterOption("Komedi", "232"), + SelectFilterOption("Drama", "218"), + SelectFilterOption("Isekai", "241"), + SelectFilterOption("Perjalanan waktu", "222"), + SelectFilterOption("Dewasa", "212"), + SelectFilterOption("LGBT+", "250"), + SelectFilterOption("School Life", "316"), + SelectFilterOption("Yaoi(BL)", "324"), + SelectFilterOption("Fantasi Timur", "536"), + SelectFilterOption("Adult", "223"), + SelectFilterOption("Slice of Life", "233"), + SelectFilterOption("Tragedy", "320"), + SelectFilterOption("Manhwa", "215"), + SelectFilterOption("Magic", "454"), + SelectFilterOption("Romance", "221"), + SelectFilterOption("Webtoon", "231"), + SelectFilterOption("Historical", "239"), + SelectFilterOption("Psychological", "247"), + SelectFilterOption("Medical", "317"), + SelectFilterOption("Smut", "213"), + SelectFilterOption("Shounen ai", "325"), + SelectFilterOption("Fantasy", "219"), + SelectFilterOption("Umum", "537"), + SelectFilterOption("Demons", "228"), + SelectFilterOption("Office Workers", "242"), + SelectFilterOption("Action", "251"), + SelectFilterOption("Shounen(B)", "321"), + SelectFilterOption("Adaptation", "216"), + SelectFilterOption("Yuri(GL)", "522"), + SelectFilterOption("Adventure", "240"), + SelectFilterOption("Manhua", "249"), + SelectFilterOption("Mystery", "319"), + SelectFilterOption("Josei(W)", "214"), + SelectFilterOption("Martial Arts", "347"), + SelectFilterOption("Full Color", "220"), + SelectFilterOption("Supernatural", "229"), + SelectFilterOption("Shoujo(G)", "237"), + SelectFilterOption("Reincarnation", "244"), + + ), + "zh" to listOf( + SelectFilterOption("全部", ""), + SelectFilterOption("热血恋爱", "127"), + SelectFilterOption("热血玄幻古风", "130"), + SelectFilterOption("彩虹都市", "121"), + SelectFilterOption("热血冒险", "131"), + SelectFilterOption("治愈", "108"), + SelectFilterOption("搞笑", "101"), + SelectFilterOption("古风", "115"), + SelectFilterOption("校园", "106"), + SelectFilterOption("猎奇", "114"), + SelectFilterOption("日常", "105"), + SelectFilterOption("同人", "116"), + SelectFilterOption("恋爱都市", "123"), + SelectFilterOption("纯爱", "535"), + SelectFilterOption("恋爱后宫搞笑", "128"), + SelectFilterOption("其它", "542"), + SelectFilterOption("生活", "135"), + SelectFilterOption("修真", "513"), + SelectFilterOption("恐怖", "111"), + SelectFilterOption("百合", "518"), + SelectFilterOption("奇幻冒险", "528"), + SelectFilterOption("耽美", "533"), + SelectFilterOption("恋爱古风", "126"), + SelectFilterOption("灵异", "540"), + SelectFilterOption("总裁", "545"), + SelectFilterOption("恋爱", "104"), + SelectFilterOption("后宫", "498"), + SelectFilterOption("玄幻", "109"), + SelectFilterOption("霸总", "516"), + SelectFilterOption("剧情", "526"), + SelectFilterOption("恋爱校园都市", "119"), + SelectFilterOption("少年", "531"), + SelectFilterOption("恋爱搞笑治愈", "124"), + SelectFilterOption("战争", "538"), + SelectFilterOption("校园日常", "129"), + SelectFilterOption("大女主", "543"), + SelectFilterOption("热血", "102"), + SelectFilterOption("架空", "496"), + SelectFilterOption("日漫", "107"), + SelectFilterOption("动作", "514"), + SelectFilterOption("悬疑", "112"), + SelectFilterOption("四格", "519"), + SelectFilterOption("热血悬疑都市", "117"), + SelectFilterOption("系统", "529"), + SelectFilterOption("校园搞笑", "122"), + SelectFilterOption("魔法", "534"), + SelectFilterOption("推理", "541"), + SelectFilterOption("热恋", "134"), + SelectFilterOption("真人", "546"), + SelectFilterOption("科幻", "499"), + SelectFilterOption("奇幻", "110"), + SelectFilterOption("欢乐向", "517"), + SelectFilterOption("励志", "527"), + SelectFilterOption("恋爱奇幻", "120"), + SelectFilterOption("少女", "532"), + SelectFilterOption("热血悬疑", "125"), + SelectFilterOption("竞技", "539"), + SelectFilterOption("格斗", "544"), + SelectFilterOption("冒险", "103"), + SelectFilterOption("穿越", "497"), + SelectFilterOption("[empty]", "515"), + SelectFilterOption("都市", "113"), + SelectFilterOption("武侠", "525"), + SelectFilterOption("玄幻都市", "118"), + SelectFilterOption("爱情", "530"), + ), + ) + // TODO: Automate this + return allGenres[lang] ?: emptyList() +} diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt similarity index 69% rename from src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt rename to src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt index 4806b53807..77e223d3fa 100644 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreGlobal.kt +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt @@ -1,7 +1,8 @@ -package eu.kanade.tachiyomi.extension.en.mangafre +package eu.kanade.tachiyomi.extension.all.novelfull import android.util.Log import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -9,18 +10,19 @@ 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.HttpUrl.Companion.toHttpUrl import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Evaluator import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale -abstract class MangafreGlobal( +abstract class NovelFullGlobal( override val lang: String, val supportsSearch: Boolean = true, ) : ParsedHttpSource() { + override val baseUrl = "https://allwebnovel.com" // https://mangafre.com override val name = "NovelFull" @@ -92,7 +94,7 @@ abstract class MangafreGlobal( query: String, filters: FilterList, ): Observable { - if (!supportsSearch) { + if (!supportsSearch && query.isNotEmpty()) { return Observable.just(MangasPage(emptyList(), false)) } return super.fetchSearchManga(page, query, filters) @@ -112,6 +114,7 @@ abstract class MangafreGlobal( return SManga.create().apply { title = document.selectFirst("h3.title")!!.text() description = document.selectFirst(".desc-text")!!.text() + .replace("\\n", "\n") thumbnail_url = document.selectFirst(".books > .book > img")!!.imgAttr() document.selectFirst(Evaluator.Class("info"))!!.children().forEach { when (it.children().first()!!.text()) { @@ -149,6 +152,60 @@ abstract class MangafreGlobal( // =============================== Chapters ============================== + private fun chapterListRequest(manga: SManga, page: Int): Request { + val url = (baseUrl + manga.url).toHttpUrl().newBuilder() + if (page != 1) { // Cache ? + url.addQueryParameter("page_num", page.toString()) + } + return GET(url.build(), headers) + } + + override fun chapterListRequest(manga: SManga): Request { + throw UnsupportedOperationException("Not used") + } + + override fun fetchChapterList(manga: SManga): Observable> { + // We need to get multiple pages for the chapters. + // The first page is already loaded in the manga details page. + // The rest of the pages are loaded with ?page_num=$page + // Chapter list will be empty when there are no more pages. + return client.newCall(chapterListRequest(manga, 1)) + .asObservableSuccess() + .flatMap { firstResponse -> + val firstDocument = firstResponse.asJsoup() + + // Determine the last page number from the pagination section. + val lastPageElement = firstDocument.selectFirst(".last a") + ?.attr("href") + + val lastPageNumber = baseUrl.toHttpUrl().newBuilder().build() + .resolve(lastPageElement!!) + ?.queryParameter("page_num") + ?.toIntOrNull() ?: 1 + + // Create an Observable to fetch all pages, starting with the first page. + Observable.range(1, lastPageNumber) + .concatMap { page -> + if (page == 1) { + // Use the already fetched first page's response. + // Can't use chapterListParse here because response buffer is already closed. + Observable.just( + firstDocument.select(chapterListSelector()).map { chapterFromElement(it) }, + ) + } else { + // Fetch subsequent pages. + client.newCall(chapterListRequest(manga, page)) + .asObservableSuccess() + .map { response -> chapterListParse(response) } + } + } + } + // Stop fetching if an empty list is encountered. + .takeUntil { it.isEmpty() } + // Combine all chapter lists into a single list. + .reduce { acc, list -> acc + list } + } + override fun chapterFromElement(element: Element): SChapter { return SChapter.create().apply { name = element.attr("title").trim() @@ -160,8 +217,6 @@ abstract class MangafreGlobal( return "a:has(> span.chapter-text)" // } - // TODO: We need to get multiple pages for the chapters. Check the android app for hidden API ? - // =============================== Pages ================================ override fun pageListParse(document: Document): List { @@ -184,9 +239,3 @@ private fun Element.imgAttr(): String = when { hasAttr("data-src") -> attr("abs:data-src") else -> attr("abs:src") } - -private fun parseChapterDate(date: String): Long { - // Uppercase the first letter of the string - val formattedDate = date.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.FRANCE) else it.toString() } - return SimpleDateFormat("MMMM d, yyyy", Locale.FRANCE).parse(formattedDate)?.time ?: 0 -} diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt deleted file mode 100644 index 20e73bdcc5..0000000000 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFactory.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangafre - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class MangafreFactory : SourceFactory { - /** - * Create a new copy of the sources - * @return The created sources - */ - override fun createSources(): List { - return listOf( - Mangafre(), - MangafreId(), - MangafreZh(), - ) - } -} - -class Mangafre : MangafreGlobal("en") -class MangafreId : MangafreGlobal("id") -class MangafreZh : MangafreGlobal("zh", supportsSearch = false) diff --git a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt b/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt deleted file mode 100644 index 9585b861bf..0000000000 --- a/src/en/mangafre/src/eu/kanade/tachiyomi/extension/en/mangafre/MangafreFilters.kt +++ /dev/null @@ -1,31 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangafre - -import eu.kanade.tachiyomi.source.model.Filter - -object Note : Filter.Header("NOTE: Ignored if using text search!") - -class SelectFilterOption(val name: String, val value: String) - -abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { - val selected: String - get() = options[state].value -} - -class GenreFilter( - options: List, - default: Int, -) : SelectFilter("Genre", options, default) - -fun getGenreFilter(lang: String): List { - val allGenres: Map> = mapOf( - "en" to listOf( - SelectFilterOption("All", ""), - SelectFilterOption("Romance", "169"), - SelectFilterOption("Action", "170"), - SelectFilterOption("Comedy", "171"), - ), - "id" to emptyList(), - "zh" to emptyList(), - ) - return allGenres[lang] ?: emptyList() -} From 050c56e7438518b90e21690236d4219820b2286b Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:35:03 +0100 Subject: [PATCH 08/10] Add headers Co-authored-by: Vetle Ledaal --- .../kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt index 77e223d3fa..d611c538ed 100644 --- a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt @@ -34,7 +34,7 @@ abstract class NovelFullGlobal( // ============================== Popular =============================== override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/comic/bookclass.html?type=hot_novel&page_num=$page&language=$lang") + return GET("$baseUrl/comic/bookclass.html?type=hot_novel&page_num=$page&language=$lang", headers) } override fun popularMangaSelector(): String { From e7081d817880872cdc9189a616af17b2a208265e Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:48:24 +0100 Subject: [PATCH 09/10] Should be ok --- .../all/novelfull/NovelFullFilters.kt | 182 +----------------- .../all/novelfull/NovelFullGlobal.kt | 59 +++--- .../all/novelfull/NovelFullPreferences.kt | 48 +++++ 3 files changed, 90 insertions(+), 199 deletions(-) create mode 100644 src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt index 6c64726ccf..6ce3328c25 100644 --- a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullFilters.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.extension.all.novelfull +import android.content.SharedPreferences import eu.kanade.tachiyomi.source.model.Filter object Note : Filter.Header("NOTE: Ignored if using text search!") +object Tip : Filter.Header("Click on `Reset` to clear all filters") class SelectFilterOption(val name: String, val value: String) @@ -16,181 +18,7 @@ class GenreFilter( default: Int, ) : SelectFilter("Genre", options, default) -fun getGenreFilter(lang: String): List { - val allGenres: Map> = mapOf( - "en" to listOf( - SelectFilterOption("All", ""), - SelectFilterOption("Romance", "169"), - SelectFilterOption("Fantasy", "168"), - SelectFilterOption("Modern", "185"), - SelectFilterOption("Adventure", "164"), - SelectFilterOption("Harem", "174"), - SelectFilterOption("Sci-Fi", "189"), - SelectFilterOption("Action", "202"), - SelectFilterOption("Historical", "175"), - SelectFilterOption("Xuanhuan", "201"), - SelectFilterOption("Urban", "203"), - SelectFilterOption("Mystery", "163"), - SelectFilterOption("LGBT+", "195"), - SelectFilterOption("Magic", "345"), - SelectFilterOption("Comedy", "211"), - SelectFilterOption("School Life", "196"), - SelectFilterOption("Drama", "224"), - SelectFilterOption("Adult", "302"), - SelectFilterOption("Time Travel", "284"), - SelectFilterOption("Psychological", "293"), - SelectFilterOption("Violence", "362"), - SelectFilterOption("History", "225"), - SelectFilterOption("Virtual Reality", "524"), - SelectFilterOption("Full Color", "260"), - SelectFilterOption("Smut", "272"), - SelectFilterOption("Shounen ai", "289"), - SelectFilterOption("Vampires", "298"), - SelectFilterOption("Seinen(M)", "350"), - SelectFilterOption("Magical Girls", "521"), - SelectFilterOption("Webtoon", "256"), - SelectFilterOption("Josei(W)", "265"), - SelectFilterOption("Mature", "282"), - SelectFilterOption("Shounen(B)", "294"), - SelectFilterOption("Manhua", "312"), - SelectFilterOption("Gore", "364"), - SelectFilterOption("Shoujo(G)", "252"), - SelectFilterOption("Isekai", "262"), - SelectFilterOption("Demons", "274"), - SelectFilterOption("Supernatural", "291"), - SelectFilterOption("Gender Bender", "300"), - SelectFilterOption("Sports", "352"), - SelectFilterOption("Reverse Isekai ", "523"), - SelectFilterOption("Adaptation", "258"), - SelectFilterOption("Slice of Life", "270"), - SelectFilterOption("Ecchi", "295"), - SelectFilterOption("parody", "520"), - SelectFilterOption("Manhwa", "254"), - SelectFilterOption("Medical", "264"), - SelectFilterOption("Yaoi(BL)", "278"), - ), - "id" to listOf( - SelectFilterOption("Semua", ""), - SelectFilterOption("Percintaan", "173"), - SelectFilterOption("Fantasi", "176"), - SelectFilterOption("Sejarah", "182"), - SelectFilterOption("Tindakan", "205"), - SelectFilterOption("Harem", "177"), - SelectFilterOption("Sci-Fi", "178"), - SelectFilterOption("Xuanhuan", "209"), - SelectFilterOption("Perkotaan", "181"), - SelectFilterOption("Kampus", "180"), - SelectFilterOption("Petualangan", "206"), - SelectFilterOption("Misteri", "208"), - SelectFilterOption("Sihir", "235"), - SelectFilterOption("Komedi", "232"), - SelectFilterOption("Drama", "218"), - SelectFilterOption("Isekai", "241"), - SelectFilterOption("Perjalanan waktu", "222"), - SelectFilterOption("Dewasa", "212"), - SelectFilterOption("LGBT+", "250"), - SelectFilterOption("School Life", "316"), - SelectFilterOption("Yaoi(BL)", "324"), - SelectFilterOption("Fantasi Timur", "536"), - SelectFilterOption("Adult", "223"), - SelectFilterOption("Slice of Life", "233"), - SelectFilterOption("Tragedy", "320"), - SelectFilterOption("Manhwa", "215"), - SelectFilterOption("Magic", "454"), - SelectFilterOption("Romance", "221"), - SelectFilterOption("Webtoon", "231"), - SelectFilterOption("Historical", "239"), - SelectFilterOption("Psychological", "247"), - SelectFilterOption("Medical", "317"), - SelectFilterOption("Smut", "213"), - SelectFilterOption("Shounen ai", "325"), - SelectFilterOption("Fantasy", "219"), - SelectFilterOption("Umum", "537"), - SelectFilterOption("Demons", "228"), - SelectFilterOption("Office Workers", "242"), - SelectFilterOption("Action", "251"), - SelectFilterOption("Shounen(B)", "321"), - SelectFilterOption("Adaptation", "216"), - SelectFilterOption("Yuri(GL)", "522"), - SelectFilterOption("Adventure", "240"), - SelectFilterOption("Manhua", "249"), - SelectFilterOption("Mystery", "319"), - SelectFilterOption("Josei(W)", "214"), - SelectFilterOption("Martial Arts", "347"), - SelectFilterOption("Full Color", "220"), - SelectFilterOption("Supernatural", "229"), - SelectFilterOption("Shoujo(G)", "237"), - SelectFilterOption("Reincarnation", "244"), - - ), - "zh" to listOf( - SelectFilterOption("全部", ""), - SelectFilterOption("热血恋爱", "127"), - SelectFilterOption("热血玄幻古风", "130"), - SelectFilterOption("彩虹都市", "121"), - SelectFilterOption("热血冒险", "131"), - SelectFilterOption("治愈", "108"), - SelectFilterOption("搞笑", "101"), - SelectFilterOption("古风", "115"), - SelectFilterOption("校园", "106"), - SelectFilterOption("猎奇", "114"), - SelectFilterOption("日常", "105"), - SelectFilterOption("同人", "116"), - SelectFilterOption("恋爱都市", "123"), - SelectFilterOption("纯爱", "535"), - SelectFilterOption("恋爱后宫搞笑", "128"), - SelectFilterOption("其它", "542"), - SelectFilterOption("生活", "135"), - SelectFilterOption("修真", "513"), - SelectFilterOption("恐怖", "111"), - SelectFilterOption("百合", "518"), - SelectFilterOption("奇幻冒险", "528"), - SelectFilterOption("耽美", "533"), - SelectFilterOption("恋爱古风", "126"), - SelectFilterOption("灵异", "540"), - SelectFilterOption("总裁", "545"), - SelectFilterOption("恋爱", "104"), - SelectFilterOption("后宫", "498"), - SelectFilterOption("玄幻", "109"), - SelectFilterOption("霸总", "516"), - SelectFilterOption("剧情", "526"), - SelectFilterOption("恋爱校园都市", "119"), - SelectFilterOption("少年", "531"), - SelectFilterOption("恋爱搞笑治愈", "124"), - SelectFilterOption("战争", "538"), - SelectFilterOption("校园日常", "129"), - SelectFilterOption("大女主", "543"), - SelectFilterOption("热血", "102"), - SelectFilterOption("架空", "496"), - SelectFilterOption("日漫", "107"), - SelectFilterOption("动作", "514"), - SelectFilterOption("悬疑", "112"), - SelectFilterOption("四格", "519"), - SelectFilterOption("热血悬疑都市", "117"), - SelectFilterOption("系统", "529"), - SelectFilterOption("校园搞笑", "122"), - SelectFilterOption("魔法", "534"), - SelectFilterOption("推理", "541"), - SelectFilterOption("热恋", "134"), - SelectFilterOption("真人", "546"), - SelectFilterOption("科幻", "499"), - SelectFilterOption("奇幻", "110"), - SelectFilterOption("欢乐向", "517"), - SelectFilterOption("励志", "527"), - SelectFilterOption("恋爱奇幻", "120"), - SelectFilterOption("少女", "532"), - SelectFilterOption("热血悬疑", "125"), - SelectFilterOption("竞技", "539"), - SelectFilterOption("格斗", "544"), - SelectFilterOption("冒险", "103"), - SelectFilterOption("穿越", "497"), - SelectFilterOption("[empty]", "515"), - SelectFilterOption("都市", "113"), - SelectFilterOption("武侠", "525"), - SelectFilterOption("玄幻都市", "118"), - SelectFilterOption("爱情", "530"), - ), - ) - // TODO: Automate this - return allGenres[lang] ?: emptyList() +fun getGenreFilterOrTip(prefs: SharedPreferences): Filter<*> { + val genres = prefs.genresFilter + return if (genres.isEmpty()) Tip else GenreFilter(genres, 0) } diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt index d611c538ed..ba31b7dd3a 100644 --- a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullGlobal.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.extension.all.novelfull +import android.app.Application +import android.content.SharedPreferences import android.util.Log import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess @@ -17,13 +19,15 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Evaluator import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get abstract class NovelFullGlobal( override val lang: String, val supportsSearch: Boolean = true, ) : ParsedHttpSource() { - override val baseUrl = "https://allwebnovel.com" // https://mangafre.com + override val baseUrl = "https://mangafre.com" // https://allwebnovel.com override val name = "NovelFull" @@ -31,6 +35,9 @@ abstract class NovelFullGlobal( override val client = network.cloudflareClient.newBuilder().build() + private val preferences: SharedPreferences = + Injekt.get().getSharedPreferences("source_$id", 0x0000) + // ============================== Popular =============================== override fun popularMangaRequest(page: Int): Request { @@ -44,8 +51,8 @@ abstract class NovelFullGlobal( override fun popularMangaFromElement(element: Element): SManga { return SManga.create().apply { title = element.select(Evaluator.Tag("a")).attr("alt") - thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.imgAttr() - setUrlWithoutDomain(element.select(Evaluator.Tag("a")).attr("href")) + element.selectFirst(Evaluator.Tag("img"))?.also { thumbnail_url = it.imgAttr() } + setUrlWithoutDomain(element.selectFirst(Evaluator.Tag("a"))!!.absUrl("href")) } } @@ -53,10 +60,18 @@ abstract class NovelFullGlobal( return ".pagination > .next" } + override fun fetchPopularManga(page: Int): Observable { + if (page == 1) { + val url = "$baseUrl/comic/index.html?language=$lang" + fetchGenres(client, url, headers, preferences) + } + return super.fetchPopularManga(page) + } + // =============================== Latest =============================== override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/comic/bookclass.html?type=last_release&page_num=$page&language=$lang") + return GET("$baseUrl/comic/bookclass.html?type=last_release&page_num=$page&language=$lang", headers) } override fun latestUpdatesSelector(): String = popularMangaSelector() @@ -69,18 +84,19 @@ abstract class NovelFullGlobal( override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { if (query.isNotEmpty()) { - return GET("$baseUrl/comic/search.html?keyword=$query&page_num=$page") + return GET("$baseUrl/comic/search.html?keyword=$query&page_num=$page", headers) } filters.forEach { filter -> when (filter) { is GenreFilter -> { val filterId = filter.selected - return GET("$baseUrl/comic/bookclass.html?type=category_novel&id=$filterId&page_num=$page&language=$lang") + Log.i("NovelFullGlobal_Genre", "Filtering by genre: $filterId") + return GET("$baseUrl/comic/bookclass.html?type=category_novel&id=$filterId&page_num=$page&language=$lang", headers) } else -> { } } } - TODO("Need to implement empty search somewhere") + return GET("$baseUrl/comic/search.html?keyword=", headers) } override fun searchMangaSelector(): String = popularMangaSelector() @@ -105,7 +121,7 @@ abstract class NovelFullGlobal( override fun getFilterList() = FilterList( Note, Filter.Separator(), - GenreFilter(getGenreFilter(lang), 0), + getGenreFilterOrTip(preferences), ) // =========================== Manga Details ============================ @@ -116,8 +132,9 @@ abstract class NovelFullGlobal( description = document.selectFirst(".desc-text")!!.text() .replace("\\n", "\n") thumbnail_url = document.selectFirst(".books > .book > img")!!.imgAttr() - document.selectFirst(Evaluator.Class("info"))!!.children().forEach { - when (it.children().first()!!.text()) { + document.selectFirst(Evaluator.Class("info"))?.children()?.forEach { + when (it.children().first()?.text()) { + // No need to translate these strings. "Author:" -> it.parseAuthorsTo(this) "Genre:" -> it.parseGenresTo(this) "Status:" -> it.parseStatusTo(this) @@ -127,24 +144,22 @@ abstract class NovelFullGlobal( } private fun Element.parseAuthorsTo(manga: SManga) { - val authors = this.select("a").map { it.text() } - manga.author = authors.joinToString(", ") + manga.author = this.select("a").joinToString { it.text() } } private fun Element.parseGenresTo(manga: SManga) { - val genres = this.select("a").map { it.text() } - manga.genre = genres.joinToString(", ") + manga.genre = this.select("a").joinToString { it.text() } } private fun Element.parseStatusTo(manga: SManga) { - this.selectFirst("h3:last-child").let { - it!! - when { - it.text().contains("ongoing", ignoreCase = true) -> manga.status = SManga.ONGOING - it.text().contains("completed", ignoreCase = true) -> manga.status = SManga.COMPLETED + this.selectFirst("h3:last-child").let { element -> + manga.status = when (element?.text()?.lowercase()) { + // No need to translate these strings. + "ongoing" -> SManga.ONGOING + "completed" -> SManga.COMPLETED else -> { - Log.e("MangafreGlobal", "Unknown status: ${it.text()}") - manga.status = SManga.UNKNOWN + Log.e("NovelFullGlobal", "Unknown status: ${element?.text()}") + SManga.UNKNOWN } } } @@ -209,7 +224,7 @@ abstract class NovelFullGlobal( override fun chapterFromElement(element: Element): SChapter { return SChapter.create().apply { name = element.attr("title").trim() - setUrlWithoutDomain(element.attr("href")) + setUrlWithoutDomain(element.absUrl("href")) } } diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt new file mode 100644 index 0000000000..3931503cc4 --- /dev/null +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt @@ -0,0 +1,48 @@ +package eu.kanade.tachiyomi.extension.all.novelfull + +import android.content.SharedPreferences +import android.util.Log +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient + +var SharedPreferences.genresFilter: List + get() { + getString("genres", "")?.let { + if (it.isEmpty()) return emptyList() + val genres = it.split(",") + val all = genres.map { genre -> + val (name, value) = genre.split(":") + SelectFilterOption(name, value) + } + return all + } + return emptyList() + } + set(value) = edit().putString("genres", value.joinToString(",") { "${it.name}:${it.value}" }).apply() + +var SharedPreferences.lastGenresFetch: Long + get() = getLong("lastGenresFetch", 0) + set(value) = edit().putLong("lastGenresFetch", value).apply() + +@Synchronized +fun fetchGenres(client: OkHttpClient, url: String, headers: Headers, prefs: SharedPreferences) { + if (prefs.genresFilter.isNotEmpty() || System.currentTimeMillis() - prefs.lastGenresFetch < 7 * 24 * 60 * 60 * 1000) { + return + } + + val response = client.newCall(GET(url, headers)).execute() + if (response.isSuccessful) { + val document = response.asJsoup() + val genres = document.select(".dropdown-menu a[href*=category]") + .map { element -> + val name = element.text() ?: "[unknown]" + val value = element.absUrl("href").toHttpUrl().queryParameter("id") ?: "[unknown]" + SelectFilterOption(name, value) + } + prefs.genresFilter = genres + prefs.lastGenresFetch = System.currentTimeMillis() + } +} From edbef33e16fb45e06a03b71ec009fdc9564fe681 Mon Sep 17 00:00:00 2001 From: FunnyTiming <192810776+FunnyTiming@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:51:17 +0100 Subject: [PATCH 10/10] Forget to lint... --- .../tachiyomi/extension/all/novelfull/NovelFullPreferences.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt index 3931503cc4..ba52a0cc39 100644 --- a/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt +++ b/src/all/novelfull/src/eu/kanade/tachiyomi/extension/all/novelfull/NovelFullPreferences.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.extension.all.novelfull import android.content.SharedPreferences -import android.util.Log import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers