diff --git a/src/all/fmreader/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fmreader/default_res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/all/fmreader/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/fmreader/default_res/mipmap-hdpi/ic_launcher.png diff --git a/src/all/fmreader/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fmreader/default_res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/all/fmreader/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/fmreader/default_res/mipmap-mdpi/ic_launcher.png diff --git a/src/all/fmreader/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fmreader/default_res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/all/fmreader/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/default_res/mipmap-xhdpi/ic_launcher.png diff --git a/src/all/fmreader/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fmreader/default_res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/all/fmreader/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/default_res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/all/fmreader/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fmreader/default_res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/all/fmreader/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/fmreader/default_res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/all/fmreader/res/web_hi_res_512.png b/multisrc/overrides/fmreader/default_res/web_hi_res_512.png similarity index 100% rename from src/all/fmreader/res/web_hi_res_512.png rename to multisrc/overrides/fmreader/default_res/web_hi_res_512.png diff --git a/multisrc/overrides/fmreader/eighteenlhplus/src/EighteenLHPlus.kt b/multisrc/overrides/fmreader/eighteenlhplus/src/EighteenLHPlus.kt new file mode 100644 index 000000000..b18f982c6 --- /dev/null +++ b/multisrc/overrides/fmreader/eighteenlhplus/src/EighteenLHPlus.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.en.eighteenlhplus + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import okhttp3.OkHttpClient + +class EighteenLHPlus : FMReader("18LHPlus", "https://18lhplus.com", "en") { + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor { chain -> + val originalRequest = chain.request() + chain.proceed(originalRequest).let { response -> + if (response.code() == 403 && originalRequest.url().host().contains("mkklcdn")) { + response.close() + chain.proceed(originalRequest.newBuilder().removeHeader("Referer").addHeader("Referer", "https://manganelo.com").build()) + } else { + response + } + } + } + .build() + override fun popularMangaNextPageSelector() = "div.col-lg-8 div.btn-group:first-of-type" + override fun getGenreList() = getAdultGenreList() +} diff --git a/multisrc/overrides/fmreader/epikmanga/src/EpikManga.kt b/multisrc/overrides/fmreader/epikmanga/src/EpikManga.kt new file mode 100644 index 000000000..689fbf3c0 --- /dev/null +++ b/multisrc/overrides/fmreader/epikmanga/src/EpikManga.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.extension.tr.epikmanga + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import rx.Observable + +class EpikManga : FMReader("Epik Manga", "https://www.epikmanga.com", "tr") { + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=views&sorting-type=DESC&Sayfa=$page", headers) + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=lastUpdate&sorting-type=DESC&Sayfa=$page", headers) + override fun popularMangaNextPageSelector() = "ul.pagination li.active + li:not(.disabled)" + + override val headerSelector = "h4 a" + + // search wasn't working on source's website + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response, query) + } + } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/seri-listesi?type=text", headers) + private fun searchMangaParse(response: Response, query: String): MangasPage { + val mangas = response.asJsoup().select("div.char.col-lg-4 a") + .filter { it.text().contains(query, ignoreCase = true) } + .map { + SManga.create().apply { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + } + return MangasPage(mangas, false) + } + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.col-md-9 div.row").first() + + return SManga.create().apply { + status = parseStatus(infoElement.select("h4:contains(Durum:)").firstOrNull()?.ownText()) + author = infoElement.select("h4:contains(Yazar:)").firstOrNull()?.ownText() + artist = infoElement.select("h4:contains(Çizer:)").firstOrNull()?.ownText() + genre = infoElement.select("h4:contains(Türler:) a").joinToString { it.text() } + thumbnail_url = infoElement.select("img.thumbnail").imgAttr() + description = document.select("div.col-md-12 p").text() + } + } + override fun chapterListSelector() = "table.table tbody tr" + override fun getFilterList(): FilterList = FilterList() +} diff --git a/multisrc/overrides/fmreader/hanascanrawqq/src/HanaScanRawQQ.kt b/multisrc/overrides/fmreader/hanascanrawqq/src/HanaScanRawQQ.kt new file mode 100644 index 000000000..9abe4bb73 --- /dev/null +++ b/multisrc/overrides/fmreader/hanascanrawqq/src/HanaScanRawQQ.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.ja.hanascanrawqq + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.Request + +class HanaScanRawQQ : FMReader("HanaScan (RawQQ)", "https://hanascan.com", "ja") { + override fun popularMangaNextPageSelector() = "div.col-md-8 button" + // Referer needs to be chapter URL + override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) +} \ No newline at end of file diff --git a/multisrc/overrides/fmreader/heroscan/src/HeroScan.kt b/multisrc/overrides/fmreader/heroscan/src/HeroScan.kt new file mode 100644 index 000000000..f5ffa1ade --- /dev/null +++ b/multisrc/overrides/fmreader/heroscan/src/HeroScan.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.en.heroscan + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import okhttp3.OkHttpClient + +class HeroScan : FMReader("HeroScan", "https://heroscan.com", "en") { + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor { chain -> + val originalRequest = chain.request() + chain.proceed(originalRequest).let { response -> + if (response.code() == 403 && originalRequest.url().host().contains("b-cdn")) { + response.close() + chain.proceed(originalRequest.newBuilder().removeHeader("Referer").addHeader("Referer", "https://isekaiscan.com").build()) + } else { + response + } + } + } + .build() +} diff --git a/multisrc/overrides/fmreader/kisslove/src/KissLove.kt b/multisrc/overrides/fmreader/kisslove/src/KissLove.kt new file mode 100644 index 000000000..b2df5120a --- /dev/null +++ b/multisrc/overrides/fmreader/kisslove/src/KissLove.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.ja.kisslove + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.source.model.Page +import org.jsoup.nodes.Document + +class KissLove : FMReader("KissLove", "https://kissaway.net", "ja") { + override fun pageListParse(document: Document): List = base64PageListParse(document) +} \ No newline at end of file diff --git a/multisrc/overrides/fmreader/mangatr/src/MangaTR.kt b/multisrc/overrides/fmreader/mangatr/src/MangaTR.kt new file mode 100644 index 000000000..ceecde634 --- /dev/null +++ b/multisrc/overrides/fmreader/mangatr/src/MangaTR.kt @@ -0,0 +1,98 @@ +package eu.kanade.tachiyomi.extension.tr.mangatr + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.FormBody +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { + override fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + } + override fun popularMangaNextPageSelector() = "div.btn-group:not(div.btn-block) button.btn-info" + // TODO: genre search possible but a bit of a pain + override fun getFilterList() = FilterList() + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/arama.html?icerik=$query", headers) + override fun searchMangaParse(response: Response): MangasPage { + val mangas = mutableListOf() + + response.asJsoup().select("div.row a[data-toggle]") + .filterNot { it.siblingElements().text().contains("Novel") } + .map { mangas.add(searchMangaFromElement(it)) } + + return MangasPage(mangas, false) + } + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + + manga.setUrlWithoutDomain(element.attr("abs:href")) + manga.title = element.text() + + return manga + } + + override fun mangaDetailsParse(document: Document): SManga { + val manga = SManga.create() + val infoElement = document.select("div#tab1").first() + + manga.author = infoElement.select("table + table tr + tr td a").first()?.text() + manga.artist = infoElement.select("table + table tr + tr td + td a").first()?.text() + manga.genre = infoElement.select("div#tab1 table + table tr + tr td + td + td").text() + manga.status = parseStatus(infoElement.select("div#tab1 table tr + tr td a").first().text()) + manga.description = infoElement.select("div.well").text().trim() + manga.thumbnail_url = document.select("img.thumbnail").attr("abs:src") + + return manga + } + + override fun chapterListSelector() = "tr.table-bordered" + override val chapterUrlSelector = "td[align=left] > a" + override val chapterTimeSelector = "td[align=right]" + private val chapterListHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest").build() + override fun fetchChapterList(manga: SManga): Observable> { + val requestUrl = "$baseUrl/cek/fetch_pages_manga.php?manga_cek=${manga.url.substringAfter("manga-").substringBefore(".")}" + return client.newCall(GET(requestUrl, chapterListHeaders)) + .asObservableSuccess() + .map { response -> + chapterListParse(response, requestUrl) + } + } + + private fun chapterListParse(response: Response, requestUrl: String): List { + val chapters = mutableListOf() + var document = response.asJsoup() + var moreChapters = true + var nextPage = 2 + + // chapters are paginated + while (moreChapters) { + document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } + if (document.select("a[data-page=$nextPage]").isNotEmpty()) { + val body = FormBody.Builder() + .add("page", nextPage.toString()) + .build() + document = client.newCall(POST(requestUrl, chapterListHeaders, body)).execute().asJsoup() + nextPage++ + } else { + moreChapters = false + } + } + return chapters + } + + override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/${chapter.url.substringAfter("cek/")}", headers) +} diff --git a/multisrc/overrides/fmreader/manhwa18/src/Manhwa18.kt b/multisrc/overrides/fmreader/manhwa18/src/Manhwa18.kt new file mode 100644 index 000000000..d38764321 --- /dev/null +++ b/multisrc/overrides/fmreader/manhwa18/src/Manhwa18.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.en.manhwa18 + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.Request + +class Manhwa18 : FMReader("Manhwa18", "https://manhwa18.com", "en") { + override fun imageRequest(page: Page): Request { + return if (page.imageUrl!!.contains("manhwa18")) { + super.imageRequest(page) + } else { + GET(page.imageUrl!!, headers.newBuilder().removeAll("Referer").build()) + } + } + override fun getGenreList() = getAdultGenreList() +} \ No newline at end of file diff --git a/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt b/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt new file mode 100644 index 000000000..43faabdc6 --- /dev/null +++ b/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.extension.all.manhwa18net + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.Request + +class Manhwa18NetFactory : SourceFactory { + override fun createSources(): List = listOf( + Manhwa18Net(), + Manhwa18NetRaw(), + ) +} + +class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") { + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC&ungenre=raw", headers) + + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC&ungenre=raw", headers) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val noRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("ungenre", "raw").toString() + return GET(noRawsUrl, headers) + } + + override fun getGenreList() = getAdultGenreList() +} + +class Manhwa18NetRaw : FMReader("Manhwa18.net", "https://manhwa18.net", "ko") { + override val requestPath = "manga-list-genre-raw.html" + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("genre", "raw").toString() + return GET(onlyRawsUrl, headers) + } + + override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) }) +} diff --git a/multisrc/overrides/fmreader/manhwasmut/src/ManhwaSmut.kt b/multisrc/overrides/fmreader/manhwasmut/src/ManhwaSmut.kt new file mode 100644 index 000000000..7a3ceb892 --- /dev/null +++ b/multisrc/overrides/fmreader/manhwasmut/src/ManhwaSmut.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.tr.manhwasmut + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.Request + +class ManhwaSmut : FMReader("ManhwaSmut", "https://manhwasmut.com", "en") { + private val noReferer = headersBuilder().removeAll("Referer").build() + override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, if (page.imageUrl!!.contains("toonily")) noReferer else headers) +} \ No newline at end of file diff --git a/multisrc/overrides/fmreader/rawlh/src/RawLH.kt b/multisrc/overrides/fmreader/rawlh/src/RawLH.kt new file mode 100644 index 000000000..495e8d055 --- /dev/null +++ b/multisrc/overrides/fmreader/rawlh/src/RawLH.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.ja.rawlh + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.Request +import org.jsoup.nodes.Document + +class RawLH : FMReader("RawLH", "https://lovehug.net", "ja") { + override val chapterUrlSelector = "" + override fun pageListParse(document: Document): List = base64PageListParse(document) + // Referer needs to be chapter URL + override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) +} \ No newline at end of file diff --git a/multisrc/overrides/fmreader/saytruyen/src/SayTruyen.kt b/multisrc/overrides/fmreader/saytruyen/src/SayTruyen.kt new file mode 100644 index 000000000..f19d1c74b --- /dev/null +++ b/multisrc/overrides/fmreader/saytruyen/src/SayTruyen.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.extension.vi.saytruyen + +import eu.kanade.tachiyomi.multisrc.fmreader.FMReader +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.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document + +class SayTruyen : FMReader("Say Truyen", "https://saytruyen.com", "vi") { + override fun mangaDetailsParse(document: Document): SManga { + val info = document.select("div.row").first() + return SManga.create().apply { + author = info.select("div.row li:has(b:contains(Tác giả)) small").text() + genre = info.select("div.row li:has(b:contains(Thể loại)) small a").joinToString { it.text() } + status = parseStatus(info.select("div.row li:has(b:contains(Tình trạng)) a").text()) + description = document.select("div.description").text() + thumbnail_url = info.select("img.thumbnail").attr("abs:src") + } + } + override fun chapterListParse(response: Response): List { + return response.asJsoup().let { document -> + document.select(chapterListSelector()).map { + chapterFromElement(it).apply { + scanlator = document.select("div.row li:has(b:contains(Nhóm dịch)) small").text() + } + } + } + } + override fun pageListParse(document: Document): List = super.pageListParse(document).onEach { it.imageUrl!!.trim() } +} diff --git a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt similarity index 99% rename from src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt index 4473eadb6..6f586f695 100644 --- a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReader.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.all.fmreader +package eu.kanade.tachiyomi.multisrc.fmreader import android.util.Base64 import eu.kanade.tachiyomi.network.GET diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt new file mode 100644 index 000000000..9989c2bc7 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.multisrc.fmreader + +import eu.kanade.tachiyomi.multisrc.ThemeSourceData.MultiLang +import eu.kanade.tachiyomi.multisrc.ThemeSourceData.SingleLang +import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator + +class FMReaderGenerator : ThemeSourceGenerator { + + override val themePkg = "fmreader" + + override val themeClass = "FMReader" + + override val baseVersionCode: Int = 1 + + /** For future sources: when testing and popularMangaRequest() returns a Jsoup error instead of results + * most likely the fix is to override popularMangaNextPageSelector() */ + + override val sources = listOf( + SingleLang("18LHPlus", "https://18lhplus.com", "en", className = "EighteenLHPlus"), + SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"), + SingleLang("HanaScan (RawQQ)", "https://hanascan.com", "ja", className = "HanaScanRawQQ"), + SingleLang("HeroScan", "https://heroscan.com", "en"), + SingleLang("KissLove", "https://kissaway.net", "ja"), + SingleLang("LHTranslation", "https://lhtranslation.net", "en"), + SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR"), + SingleLang("ManhuaScan", "https://manhuascan.com", "en"), + SingleLang("Manhwa18", "https://manhwa18.com", "en"), + MultiLang("Manhwa18.net", "https://manhwa18.net", listOf("en", "ko"), className = "Manhwa18NetFactory"), + SingleLang("ManhwaSmut", "https://manhwasmut.com", "en"), + SingleLang("RawLH", "https://lovehug.net", "ja"), + SingleLang("Say Truyen", "https://saytruyen.com", "vi"), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + FMReaderGenerator().createAll() + } + } +} diff --git a/src/all/fmreader/AndroidManifest.xml b/src/all/fmreader/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/fmreader/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/fmreader/build.gradle b/src/all/fmreader/build.gradle deleted file mode 100644 index 59b373b9e..000000000 --- a/src/all/fmreader/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'FMReader (multiple aggregators)' - pkgNameSuffix = 'all.fmreader' - extClass = '.FMReaderFactory' - extVersionCode = 28 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt b/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt deleted file mode 100644 index 98140c03b..000000000 --- a/src/all/fmreader/src/eu/kanade/tachiyomi/extension/all/fmreader/FMReaderFactory.kt +++ /dev/null @@ -1,285 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.fmreader - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -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.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class FMReaderFactory : SourceFactory { - override fun createSources(): List = listOf( - LHTranslation(), - KissLove(), - HanaScan(), - RawLH(), - Manhwa18(), - EighteenLHPlus(), - MangaTR(), - Manhwa18Net(), - Manhwa18NetRaw(), - SayTruyen(), - EpikManga(), - ManhuaScan(), - ManhwaSmut(), - HeroScan() - ) -} - -/** For future sources: when testing and popularMangaRequest() returns a Jsoup error instead of results - * most likely the fix is to override popularMangaNextPageSelector() */ - -class LHTranslation : FMReader("LHTranslation", "https://lhtranslation.net", "en") - -class KissLove : FMReader("KissLove", "https://kissaway.net", "ja") { - override fun pageListParse(document: Document): List = base64PageListParse(document) -} - -class HanaScan : FMReader("HanaScan (RawQQ)", "https://hanascan.com", "ja") { - override fun popularMangaNextPageSelector() = "div.col-md-8 button" - // Referer needs to be chapter URL - override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) -} - -class HeroScan : FMReader("HeroScan", "https://heroscan.com", "en") { - override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor { chain -> - val originalRequest = chain.request() - chain.proceed(originalRequest).let { response -> - if (response.code() == 403 && originalRequest.url().host().contains("b-cdn")) { - response.close() - chain.proceed(originalRequest.newBuilder().removeHeader("Referer").addHeader("Referer", "https://isekaiscan.com").build()) - } else { - response - } - } - } - .build() -} - -class RawLH : FMReader("RawLH", "https://lovehug.net", "ja") { - override val chapterUrlSelector = "" - override fun pageListParse(document: Document): List = base64PageListParse(document) - // Referer needs to be chapter URL - override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) -} - -class Manhwa18 : FMReader("Manhwa18", "https://manhwa18.com", "en") { - override fun imageRequest(page: Page): Request { - return if (page.imageUrl!!.contains("manhwa18")) { - super.imageRequest(page) - } else { - GET(page.imageUrl!!, headers.newBuilder().removeAll("Referer").build()) - } - } - override fun getGenreList() = getAdultGenreList() -} - -class EighteenLHPlus : FMReader("18LHPlus", "https://18lhplus.com", "en") { - override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor { chain -> - val originalRequest = chain.request() - chain.proceed(originalRequest).let { response -> - if (response.code() == 403 && originalRequest.url().host().contains("mkklcdn")) { - response.close() - chain.proceed(originalRequest.newBuilder().removeHeader("Referer").addHeader("Referer", "https://manganelo.com").build()) - } else { - response - } - } - } - .build() - override fun popularMangaNextPageSelector() = "div.col-lg-8 div.btn-group:first-of-type" - override fun getGenreList() = getAdultGenreList() -} - -class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - } - override fun popularMangaNextPageSelector() = "div.btn-group:not(div.btn-block) button.btn-info" - // TODO: genre search possible but a bit of a pain - override fun getFilterList() = FilterList() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/arama.html?icerik=$query", headers) - override fun searchMangaParse(response: Response): MangasPage { - val mangas = mutableListOf() - - response.asJsoup().select("div.row a[data-toggle]") - .filterNot { it.siblingElements().text().contains("Novel") } - .map { mangas.add(searchMangaFromElement(it)) } - - return MangasPage(mangas, false) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("abs:href")) - manga.title = element.text() - - return manga - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - val infoElement = document.select("div#tab1").first() - - manga.author = infoElement.select("table + table tr + tr td a").first()?.text() - manga.artist = infoElement.select("table + table tr + tr td + td a").first()?.text() - manga.genre = infoElement.select("div#tab1 table + table tr + tr td + td + td").text() - manga.status = parseStatus(infoElement.select("div#tab1 table tr + tr td a").first().text()) - manga.description = infoElement.select("div.well").text().trim() - manga.thumbnail_url = document.select("img.thumbnail").attr("abs:src") - - return manga - } - - override fun chapterListSelector() = "tr.table-bordered" - override val chapterUrlSelector = "td[align=left] > a" - override val chapterTimeSelector = "td[align=right]" - private val chapterListHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest").build() - override fun fetchChapterList(manga: SManga): Observable> { - val requestUrl = "$baseUrl/cek/fetch_pages_manga.php?manga_cek=${manga.url.substringAfter("manga-").substringBefore(".")}" - return client.newCall(GET(requestUrl, chapterListHeaders)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, requestUrl) - } - } - - private fun chapterListParse(response: Response, requestUrl: String): List { - val chapters = mutableListOf() - var document = response.asJsoup() - var moreChapters = true - var nextPage = 2 - - // chapters are paginated - while (moreChapters) { - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - if (document.select("a[data-page=$nextPage]").isNotEmpty()) { - val body = FormBody.Builder() - .add("page", nextPage.toString()) - .build() - document = client.newCall(POST(requestUrl, chapterListHeaders, body)).execute().asJsoup() - nextPage++ - } else { - moreChapters = false - } - } - return chapters - } - - override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/${chapter.url.substringAfter("cek/")}", headers) -} - -class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") { - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC&ungenre=raw", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC&ungenre=raw", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val noRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("ungenre", "raw").toString() - return GET(noRawsUrl, headers) - } - - override fun getGenreList() = getAdultGenreList() -} - -class Manhwa18NetRaw : FMReader("Manhwa18.net Raw", "https://manhwa18.net", "ko") { - override val requestPath = "manga-list-genre-raw.html" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("genre", "raw").toString() - return GET(onlyRawsUrl, headers) - } - - override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) }) -} - -class SayTruyen : FMReader("Say Truyen", "https://saytruyen.com", "vi") { - override fun mangaDetailsParse(document: Document): SManga { - val info = document.select("div.row").first() - return SManga.create().apply { - author = info.select("div.row li:has(b:contains(Tác giả)) small").text() - genre = info.select("div.row li:has(b:contains(Thể loại)) small a").joinToString { it.text() } - status = parseStatus(info.select("div.row li:has(b:contains(Tình trạng)) a").text()) - description = document.select("div.description").text() - thumbnail_url = info.select("img.thumbnail").attr("abs:src") - } - } - override fun chapterListParse(response: Response): List { - return response.asJsoup().let { document -> - document.select(chapterListSelector()).map { - chapterFromElement(it).apply { - scanlator = document.select("div.row li:has(b:contains(Nhóm dịch)) small").text() - } - } - } - } - override fun pageListParse(document: Document): List = super.pageListParse(document).onEach { it.imageUrl!!.trim() } -} - -class EpikManga : FMReader("Epik Manga", "https://www.epikmanga.com", "tr") { - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=views&sorting-type=DESC&Sayfa=$page", headers) - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/seri-listesi?sorting=lastUpdate&sorting-type=DESC&Sayfa=$page", headers) - override fun popularMangaNextPageSelector() = "ul.pagination li.active + li:not(.disabled)" - - override val headerSelector = "h4 a" - - // search wasn't working on source's website - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/seri-listesi?type=text", headers) - private fun searchMangaParse(response: Response, query: String): MangasPage { - val mangas = response.asJsoup().select("div.char.col-lg-4 a") - .filter { it.text().contains(query, ignoreCase = true) } - .map { - SManga.create().apply { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - } - return MangasPage(mangas, false) - } - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.col-md-9 div.row").first() - - return SManga.create().apply { - status = parseStatus(infoElement.select("h4:contains(Durum:)").firstOrNull()?.ownText()) - author = infoElement.select("h4:contains(Yazar:)").firstOrNull()?.ownText() - artist = infoElement.select("h4:contains(Çizer:)").firstOrNull()?.ownText() - genre = infoElement.select("h4:contains(Türler:) a").joinToString { it.text() } - thumbnail_url = infoElement.select("img.thumbnail").imgAttr() - description = document.select("div.col-md-12 p").text() - } - } - override fun chapterListSelector() = "table.table tbody tr" - override fun getFilterList(): FilterList = FilterList() -} - -class ManhuaScan : FMReader("ManhuaScan", "https://manhuascan.com", "en") - -class ManhwaSmut : FMReader("ManhwaSmut", "https://manhwasmut.com", "en") { - private val noReferer = headersBuilder().removeAll("Referer").build() - override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, if (page.imageUrl!!.contains("toonily")) noReferer else headers) -}