mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 10:22:47 +01:00
Add Dm5 (#492)
This commit is contained in:
parent
6db59cbe0d
commit
b5cca97594
2
src/zh/dm5/AndroidManifest.xml
Normal file
2
src/zh/dm5/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
12
src/zh/dm5/build.gradle
Normal file
12
src/zh/dm5/build.gradle
Normal file
@ -0,0 +1,12 @@
|
||||
ext {
|
||||
extName = 'Dm5'
|
||||
extClass = '.Dm5'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:unpacker'))
|
||||
}
|
BIN
src/zh/dm5/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/zh/dm5/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
src/zh/dm5/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/zh/dm5/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
src/zh/dm5/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/zh/dm5/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
src/zh/dm5/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/zh/dm5/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
BIN
src/zh/dm5/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/zh/dm5/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
@ -0,0 +1,91 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.dm5
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
import android.text.TextPaint
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.jsoup.parser.Parser
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
// This file is modified from DMZJ extension
|
||||
|
||||
val json: Json by injectLazy()
|
||||
|
||||
@Serializable
|
||||
class ChapterCommentDto(
|
||||
val PostContent: String,
|
||||
val Poster: String,
|
||||
) {
|
||||
override fun toString() = "$Poster: $PostContent"
|
||||
}
|
||||
|
||||
fun parseChapterComments(response: Response): List<String> {
|
||||
val result: List<ChapterCommentDto> = json.decodeFromString(response.body.string())
|
||||
if (result.isEmpty()) return listOf("没有吐槽")
|
||||
return result.map {
|
||||
Parser.unescapeEntities(it.toString(), false)
|
||||
}
|
||||
}
|
||||
|
||||
object CommentsInterceptor : Interceptor {
|
||||
private const val MAX_HEIGHT = 1920
|
||||
private const val WIDTH = 1080
|
||||
private const val UNIT = 32
|
||||
private const val UNIT_F = UNIT.toFloat()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
if (!response.request.url.toString().contains("pagerdata.ashx")) return response
|
||||
|
||||
val comments = parseChapterComments(response)
|
||||
|
||||
val paint = TextPaint().apply {
|
||||
color = Color.BLACK
|
||||
textSize = UNIT_F
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
var height = UNIT
|
||||
val layouts = comments.map {
|
||||
@Suppress("DEPRECATION")
|
||||
StaticLayout(it, paint, WIDTH - 2 * UNIT, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
|
||||
}.takeWhile {
|
||||
val lineHeight = it.height + UNIT
|
||||
if (height + lineHeight <= MAX_HEIGHT) {
|
||||
height += lineHeight
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val bitmap = Bitmap.createBitmap(WIDTH, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.eraseColor(Color.WHITE)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
var y = UNIT
|
||||
for (layout in layouts) {
|
||||
canvas.save()
|
||||
canvas.translate(UNIT_F, y.toFloat())
|
||||
layout.draw(canvas)
|
||||
canvas.restore()
|
||||
y += layout.height + UNIT
|
||||
}
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, output)
|
||||
val body = output.toByteArray().toResponseBody("image/png".toMediaType())
|
||||
return response.newBuilder().body(body).build()
|
||||
}
|
||||
}
|
218
src/zh/dm5/src/eu/kanade/tachiyomi/extension/zh/dm5/Dm5.kt
Normal file
218
src/zh/dm5/src/eu/kanade/tachiyomi/extension/zh/dm5/Dm5.kt
Normal file
@ -0,0 +1,218 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.dm5
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
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.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class Dm5 : ParsedHttpSource(), ConfigurableSource {
|
||||
override val lang = "zh"
|
||||
override val supportsLatest = true
|
||||
override val name = "动漫屋"
|
||||
override val baseUrl = "https://www.dm5.cn"
|
||||
override val client: OkHttpClient = network.client.newBuilder()
|
||||
.addInterceptor(CommentsInterceptor)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
// Some mangas are blocked without this
|
||||
override fun headersBuilder() = super.headersBuilder().set("Accept-Language", "zh-TW")
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manhua-list-p$page/", headers)
|
||||
override fun popularMangaNextPageSelector(): String = "div.page-pagination a:contains(>)"
|
||||
override fun popularMangaSelector(): String = "ul.mh-list > li > div.mh-item"
|
||||
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
title = element.selectFirst("h2.title > a")!!.text()
|
||||
thumbnail_url = element.selectFirst("p.mh-cover")!!.attr("style")
|
||||
.substringAfter("url(").substringBefore(")")
|
||||
url = element.selectFirst("h2.title > a")!!.attr("href")
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manhua-list-s2-p$page/", headers)
|
||||
override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
|
||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
return GET("$baseUrl/search?title=$query&language=1&page=$page", headers)
|
||||
}
|
||||
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
|
||||
override fun searchMangaSelector(): String = "ul.mh-list > li, div.banner_detail_form"
|
||||
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
title = element.selectFirst(".title > a")!!.text()
|
||||
thumbnail_url = element.selectFirst("img")?.attr("src")
|
||||
?: element.selectFirst("p.mh-cover")!!.attr("style")
|
||||
.substringAfter("url(").substringBefore(")")
|
||||
url = element.selectFirst(".title > a")!!.attr("href")
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
title = document.selectFirst("div.banner_detail_form p.title")!!.ownText()
|
||||
thumbnail_url = document.selectFirst("div.banner_detail_form img")!!.attr("abs:src")
|
||||
author = document.selectFirst("div.banner_detail_form p.subtitle > a")!!.text()
|
||||
artist = author
|
||||
genre = document.select("div.banner_detail_form p.tip a").eachText().joinToString(", ")
|
||||
val el = document.selectFirst("div.banner_detail_form p.content")!!
|
||||
description = el.ownText() + el.selectFirst("span")?.ownText().orEmpty()
|
||||
status = when (document.selectFirst("div.banner_detail_form p.tip > span > span")!!.text()) {
|
||||
"连载中" -> SManga.ONGOING
|
||||
"已完结" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val document = response.asJsoup()
|
||||
// May need to click button on website to read
|
||||
document.selectFirst("ul#detail-list-select-1")?.attr("class")
|
||||
?: throw Exception("請到webview確認")
|
||||
val li = document.select("div#chapterlistload li > a").map {
|
||||
SChapter.create().apply {
|
||||
url = it.attr("href")
|
||||
name = if (it.selectFirst("span.detail-lock, span.view-lock") != null) {
|
||||
"\uD83D\uDD12"
|
||||
} else {
|
||||
""
|
||||
} + (it.selectFirst("p.title")?.text() ?: it.text())
|
||||
|
||||
val dateStr = it.selectFirst("p.tip")
|
||||
if (dateStr != null) {
|
||||
date_upload = dateFormat.parse(dateStr.text())?.time ?: 0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort chapter by url (related to upload time)
|
||||
if (preferences.getBoolean(SORT_CHAPTER_PREF, false)) {
|
||||
return li.sortedByDescending { it.url.drop(2).dropLast(1).toInt() }
|
||||
}
|
||||
|
||||
// Sometimes list is in ascending order, probably unread paid manga
|
||||
return if (document.selectFirst("div.detail-list-title a.order")!!.text() == "正序") {
|
||||
li.reversed()
|
||||
} else {
|
||||
li
|
||||
}
|
||||
}
|
||||
override fun chapterListSelector(): String = throw UnsupportedOperationException()
|
||||
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val images = document.select("div#barChapter > img.load-src")
|
||||
val result: ArrayList<Page>
|
||||
val script = document.selectFirst("script:containsData(DM5_MID)")!!.data()
|
||||
if (!script.contains("DM5_VIEWSIGN_DT")) {
|
||||
throw Exception(document.selectFirst("div.view-pay-form p.subtitle")!!.text())
|
||||
}
|
||||
val cid = script.substringAfter("var DM5_CID=").substringBefore(";")
|
||||
if (!images.isEmpty()) {
|
||||
result = images.mapIndexed { index, it ->
|
||||
Page(index, "", it.attr("data-src"))
|
||||
} as ArrayList<Page>
|
||||
} else {
|
||||
val mid = script.substringAfter("var DM5_MID=").substringBefore(";")
|
||||
val dt = script.substringAfter("var DM5_VIEWSIGN_DT=\"").substringBefore("\";")
|
||||
val sign = script.substringAfter("var DM5_VIEWSIGN=\"").substringBefore("\";")
|
||||
val requestUrl = document.location()
|
||||
val imageCount = script.substringAfter("var DM5_IMAGE_COUNT=").substringBefore(";").toInt()
|
||||
result = (1..imageCount).map {
|
||||
val url = requestUrl.toHttpUrl().newBuilder()
|
||||
.addPathSegment("chapterfun.ashx")
|
||||
.addQueryParameter("cid", cid)
|
||||
.addQueryParameter("page", it.toString())
|
||||
.addQueryParameter("key", "")
|
||||
.addQueryParameter("language", "1")
|
||||
.addQueryParameter("gtk", "6")
|
||||
.addQueryParameter("_cid", cid)
|
||||
.addQueryParameter("_mid", mid)
|
||||
.addQueryParameter("_dt", dt)
|
||||
.addQueryParameter("_sign", sign)
|
||||
.build()
|
||||
Page(it, url.toString())
|
||||
} as ArrayList<Page>
|
||||
}
|
||||
|
||||
if (preferences.getBoolean(CHAPTER_COMMENTS_PREF, false)) {
|
||||
val pageSize = script.substringAfter("var DM5_PAGEPCOUNT = ").substringBefore(";")
|
||||
val tid = script.substringAfter("var DM5_TIEBATOPICID='").substringBefore("'")
|
||||
for (i in 1..pageSize.toInt()) {
|
||||
result.add(
|
||||
Page(
|
||||
result.size,
|
||||
"",
|
||||
"$baseUrl/m$cid/pagerdata.ashx?pageindex=$i&pagesize=$pageSize&tid=$tid&cid=$cid&t=9",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun imageUrlRequest(page: Page): Request {
|
||||
val referer = page.url.substringBefore("chapterfun.ashx")
|
||||
val header = headers.newBuilder().add("Referer", referer).build()
|
||||
return GET(page.url, header)
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String {
|
||||
val script = Unpacker.unpack(response.body.string())
|
||||
val pix = script.substringAfter("var pix=\"").substringBefore("\"")
|
||||
val pvalue = script.substringAfter("var pvalue=[\"").substringBefore("\"")
|
||||
val query = script.substringAfter("pix+pvalue[i]+\"").substringBefore("\"")
|
||||
return pix + pvalue + query
|
||||
}
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
if (!page.imageUrl!!.contains("pagerdata.ashx")) {
|
||||
return GET(page.imageUrl!!, headers)
|
||||
}
|
||||
|
||||
val referer = page.imageUrl!!.substringBefore("pagerdata.ashx")
|
||||
val header = headers.newBuilder().add("Referer", referer).build()
|
||||
return GET(page.imageUrl!!, header)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val chapterCommentsPreference = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = CHAPTER_COMMENTS_PREF
|
||||
title = "章末吐槽页"
|
||||
summary = "修改后,已加载的章节需要清除章节缓存才能生效。"
|
||||
setDefaultValue(false)
|
||||
}
|
||||
val sortChapterPreference = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = SORT_CHAPTER_PREF
|
||||
title = "依照上傳時間排序章節"
|
||||
setDefaultValue(false)
|
||||
}
|
||||
screen.addPreference(chapterCommentsPreference)
|
||||
screen.addPreference(sortChapterPreference)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHAPTER_COMMENTS_PREF = "chapterComments"
|
||||
private const val SORT_CHAPTER_PREF = "sortChapter"
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user