Split Mangabz multisrc and update Vomic (#16922)
|
@ -1,11 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="MangabzGenerator" type="JetRunConfigurationType" nameIsGenerated="true">
|
|
||||||
<module name="tachiyomi-extensions.multisrc.main" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="eu.kanade.tachiyomi.multisrc.mangabz.MangabzGenerator" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktFormat" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=mangabz" />
|
|
||||||
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktLint" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=mangabz" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="WeebreaderGenerator" type="JetRunConfigurationType" nameIsGenerated="true">
|
|
||||||
<module name="tachiyomi-extensions.multisrc.main" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="eu.kanade.tachiyomi.multisrc.weebreader.WeebreaderGenerator" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktFormat" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=weebreader" />
|
|
||||||
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktLint" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=weebreader" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,3 +0,0 @@
|
||||||
dependencies {
|
|
||||||
implementation project(':lib-unpacker')
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.vomic
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangabz.MangabzTheme
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
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.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import org.jsoup.select.Elements
|
|
||||||
import org.jsoup.select.Evaluator
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class Vomic : MangabzTheme("vomic", ""), ConfigurableSource {
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
override val baseUrl: String
|
|
||||||
|
|
||||||
init {
|
|
||||||
val mirrors = MIRRORS
|
|
||||||
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
|
|
||||||
baseUrl = "http://" + mirrors[mirrorIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder().removeAll("Referer")
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
|
|
||||||
|
|
||||||
// original credit: https://github.com/tachiyomiorg/tachiyomi-extensions/pull/5628
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
val link = Evaluator.Tag("a")
|
|
||||||
val image = Evaluator.Tag("img")
|
|
||||||
val paragraph = Evaluator.Tag("p")
|
|
||||||
|
|
||||||
/* top banner - no thumbnail
|
|
||||||
document.selectFirst(Evaluator.Class("banner-con")).select(link).mapTo(mangas) { element ->
|
|
||||||
SManga.create().apply {
|
|
||||||
title = element.attr("title")
|
|
||||||
url = element.attr("href")
|
|
||||||
thumbnail_url = element.selectFirst(image).attr("src")
|
|
||||||
.takeIf { !it.endsWith("/static/images/bg/banner_info_a.jpg") }
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
val mangas = buildList {
|
|
||||||
// ranking sidebar
|
|
||||||
addAll(document.selectFirst(Evaluator.Class("rank-list"))!!.children())
|
|
||||||
// carousel list
|
|
||||||
addAll(document.selectFirst(Evaluator.Class("carousel-right-list"))!!.children())
|
|
||||||
// recommend list
|
|
||||||
addAll(document.select(Evaluator.Class("index-manga-item"))!!)
|
|
||||||
}.map { element ->
|
|
||||||
SManga.create().apply {
|
|
||||||
title = element.selectFirst(paragraph)!!.text()
|
|
||||||
url = element.selectFirst(link)!!.attr("href")
|
|
||||||
thumbnail_url = element.selectFirst(image)!!.attr("src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MangasPage(mangas.distinctBy { it.url }, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun parseDescription(element: Element, title: String, details: Elements): String {
|
|
||||||
val text = element.ownText()
|
|
||||||
val collapsed = element.selectFirst(Evaluator.Tag("span"))?.ownText() ?: ""
|
|
||||||
val source = details[3].text()
|
|
||||||
return "$source\n\n$text$collapsed"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
|
||||||
val chapterId = manga.url.removePrefix("/").removeSuffix("_c/")
|
|
||||||
return super.fetchChapterList(manga).doOnNext {
|
|
||||||
for (chapter in it) chapter.url = chapter.url + "chapterimage.ashx?mid=" + chapterId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getChapterElements(document: Document): Elements {
|
|
||||||
val chapterId = document.location().removeSuffix("_c/").substringAfterLast('/')
|
|
||||||
val response = client.newCall(GET("$baseUrl/chapter-$chapterId-s2/", headers)).execute()
|
|
||||||
return Jsoup.parseBodyFragment(response.body.string()).body().children()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val needPageCount = false
|
|
||||||
|
|
||||||
override fun parseDate(listTitle: String): Long {
|
|
||||||
val date = listTitle.split("|")[2].trim()
|
|
||||||
return dateFormat.parse(date)!!.time
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
|
||||||
val urls = response.body.string().run {
|
|
||||||
val left = indexOf('[')
|
|
||||||
val right = lastIndexOf(']')
|
|
||||||
if (left + 1 == right) return emptyList()
|
|
||||||
substring(left + 1, right).split(", ")
|
|
||||||
}
|
|
||||||
return urls.mapIndexed { index, rawUrl ->
|
|
||||||
val url = rawUrl.trim('"')
|
|
||||||
val imageUrl = when {
|
|
||||||
url.startsWith("http://127.0.0.1") -> url.toHttpUrl().queryParameter("url")
|
|
||||||
else -> url
|
|
||||||
}
|
|
||||||
Page(index, imageUrl = imageUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageRequest(page: Page): Request {
|
|
||||||
val url = page.imageUrl!!
|
|
||||||
val host = url.toHttpUrl().host
|
|
||||||
val headers = headersBuilder().set("Referer", "https://$host/").build()
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
ListPreference(screen.context).apply {
|
|
||||||
val mirrors = MIRRORS
|
|
||||||
key = MIRROR_PREF
|
|
||||||
title = "镜像网址"
|
|
||||||
summary = "%s\n重启生效"
|
|
||||||
entries = mirrors
|
|
||||||
entryValues = Array(mirrors.size) { it.toString() }
|
|
||||||
setDefaultValue("0")
|
|
||||||
}.let(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MIRROR_PREF = "MIRROR"
|
|
||||||
private val MIRRORS get() = arrayOf("www.vomicmh.com", "www.iewoai.com")
|
|
||||||
|
|
||||||
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.ENGLISH) }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.mangabz
|
|
||||||
|
|
||||||
import generator.ThemeSourceData.SingleLang
|
|
||||||
import generator.ThemeSourceGenerator
|
|
||||||
|
|
||||||
class MangabzGenerator : ThemeSourceGenerator {
|
|
||||||
override val themeClass = "MangabzTheme"
|
|
||||||
override val themePkg = "mangabz"
|
|
||||||
override val baseVersionCode = 1
|
|
||||||
override val sources = listOf(
|
|
||||||
SingleLang("Mangabz", "https://mangabz.com", "zh", overrideVersionCode = 6),
|
|
||||||
SingleLang("vomic", "http://www.vomicmh.com", "zh", className = "Vomic"),
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
MangabzGenerator().createAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Mangabz'
|
||||||
|
pkgNameSuffix = 'zh.mangabz'
|
||||||
|
extClass = '.Mangabz'
|
||||||
|
extVersionCode = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':lib-unpacker')
|
||||||
|
}
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
@ -4,7 +4,6 @@ import android.app.Application
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.lib.unpacker.SubstringExtractor
|
import eu.kanade.tachiyomi.lib.unpacker.SubstringExtractor
|
||||||
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
|
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
|
||||||
import eu.kanade.tachiyomi.multisrc.mangabz.MangabzTheme
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
@ -25,7 +24,7 @@ import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class Mangabz : MangabzTheme("Mangabz", ""), ConfigurableSource {
|
class Mangabz : MangabzTheme("Mangabz"), ConfigurableSource {
|
||||||
|
|
||||||
override val baseUrl: String
|
override val baseUrl: String
|
||||||
override val client: OkHttpClient
|
override val client: OkHttpClient
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.mangabz
|
package eu.kanade.tachiyomi.extension.zh.mangabz
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
@ -18,10 +18,10 @@ import org.jsoup.select.Evaluator
|
||||||
|
|
||||||
abstract class MangabzTheme(
|
abstract class MangabzTheme(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
|
||||||
override val lang: String = "zh",
|
|
||||||
) : HttpSource() {
|
) : HttpSource() {
|
||||||
|
|
||||||
|
override val lang = "zh"
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl)
|
override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl)
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,12 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'vomic'
|
||||||
|
pkgNameSuffix = 'zh.vomic'
|
||||||
|
extClass = '.Vomic'
|
||||||
|
extVersionCode = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,89 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.zh.vomic
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
val SManga.id get() = url.substring(1, 1 + 32)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDto(
|
||||||
|
private val mid: String,
|
||||||
|
private val title: String,
|
||||||
|
private val site: SiteDto? = null,
|
||||||
|
private val cover_img_url: String?,
|
||||||
|
private val authors_name: List<String>? = null,
|
||||||
|
private val status: String? = null,
|
||||||
|
private val categories: JsonElement? = null,
|
||||||
|
private val description: String? = null,
|
||||||
|
) {
|
||||||
|
fun toSMangaOrNull() = if (title.isEmpty()) null else toSManga()
|
||||||
|
|
||||||
|
private fun toSManga() = SManga.create().apply {
|
||||||
|
url = "/${mid}_c/"
|
||||||
|
title = this@MangaDto.title
|
||||||
|
thumbnail_url = cover_img_url
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toSMangaDetails() = toSManga().apply {
|
||||||
|
author = authors_name!!.joinToString()
|
||||||
|
description = "站点:" + site + "\n\n" + this@MangaDto.description
|
||||||
|
genre = categories!!.jsonArray.joinToString { it.jsonPrimitive.content }
|
||||||
|
status = when (this@MangaDto.status!!) {
|
||||||
|
"连载中" -> SManga.ONGOING
|
||||||
|
"已完结" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SiteDto(
|
||||||
|
private val site_en: String,
|
||||||
|
private val site_cn: String? = null,
|
||||||
|
) {
|
||||||
|
override fun toString() = "$site_cn ($site_en)"
|
||||||
|
}
|
||||||
|
|
||||||
|
val SChapter.id: Pair<String, String>
|
||||||
|
get() {
|
||||||
|
val url = url
|
||||||
|
val length = url.length
|
||||||
|
val mangaId = url.substring(length - 32, length)
|
||||||
|
val chapterId = url.substring(3, 3 + 32)
|
||||||
|
return Pair(mangaId, chapterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterDto(
|
||||||
|
private val title: String,
|
||||||
|
private val cid: String,
|
||||||
|
private val update_time: String,
|
||||||
|
) {
|
||||||
|
fun toSChapter(mangaId: String, dateFormat: SimpleDateFormat) = SChapter.create().apply {
|
||||||
|
url = "/m_$cid/chapterimage.ashx?mid=$mangaId"
|
||||||
|
name = title
|
||||||
|
date_upload = dateFormat.parse(update_time)!!.time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaListDto(
|
||||||
|
private val page: Int,
|
||||||
|
private val result_count: Int,
|
||||||
|
private val result: List<MangaDto>,
|
||||||
|
) {
|
||||||
|
val entries get() = if (result_count != 0) result else emptyList()
|
||||||
|
val hasNextPage get() = page < 100 && page * 12 < result_count
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class RankingDto(val result: List<MangaDto>)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ResponseDto<T>(val data: T)
|
|
@ -0,0 +1,23 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.zh.vomic
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
|
class SearchQuery(val title: String, val category: String)
|
||||||
|
|
||||||
|
fun parseSearchQuery(query: String, filters: FilterList): SearchQuery {
|
||||||
|
for (filter in filters) {
|
||||||
|
if (filter is SearchCategoryToggle) {
|
||||||
|
if (filter.state) return SearchQuery("", query)
|
||||||
|
} else if (filter is CategoryFilter) {
|
||||||
|
return SearchQuery(query, filter.state.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SearchQuery(query, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFilterListInternal() = FilterList(SearchCategoryToggle(), CategoryFilter())
|
||||||
|
|
||||||
|
private class SearchCategoryToggle : Filter.CheckBox("将搜索词视为分类,勾选后下面的文本框无效")
|
||||||
|
|
||||||
|
private class CategoryFilter : Filter.Text("分类")
|
|
@ -0,0 +1,175 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.zh.vomic
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Base64
|
||||||
|
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.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.HttpSource
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
class Vomic : HttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
|
override val name = "vomic"
|
||||||
|
|
||||||
|
override val lang = "zh"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
override val baseUrl: String
|
||||||
|
|
||||||
|
private val apiUrl: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
val mirrors = MIRRORS
|
||||||
|
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
|
||||||
|
baseUrl = "http://" + mirrors[mirrorIndex]
|
||||||
|
apiUrl = "http://" + mirrors[mirrorIndex].replace("www.", "api.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun headersBuilder() = Headers.Builder().add("User-Agent", System.getProperty("http.agent")!!)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int) = GET("$apiUrl/api/v1/rank/rank-data?rank_id=1&page=$page", headers)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val mangaList: RankingDto = response.parseAs()
|
||||||
|
val entries = mangaList.result.mapNotNull { it.toSMangaOrNull() }
|
||||||
|
val hasNextPage = response.request.url.queryParameter("page") != "4"
|
||||||
|
return MangasPage(entries, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun getFilterList() = getFilterListInternal()
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val searchQuery = parseSearchQuery(query.trim(), filters)
|
||||||
|
if (searchQuery.title.isEmpty() && searchQuery.category.isEmpty()) throw Exception("请输入搜索词或分类")
|
||||||
|
|
||||||
|
val url = "$apiUrl/api/v1/search/search-comic-data".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("title", searchQuery.title)
|
||||||
|
.addQueryParameter("category", searchQuery.category)
|
||||||
|
.addEncodedQueryParameter("page", page.toString())
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val mangaList: MangaListDto = response.parseAs()
|
||||||
|
val entries = mangaList.entries.mapNotNull { it.toSMangaOrNull() }
|
||||||
|
return MangasPage(entries, mangaList.hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga) = "$baseUrl/#/detail?id=${manga.id}"
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga) =
|
||||||
|
GET("$apiUrl/api/v1/detail/get-comic-detail-data?mid=${manga.id}", headers)
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) =
|
||||||
|
response.parseAs<MangaDto>().toSMangaDetails()
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) =
|
||||||
|
GET("$apiUrl/api/v1/detail/get-comic-detail-chapter-data?mid=${manga.id}", headers)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val chapters: List<ChapterDto> = response.parseAs()
|
||||||
|
val mangaId = response.request.url.queryParameter("mid")!!
|
||||||
|
val dateFormat = dateFormat
|
||||||
|
return chapters.map { it.toSChapter(mangaId, dateFormat) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter): String {
|
||||||
|
val (mangaId, chapterId) = chapter.id
|
||||||
|
return "$baseUrl/#/page/$mangaId/$chapterId"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val (mangaId, chapterId) = chapter.id
|
||||||
|
val key = run {
|
||||||
|
val alphanumeric = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
val chars = CharArray(24) { alphanumeric.random() }
|
||||||
|
String(chars)
|
||||||
|
}
|
||||||
|
val time = System.currentTimeMillis().toString()
|
||||||
|
val encrypted = run {
|
||||||
|
val keySpec = SecretKeySpec(key.toByteArray(), "DESede")
|
||||||
|
val iv = "k8tUyS\$m"
|
||||||
|
val ivSpec = IvParameterSpec(iv.toByteArray())
|
||||||
|
val payload = key + iv + "cid=" + chapterId + "&mid=" + mangaId + time
|
||||||
|
val bytes = Cipher.getInstance("DESede/CBC/PKCS5Padding").run {
|
||||||
|
init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
|
||||||
|
doFinal(payload.toByteArray())
|
||||||
|
}
|
||||||
|
Base64.encodeToString(bytes, Base64.DEFAULT)
|
||||||
|
}
|
||||||
|
val url = "$apiUrl/api/v2/page/get-comic-page-img-data".toHttpUrl().newBuilder()
|
||||||
|
.addEncodedQueryParameter("k", key)
|
||||||
|
.addEncodedQueryParameter("t", time)
|
||||||
|
.addQueryParameter("e", encrypted)
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val pageList: List<String> = response.parseAs()
|
||||||
|
if (pageList.size == 1 && pageList[0] == "https://cdn.vomicer.com/qiniu/vomic/otherImg/info2.webp") {
|
||||||
|
throw Exception("无法阅读此章节")
|
||||||
|
}
|
||||||
|
return pageList.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun imageRequest(page: Page): Request {
|
||||||
|
val url = page.imageUrl!!
|
||||||
|
val host = url.toHttpUrl().host
|
||||||
|
val headers = headersBuilder().set("Referer", "https://$host/").build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
val mirrors = MIRRORS
|
||||||
|
key = MIRROR_PREF
|
||||||
|
title = "镜像网址"
|
||||||
|
summary = "%s\n重启生效"
|
||||||
|
entries = mirrors
|
||||||
|
entryValues = Array(mirrors.size) { it.toString() }
|
||||||
|
setDefaultValue("0")
|
||||||
|
}.let(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAs(): T =
|
||||||
|
json.decodeFromString<ResponseDto<T>>(body.string()).data
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MIRROR_PREF = "MIRROR"
|
||||||
|
private val MIRRORS get() = arrayOf("www.vomicmh.com", "www.iewoai.com")
|
||||||
|
|
||||||
|
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.ENGLISH) }
|
||||||
|
}
|
||||||
|
}
|