Remove Bainian Manhua: site is down (#12838)
| @ -1,2 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest package="eu.kanade.tachiyomi.extension" /> | ||||
| @ -1,11 +0,0 @@ | ||||
| apply plugin: 'com.android.application' | ||||
| apply plugin: 'kotlin-android' | ||||
| 
 | ||||
| ext { | ||||
|     extName = 'Bainian Manhua' | ||||
|     pkgNameSuffix = 'zh.bainianmanga' | ||||
|     extClass = '.Bainian' | ||||
|     extVersionCode = 8 | ||||
| } | ||||
| 
 | ||||
| apply from: "$rootDir/common.gradle" | ||||
| Before Width: | Height: | Size: 3.3 KiB | 
| Before Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 4.4 KiB | 
| Before Width: | Height: | Size: 8.1 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 56 KiB | 
| @ -1,205 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.extension.zh.bainianmanga | ||||
| 
 | ||||
| import android.app.Application | ||||
| import android.content.SharedPreferences | ||||
| import androidx.preference.PreferenceScreen | ||||
| import androidx.preference.SwitchPreferenceCompat | ||||
| import eu.kanade.tachiyomi.AppInfo | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.interceptor.rateLimit | ||||
| 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.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 org.jsoup.select.Evaluator | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.Locale | ||||
| 
 | ||||
| class Bainian : ParsedHttpSource(), ConfigurableSource { | ||||
| 
 | ||||
|     override val name = "百年漫画" | ||||
|     override val lang = "zh" | ||||
|     override val supportsLatest = true | ||||
| 
 | ||||
|     private val preferences: SharedPreferences = | ||||
|         Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) | ||||
| 
 | ||||
|     private val useMirror = preferences.getBoolean(USE_MIRROR_PREF, false) | ||||
|     override val baseUrl = if (useMirror) "https://www.mhqj.com" else "https://www.bnman.net" | ||||
|     private fun String.stripMirror() = if (useMirror) "/comic" + removePrefix("/manhuadaquan") else this | ||||
|     private fun String.toMirror() = if (useMirror) baseUrl + "/manhuadaquan" + removePrefix("/comic") else baseUrl + this | ||||
| 
 | ||||
|     override val client = network.client.newBuilder() | ||||
|         .rateLimit(2) | ||||
|         .addInterceptor(MimeInterceptor) | ||||
|         .build() | ||||
| 
 | ||||
|     override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/hot/$page.html", headers) | ||||
|     override fun popularMangaNextPageSelector() = ".pagination > li:last-child > a" | ||||
|     override fun popularMangaSelector() = "ul#list_img > li > a" | ||||
|     override fun popularMangaFromElement(element: Element) = SManga.create().apply { | ||||
|         url = element.attr("href").stripMirror() | ||||
|         val children = element.children() | ||||
|         title = children[2].text() | ||||
|         thumbnail_url = children[0].attr("src") | ||||
|     } | ||||
| 
 | ||||
|     override fun popularMangaParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|         if (filters.isEmpty()) parseFilters(document) // parse filters here | ||||
|         val mangas = document.select(popularMangaSelector()).map(::popularMangaFromElement) | ||||
|         val hasNextPage = document.selectFirst(popularMangaNextPageSelector()) != null | ||||
|         return MangasPage(mangas, hasNextPage) | ||||
|     } | ||||
| 
 | ||||
|     override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/new/$page.html", headers) | ||||
|     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() | ||||
|     override fun latestUpdatesSelector() = popularMangaSelector() | ||||
|     override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) | ||||
| 
 | ||||
|     override fun latestUpdatesParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|         if (filters.isEmpty()) parseFilters(document) // parse filters here | ||||
|         val mangas = document.select(latestUpdatesSelector()).map(::latestUpdatesFromElement) | ||||
|         val hasNextPage = document.selectFirst(latestUpdatesNextPageSelector()) != null | ||||
|         return MangasPage(mangas, hasNextPage) | ||||
|     } | ||||
| 
 | ||||
|     override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
|     override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         if (query.isNotEmpty()) { | ||||
|             return GET("$baseUrl/search/$query/$page.html", headers) | ||||
|         } | ||||
|         for (filter in filters) if (filter is CategoryFilter) { | ||||
|             val path = filter.getPath() | ||||
|             if (path.isNotEmpty()) return GET("$baseUrl$path/$page.html", headers) | ||||
|         } | ||||
|         return popularMangaRequest(page) | ||||
|     } | ||||
| 
 | ||||
|     override fun mangaDetailsRequest(manga: SManga) = GET(manga.url.toMirror(), headers) | ||||
|     override fun mangaDetailsParse(document: Document) = SManga.create().apply { | ||||
|         val details = document.selectFirst(Evaluator.Class("info")).child(0).children() | ||||
|         title = details[0].text() | ||||
|         author = details[3].child(1).text() | ||||
|         description = document.selectFirst(Evaluator.Class("mt10")).text() | ||||
|         genre = "${details[2].child(1).text()}, ${details[4].child(1).text()}" | ||||
|         status = when (document.selectFirst(".title01 > h2").text()) { | ||||
|             "连载中" -> SManga.ONGOING | ||||
|             "已完结" -> SManga.COMPLETED | ||||
|             else -> SManga.UNKNOWN | ||||
|         } | ||||
|         thumbnail_url = document.selectFirst(".bpic > img").attr("src") | ||||
|         initialized = true | ||||
|     } | ||||
| 
 | ||||
|     override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) | ||||
|     override fun chapterListSelector() = "ul.jslist01 > li > a:not([href^=http])" | ||||
|     override fun chapterFromElement(element: Element) = SChapter.create().apply { | ||||
|         url = element.attr("href").stripMirror() | ||||
|         name = element.text() | ||||
|     } | ||||
| 
 | ||||
|     override fun chapterListParse(response: Response): List<SChapter> { | ||||
|         val document = response.asJsoup() | ||||
|         val list = document.select(chapterListSelector()).map { chapterFromElement(it) } | ||||
|         if (list.isNotEmpty() && isNewDateLogic) { | ||||
|             // div.info > ul:eq(0) > li:eq(5) > p:eq(1) | ||||
|             val date = document.selectFirst(Evaluator.Class("info")).child(0).child(5).child(1).text() | ||||
|             list[0].date_upload = dateFormat.parse(date)?.time ?: 0 | ||||
|         } | ||||
|         return list | ||||
|     } | ||||
| 
 | ||||
|     override fun pageListRequest(chapter: SChapter) = GET(chapter.url.toMirror(), headers) | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         val script = document.selectFirst("body > script").data() | ||||
|         val leftIndex = script.indexOf('[') | ||||
|         val rightIndex = script.lastIndexOf(']') | ||||
|         if (rightIndex - leftIndex <= 1) return emptyList() // empty string or empty list | ||||
|         // '["...","..."]' - check baseUrl/static/manhua/comic.js | ||||
|         val images = script.substring(leftIndex + 2, rightIndex - 1).replace("\\", "").split("\",\"") | ||||
|         return images.mapIndexed { i, it -> | ||||
|             val imageUrl = when { | ||||
|                 it.startsWith("http") -> it.replace("cxcyfhpt.com", "ygeol.net") | ||||
|                 else -> "https://img.jiequegongchang.com/$it" | ||||
|             } | ||||
|             // some hosts have invalid SSL certificates | ||||
|             val imageUrlInHttp = "http:" + imageUrl.substringAfter(':') | ||||
|             Page(i, imageUrl = imageUrlInHttp) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") | ||||
| 
 | ||||
|     private class CategoryFilter(name: String, values: Array<String>, private val paths: List<String>) : | ||||
|         Filter.Select<String>(name, values) { | ||||
|         fun getPath() = paths[state] | ||||
|     } | ||||
| 
 | ||||
|     private class FilterData(private val name: String, private val values: Array<String>, private val paths: List<String>) { | ||||
|         fun toFilter() = CategoryFilter(name, values, paths) | ||||
|     } | ||||
| 
 | ||||
|     private var filters: List<FilterData> = emptyList() | ||||
| 
 | ||||
|     private fun parseFilters(document: Document) { | ||||
|         val categories = document.selectFirst(Evaluator.Class("select")).child(0).children().filter { it.tagName() == "dl" } | ||||
|         filters = categories.map { element -> | ||||
|             val children = element.children() | ||||
|             val size = children.size | ||||
|             val values = ArrayList<String>(size).apply { add("全部") } | ||||
|             val paths = ArrayList<String>(size).apply { add("") } | ||||
|             val iterator = children.iterator().apply { next() } // skip first | ||||
|             while (iterator.hasNext()) { | ||||
|                 val next = iterator.next() | ||||
|                 values.add(next.text()) | ||||
|                 paths.add(next.child(0).attr("href").removeSuffix(".html")) | ||||
|             } | ||||
|             FilterData(children[0].text(), values.toTypedArray(), paths) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun getFilterList(): FilterList { | ||||
|         val list: List<Filter<*>> = | ||||
|             if (filters.isNotEmpty()) buildList(filters.size + 3) { | ||||
|                 add(Filter.Header("如果使用文本搜索,将会忽略分类筛选")) | ||||
|                 add(Filter.Header("最多使用一个分类筛选,多选时只有第一个有效")) | ||||
|                 add(Filter.Header("提示:分类筛选非常不准")) | ||||
|                 filters.forEach { add(it.toFilter()) } | ||||
|             } else buildList(2) { | ||||
|                 add(Filter.Header("点击“重置”即可刷新分类,如果失败,")) | ||||
|                 add(Filter.Header("请尝试重新从图源列表点击进入图源")) | ||||
|             } | ||||
|         return FilterList(list) | ||||
|     } | ||||
| 
 | ||||
|     override fun setupPreferenceScreen(screen: PreferenceScreen) { | ||||
|         SwitchPreferenceCompat(screen.context).apply { | ||||
|             key = USE_MIRROR_PREF | ||||
|             title = "使用镜像网站" | ||||
|             summary = "使用“漫画全集”网站,重启生效" | ||||
|             setDefaultValue(false) | ||||
|         }.let { screen.addPreference(it) } | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         private const val USE_MIRROR_PREF = "USE_MIRROR" | ||||
| 
 | ||||
|         private val isNewDateLogic = AppInfo.getVersionCode() >= 81 | ||||
|         private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } | ||||
|     } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.extension.zh.bainianmanga | ||||
| 
 | ||||
| import okhttp3.Interceptor | ||||
| import okhttp3.MediaType.Companion.toMediaType | ||||
| import okhttp3.Response | ||||
| import okhttp3.ResponseBody.Companion.asResponseBody | ||||
| 
 | ||||
| object MimeInterceptor : Interceptor { | ||||
|     override fun intercept(chain: Interceptor.Chain): Response { | ||||
|         val request = chain.request() | ||||
|         val response = chain.proceed(request) | ||||
|         if (response.header("Content-Type") == "application/octet-stream" && | ||||
|             request.url.toString().contains(".jpg") | ||||
|         ) { | ||||
|             val body = response.body!!.source().asResponseBody(jpegMime) | ||||
|             return response.newBuilder().body(body).build() | ||||
|         } | ||||
|         return response | ||||
|     } | ||||
| 
 | ||||
|     private val jpegMime = "image/jpeg".toMediaType() | ||||
| } | ||||
 stevenyomi
						stevenyomi