diff --git a/.run/MangabzGenerator.run.xml b/.run/MangabzGenerator.run.xml deleted file mode 100644 index f7e671c76..000000000 --- a/.run/MangabzGenerator.run.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.run/WeebreaderGenerator.run.xml b/.run/WeebreaderGenerator.run.xml deleted file mode 100644 index 3aa96b3a8..000000000 --- a/.run/WeebreaderGenerator.run.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/multisrc/overrides/mangabz/mangabz/additional.gradle b/multisrc/overrides/mangabz/mangabz/additional.gradle deleted file mode 100644 index 027379881..000000000 --- a/multisrc/overrides/mangabz/mangabz/additional.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation project(':lib-unpacker') -} diff --git a/multisrc/overrides/mangabz/vomic/src/Vomic.kt b/multisrc/overrides/mangabz/vomic/src/Vomic.kt deleted file mode 100644 index ae4d2c48b..000000000 --- a/multisrc/overrides/mangabz/vomic/src/Vomic.kt +++ /dev/null @@ -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().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> { - 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 { - 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) } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzGenerator.kt deleted file mode 100644 index f78c07fc2..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzGenerator.kt +++ /dev/null @@ -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) { - MangabzGenerator().createAll() - } - } -} diff --git a/multisrc/overrides/mangabz/mangabz/AndroidManifest.xml b/src/zh/mangabz/AndroidManifest.xml similarity index 100% rename from multisrc/overrides/mangabz/mangabz/AndroidManifest.xml rename to src/zh/mangabz/AndroidManifest.xml diff --git a/src/zh/mangabz/build.gradle b/src/zh/mangabz/build.gradle new file mode 100644 index 000000000..4c3fc1758 --- /dev/null +++ b/src/zh/mangabz/build.gradle @@ -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') +} diff --git a/multisrc/overrides/mangabz/mangabz/res/mipmap-hdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/mipmap-hdpi/ic_launcher.png rename to src/zh/mangabz/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/mangabz/res/mipmap-mdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/mipmap-mdpi/ic_launcher.png rename to src/zh/mangabz/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/mangabz/res/mipmap-xhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/mipmap-xhdpi/ic_launcher.png rename to src/zh/mangabz/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/mangabz/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/mipmap-xxhdpi/ic_launcher.png rename to src/zh/mangabz/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/mangabz/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/mipmap-xxxhdpi/ic_launcher.png rename to src/zh/mangabz/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/mangabz/res/web_hi_res_512.png b/src/zh/mangabz/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mangabz/mangabz/res/web_hi_res_512.png rename to src/zh/mangabz/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangabz/mangabz/src/CookieInterceptor.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/CookieInterceptor.kt similarity index 100% rename from multisrc/overrides/mangabz/mangabz/src/CookieInterceptor.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/CookieInterceptor.kt diff --git a/multisrc/overrides/mangabz/mangabz/src/Date.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Date.kt similarity index 100% rename from multisrc/overrides/mangabz/mangabz/src/Date.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Date.kt diff --git a/multisrc/overrides/mangabz/mangabz/src/Filters.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Filters.kt similarity index 100% rename from multisrc/overrides/mangabz/mangabz/src/Filters.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Filters.kt diff --git a/multisrc/overrides/mangabz/mangabz/src/Mangabz.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt similarity index 98% rename from multisrc/overrides/mangabz/mangabz/src/Mangabz.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt index d28bef540..25fa4148f 100644 --- a/multisrc/overrides/mangabz/mangabz/src/Mangabz.kt +++ b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt @@ -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 diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzTheme.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzTheme.kt similarity index 97% rename from multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzTheme.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzTheme.kt index e6dc71392..21d1f30cd 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangabz/MangabzTheme.kt +++ b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzTheme.kt @@ -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) diff --git a/multisrc/overrides/mangabz/mangabz/src/MangabzUrlActivity.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzUrlActivity.kt similarity index 100% rename from multisrc/overrides/mangabz/mangabz/src/MangabzUrlActivity.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzUrlActivity.kt diff --git a/multisrc/overrides/mangabz/mangabz/src/Preferences.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Preferences.kt similarity index 100% rename from multisrc/overrides/mangabz/mangabz/src/Preferences.kt rename to src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Preferences.kt diff --git a/src/zh/vomic/AndroidManifest.xml b/src/zh/vomic/AndroidManifest.xml new file mode 100644 index 000000000..30deb7f79 --- /dev/null +++ b/src/zh/vomic/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/zh/vomic/build.gradle b/src/zh/vomic/build.gradle new file mode 100644 index 000000000..e52e592d3 --- /dev/null +++ b/src/zh/vomic/build.gradle @@ -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" diff --git a/multisrc/overrides/mangabz/vomic/res/mipmap-hdpi/ic_launcher.png b/src/zh/vomic/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/mipmap-hdpi/ic_launcher.png rename to src/zh/vomic/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/vomic/res/mipmap-mdpi/ic_launcher.png b/src/zh/vomic/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/mipmap-mdpi/ic_launcher.png rename to src/zh/vomic/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/vomic/res/mipmap-xhdpi/ic_launcher.png b/src/zh/vomic/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/mipmap-xhdpi/ic_launcher.png rename to src/zh/vomic/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/vomic/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/vomic/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/mipmap-xxhdpi/ic_launcher.png rename to src/zh/vomic/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/vomic/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/vomic/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/mipmap-xxxhdpi/ic_launcher.png rename to src/zh/vomic/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangabz/vomic/res/web_hi_res_512.png b/src/zh/vomic/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mangabz/vomic/res/web_hi_res_512.png rename to src/zh/vomic/res/web_hi_res_512.png diff --git a/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Dto.kt b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Dto.kt new file mode 100644 index 000000000..f522a2865 --- /dev/null +++ b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Dto.kt @@ -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? = 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 + 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, +) { + 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) + +@Serializable +class ResponseDto(val data: T) diff --git a/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Filters.kt b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Filters.kt new file mode 100644 index 000000000..ddc251027 --- /dev/null +++ b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Filters.kt @@ -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("分类") diff --git a/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt new file mode 100644 index 000000000..240760b36 --- /dev/null +++ b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt @@ -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().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().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 { + val chapters: List = 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 { + val pageList: List = 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 Response.parseAs(): T = + json.decodeFromString>(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) } + } +}