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 eu.kanade.tachiyomi.lib.unpacker.SubstringExtractor
|
||||
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.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
@ -25,7 +24,7 @@ import rx.Observable
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class Mangabz : MangabzTheme("Mangabz", ""), ConfigurableSource {
|
||||
class Mangabz : MangabzTheme("Mangabz"), ConfigurableSource {
|
||||
|
||||
override val baseUrl: String
|
||||
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 eu.kanade.tachiyomi.network.GET
|
||||
|
@ -18,10 +18,10 @@ import org.jsoup.select.Evaluator
|
|||
|
||||
abstract class MangabzTheme(
|
||||
override val name: String,
|
||||
override val baseUrl: String,
|
||||
override val lang: String = "zh",
|
||||
) : HttpSource() {
|
||||
|
||||
override val lang = "zh"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|