Remove Pufei Manhua: site is down (#12826)

This commit is contained in:
stevenyomi 2022-08-07 05:29:59 +08:00 committed by GitHub
parent 4a54a8c801
commit 5975d2528e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 0 additions and 418 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,15 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Pufei Manhua'
pkgNameSuffix = 'zh.pufei'
extClass = '.Pufei'
extVersionCode = 10
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation 'com.github.stevenyomi:unpacker:12a09e3c1a' // 1.1
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,58 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.pufei
import android.os.SystemClock
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.TimeUnit
// See https://github.com/tachiyomiorg/tachiyomi/pull/7389
internal class NonblockingRateLimiter(
private val permits: Int,
period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS,
) : Interceptor {
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
override fun intercept(chain: Interceptor.Chain): Response {
// Ignore canceled calls, otherwise they would jam the queue
if (chain.call().isCanceled()) {
throw IOException()
}
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
// Final check
if (chain.call().isCanceled()) {
throw IOException()
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.pufei
import eu.kanade.tachiyomi.network.GET
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response
import okhttp3.ResponseBody.Companion.asResponseBody
object OctetStreamInterceptor : 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") {
return response
}
if (response.header("Content-Length")!!.toInt() < 100) { // usually 96
// The actual URL is '/.../xxx.jpg/0'.
val peek = response.peekBody(100).string()
if (peek.startsWith("The actual URL")) {
response.body!!.close()
val actualPath = peek.substringAfter('\'').substringBeforeLast('\'')
return chain.proceed(GET("https://manhua.acimg.cn$actualPath"))
}
}
val url = request.url.encodedPath
val mediaType = when {
url.endsWith(".h") -> webpMediaType
url.contains(".jpg") -> jpegMediaType
else -> return response
}
val body = response.body!!.source().asResponseBody(mediaType)
return response.newBuilder().body(body).build()
}
private val jpegMediaType = "image/jpeg".toMediaType()
private val webpMediaType = "image/webp".toMediaType()
}

View File

@ -1,223 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.pufei
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import com.github.stevenyomi.unpacker.ProgressiveParser
import com.github.stevenyomi.unpacker.Unpacker
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
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 okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
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
// Uses www733dm/IMH/dm456 theme
class Pufei : 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 domain = preferences.getString(MIRROR_PREF, "0")!!
.toInt().coerceAtMost(MIRRORS.size - 1).let { MIRRORS[it] }
override val baseUrl = "http://m.$domain"
private val pcUrl = "http://www.$domain"
override val client = network.client.newBuilder()
.addInterceptor(NonblockingRateLimiter(2))
.addInterceptor(OctetStreamInterceptor)
.build()
private val searchClient = network.client.newBuilder()
.followRedirects(false)
.build()
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manhua/paihang.html", headers)
override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used.")
override fun popularMangaSelector() = "ul#detail > li > a"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
url = element.attr("href").removeSuffix("/index.html")
title = element.selectFirst(Evaluator.Tag("h3")).text()
thumbnail_url = element.selectFirst(Evaluator.Tag("img")).attr("data-src")
}
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asPufeiJsoup()
val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
return MangasPage(mangas, false)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manhua/update.html", headers)
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asPufeiJsoup()
val mangas = document.select(latestUpdatesSelector()).map { latestUpdatesFromElement(it) }
return MangasPage(mangas, false)
}
private val searchCache = HashMap<String, String>()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (query.isNotBlank()) {
val path = searchCache.getOrPut(query) {
val formBody = FormBody.Builder(GB2312)
.addEncoded("tempid", "4")
.addEncoded("show", "title,player,playadmin,bieming,pinyin")
.add("keyboard", query)
.build()
val request = POST("$baseUrl/e/search/index.php", headers, formBody)
searchClient.newCall(request).execute().header("location")!!
}
val sortQuery = parseSearchSort(filters)
GET("$baseUrl/e/search/$path$sortQuery&page=${page - 1}")
} else {
val path = parseFilters(page, filters)
if (path.isEmpty())
popularMangaRequest(page)
else
GET("$baseUrl$path", headers)
}
}
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used.")
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asPufeiJsoup()
val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) }
val hasNextPage = run {
for (element in document.body().children().asReversed()) {
if (element.tagName() == "a") return@run true
else if (element.tagName() == "b") return@run false
}
false
}
return MangasPage(mangas, hasNextPage)
}
override fun getFilterList() = getFilters()
// 让 WebView 显示移动端页面
override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
client.newCall(GET(pcUrl + manga.urlWithCheck(), headers)).asObservableSuccess()
.map { mangaDetailsParse(it.asPufeiJsoup()) }
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val details = document.selectFirst(Evaluator.Class("detailInfo")).children()
title = details[0].child(0).text() // div.titleInfo > h1
val genreList = mutableListOf<String>()
for (item in details[1].children()) { // ul > li
when (item.child(0).text()) { // span
"作者:" -> author = item.ownText()
"类别:" -> item.ownText().let { if (it.isNotEmpty()) genreList.add(it) }
"关键词:" -> item.ownText().let { if (it.isNotEmpty()) genreList.addAll(it.split(',')) }
}
}
author = author ?: details[0].ownText().removePrefix("作者:")
if (genreList.isEmpty()) {
genreList.add(document.selectFirst(Evaluator.Class("position")).child(1).text())
}
genre = genreList.joinToString()
description = document.selectFirst("div.introduction")?.text() ?: details[2].ownText()
status = SManga.UNKNOWN // 所有漫画的标记都是连载,所以没有意义,参见 baseUrl/manhua/wanjie.html
thumbnail_url = document.selectFirst("img.pic").attr("src")
}
override fun chapterListRequest(manga: SManga) = GET(pcUrl + manga.urlWithCheck(), headers)
override fun chapterListSelector() = "div.plistBox ul > li > a"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
url = element.attr("href")
name = element.attr("title")
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asPufeiJsoup()
val list = document.select(chapterListSelector()).map { chapterFromElement(it) }
if (isNewDateLogic && list.isNotEmpty()) {
val date = document.selectFirst("li.twoCol:contains(更新时间)").text().removePrefix("更新时间:").trim()
list[0].date_upload = dateFormat.parse(date)?.time ?: 0
}
return list
}
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers)
override fun pageListParse(response: Response): List<Page> {
val html = String(response.body!!.bytes(), GB2312).let(::ProgressiveParser)
val base64 = html.substringBetween("cp=\"", "\"")
val script = String(Base64.decode(base64, Base64.DEFAULT))
val result = Unpacker.unpack(script, "[", "]")
.ifEmpty { return emptyList() }
.replace("\\", "")
.removeSurrounding("\"").split("\",\"")
// baseUrl/skin/2014mh/view.js (imgserver), mobileUrl/skin/main.js (IMH.reader)
return result.mapIndexed { i, image ->
val imageUrl = if (image.startsWith("http")) image else IMAGE_SERVER + image
Page(i, imageUrl = imageUrl)
}
}
override fun pageListParse(document: Document) = throw UnsupportedOperationException("Not used.")
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = MIRROR_PREF
title = "使用镜像网站"
summary = "选择要使用的镜像网站,重启生效\n已选择:%s"
entries = MIRRORS_DESCRIPTION
entryValues = MIRROR_VALUES
setDefaultValue("0")
}.let { screen.addPreference(it) }
}
companion object {
private const val MIRROR_PREF = "MIRROR"
private val MIRROR_VALUES = arrayOf("0", "1", "2", "3", "4")
private val MIRRORS = arrayOf(
"pufei.cc",
"pfmh.net",
"alimanhua.com",
"8nfw.com",
"pufei5.com",
)
private val MIRRORS_DESCRIPTION = arrayOf(
"pufei.cc",
"pfmh.net",
"alimanhua.com (阿狸漫画)",
"8nfw.com (风之动漫)",
"pufei5.com (不推荐)",
)
private const val IMAGE_SERVER = "http://res.img.tueqi.com/"
}
}

View File

@ -1,51 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.pufei
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
internal fun getFilters() = FilterList(
Filter.Header("排序只对文本搜索和分类筛选有效"),
SortFilter(),
Filter.Separator(),
Filter.Header("以下筛选最多使用一个,使用文本搜索时将会忽略"),
CategoryFilter(),
AlphabetFilter(),
)
internal fun parseSearchSort(filters: FilterList): String =
filters.filterIsInstance<SortFilter>().firstOrNull()?.let { SORT_QUERIES[it.state] } ?: ""
internal fun parseFilters(page: Int, filters: FilterList): String {
val pageStr = if (page == 1) "" else "_$page"
var category = 0
var categorySort = 0
var alphabet = 0
for (filter in filters) when (filter) {
is SortFilter -> categorySort = filter.state
is CategoryFilter -> category = filter.state
is AlphabetFilter -> alphabet = filter.state
else -> {}
}
return if (category > 0) {
"/${CATEGORY_KEYS[category]}/${SORT_KEYS[categorySort]}$pageStr.html"
} else if (alphabet > 0) {
"/mh/${ALPHABET[alphabet].lowercase()}/index$pageStr.html"
} else {
""
}
}
internal class SortFilter : Filter.Select<String>("排序", SORT_NAMES)
private val SORT_NAMES = arrayOf("添加时间", "更新时间", "点击次数")
private val SORT_KEYS = arrayOf("index", "update", "view")
private val SORT_QUERIES = arrayOf("&orderby=newstime", "&orderby=lastdotime", "&orderby=onclick")
internal class CategoryFilter : Filter.Select<String>("分类", CATEGORY_NAMES)
private val CATEGORY_NAMES = arrayOf("全部", "少年热血", "少女爱情", "武侠格斗", "科幻魔幻", "竞技体育", "搞笑喜剧", "耽美人生", "侦探推理", "恐怖灵异")
private val CATEGORY_KEYS = arrayOf("", "shaonianrexue", "shaonvaiqing", "wuxiagedou", "kehuan", "jingjitiyu", "gaoxiaoxiju", "danmeirensheng", "zhentantuili", "kongbulingyi")
internal class AlphabetFilter : Filter.Select<String>("字母", ALPHABET)
private val ALPHABET = arrayOf("全部", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")

View File

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.pufei
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
internal val GB2312 = charset("GB2312")
internal fun Response.asPufeiJsoup(): Document =
Jsoup.parse(String(body!!.bytes(), GB2312), request.url.toString())
internal fun SManga.urlWithCheck(): String {
val result = url
if (result.endsWith("/index.html")) {
throw Exception("作品地址格式过期,请迁移更新")
}
return result
}
internal val isNewDateLogic = AppInfo.getVersionCode() >= 81
internal val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH)
}