diff --git a/src/all/junmeitu/AndroidManifest.xml b/src/all/junmeitu/AndroidManifest.xml new file mode 100644 index 000000000..b4571bfa8 --- /dev/null +++ b/src/all/junmeitu/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/all/junmeitu/build.gradle b/src/all/junmeitu/build.gradle new file mode 100644 index 000000000..ea226bb22 --- /dev/null +++ b/src/all/junmeitu/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Junmei tu' + pkgNameSuffix = 'all.junmeitu' + extClass = '.JunmeiTu' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/junmeitu/res/mipmap-hdpi/ic_launcher.png b/src/all/junmeitu/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..297299abd Binary files /dev/null and b/src/all/junmeitu/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/junmeitu/res/mipmap-mdpi/ic_launcher.png b/src/all/junmeitu/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..e69410704 Binary files /dev/null and b/src/all/junmeitu/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/junmeitu/res/mipmap-xhdpi/ic_launcher.png b/src/all/junmeitu/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..26508df4b Binary files /dev/null and b/src/all/junmeitu/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/junmeitu/res/mipmap-xxhdpi/ic_launcher.png b/src/all/junmeitu/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..83b2ff735 Binary files /dev/null and b/src/all/junmeitu/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/junmeitu/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/junmeitu/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..9047b6db1 Binary files /dev/null and b/src/all/junmeitu/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/junmeitu/res/web_hi_res_512.png b/src/all/junmeitu/res/web_hi_res_512.png new file mode 100644 index 000000000..3c7c7317a Binary files /dev/null and b/src/all/junmeitu/res/web_hi_res_512.png differ diff --git a/src/all/junmeitu/src/eu/kanade/tachiyomi/extension/all/junmeitu/JunmeiTu.kt b/src/all/junmeitu/src/eu/kanade/tachiyomi/extension/all/junmeitu/JunmeiTu.kt new file mode 100644 index 000000000..3ce103d56 --- /dev/null +++ b/src/all/junmeitu/src/eu/kanade/tachiyomi/extension/all/junmeitu/JunmeiTu.kt @@ -0,0 +1,211 @@ +package eu.kanade.tachiyomi.extension.all.junmeitu + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.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 org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class JunmeiTu() : ConfigurableSource, ParsedHttpSource() { + override val lang = "all" + override val name = "Junmei tu" + override val supportsLatest = true + + // Preference + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override val baseUrl: String = getMirrorPref()!! + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val mirrorPref = ListPreference(screen.context).apply { + key = "${MIRROR_PREF_KEY}_$lang" + title = MIRROR_PREF_TITLE + entries = MIRROR_PREF_ENTRIES + entryValues = MIRROR_PREF_ENTRY_VALUES + setDefaultValue(MIRROR_PREF_DEFAULT_VALUE) + summary = "%s" + + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + val index = findIndexOfValue(selected) + val entry = entryValues[index] as String + preferences.edit().putString("${MIRROR_PREF_KEY}_$lang", entry).commit() + } + } + screen.addPreference(mirrorPref) + } + + private fun getMirrorPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE) + + companion object { + private const val MIRROR_PREF_KEY = "MIRROR" + private const val MIRROR_PREF_TITLE = "Mirror" + private val MIRROR_PREF_ENTRIES = arrayOf("Junmeitu.com", "Meijuntu.com") + private val MIRROR_PREF_ENTRY_VALUES = arrayOf("https://junmeitu.com", "https://meijuntu.com") + private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0] + } + + // Latest + override fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("img").attr("abs:src") + manga.title = element.select("p").text() + manga.setUrlWithoutDomain(element.select("a").attr("abs:href")) + return manga + } + + override fun latestUpdatesNextPageSelector() = "span + a + a" + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/beauty/index-$page.html") + } + + override fun latestUpdatesSelector() = ".pic-list > ul > li" + + // Popular + override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) + override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/beauty/hot-$page.html") + } + override fun popularMangaSelector() = latestUpdatesSelector() + + // Search + override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) + override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val tagFilter = filters.findInstance() + val modelFilter = filters.findInstance() + val groupFilter = filters.findInstance() + val categoryFilter = filters.findInstance() + val sortFilter = filters.findInstance() + return when { + query.isNotEmpty() -> GET("$baseUrl/search/$query-$page.html") + tagFilter!!.state.isNotEmpty() -> GET("$baseUrl/tags/${tagFilter.state}-${categoryFilter!!.selected}-$page.html") + modelFilter!!.state.isNotEmpty() -> GET("$baseUrl/model/${modelFilter.state}-$page.html") + groupFilter!!.state.isNotEmpty() -> GET("$baseUrl/xzjg/${groupFilter.state}-$page.html") + categoryFilter!!.state != 0 -> GET("$baseUrl/${categoryFilter.slug}/${sortFilter!!.selected}-$page.html") + sortFilter!!.state != 0 -> GET("$baseUrl/${categoryFilter.slug}/${sortFilter.selected}-$page.html") + else -> latestUpdatesRequest(page) + } + } + override fun searchMangaSelector() = latestUpdatesSelector() + + // Details + override fun mangaDetailsParse(document: Document): SManga { + val manga = SManga.create() + manga.title = document.selectFirst(".news-title,.title").text() + manga.description = document.select(".news-info,.picture-details").text() + "\n" + document.select(".introduce").text() + val genres = mutableListOf() + document.select(".relation_tags > a").forEach { + genres.add(it.text()) + } + manga.genre = genres.joinToString(", ") + return manga + } + + override fun chapterFromElement(element: Element): SChapter { + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(element.select(".position a:last-child").first().attr("abs:href")) + chapter.chapter_number = 0F + chapter.name = element.select(".news-title,.title").text() + return chapter + } + + override fun chapterListSelector() = "html" + + // Pages + override fun pageListParse(document: Document): List { + val numPages = document.select(".pages > a:nth-last-of-type(2)").text().toIntOrNull() + var url = document.select(".position a:last-child").first().attr("abs:href") + val pages = mutableListOf() + var count = 0 + if (numPages != null) { + url = url.substringBeforeLast(".") + while (numPages != count) { + count += 1 + val doc = when (count) { + 1 -> document + else -> client.newCall(GET("$url-$count.html")).execute().asJsoup() + } + doc.select(".pictures img,.news-body img").forEachIndexed() { index, it -> + val imgUrl = when { + it.hasAttr("data-original") -> it.attr("abs:data-original") + it.hasAttr("data-src") -> it.attr("abs:data-src") + it.hasAttr("data-lazy-src") -> it.attr("abs:data-lazy-src") + else -> it.attr("abs:src") + } + pages.add(Page(index, imgUrl, imgUrl)) + } + } + } else { + document.select(".pictures img,.news-body img").forEachIndexed() { index, it -> + val imgUrl = when { + it.hasAttr("data-original") -> it.attr("abs:data-original") + it.hasAttr("data-src") -> it.attr("abs:data-src") + it.hasAttr("data-lazy-src") -> it.attr("abs:data-lazy-src") + else -> it.attr("abs:src") + } + pages.add(Page(index, imgUrl, imgUrl)) + } + } + return pages + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") + + // Filters + override fun getFilterList(): FilterList = FilterList( + Filter.Header("NOTE: Ignored if using text search!"), + Filter.Header("NOTE: Filter are weird for this extension!"), + Filter.Separator(), + TagFilter(), + ModelFilter(), + GroupFilter(), + CategoryFilter(getCategoryFilter(), 0), + SortFilter(getSortFilter(), 0) + ) + + class SelectFilterOption(val name: String, val value: String = name) + 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 + val slug: String + get() = options[state].name + } + + class TagFilter : Filter.Text("Tag") + class ModelFilter : Filter.Text("Model") + class GroupFilter : Filter.Text("Group") + class CategoryFilter(options: List, default: Int) : SelectFilter("Category", options, default) + class SortFilter(options: List, default: Int) : SelectFilter("Sort", options, default) + + private fun getCategoryFilter() = listOf( + SelectFilterOption("beauty", "6"), + SelectFilterOption("handsome", "5"), + SelectFilterOption("news", "30"), + SelectFilterOption("street", "32"), + ) + + private fun getSortFilter() = listOf( + SelectFilterOption("default", "index"), + SelectFilterOption("hot"), + ) + + private inline fun Iterable<*>.findInstance() = find { it is T } as? T +}