diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml
index 93527ab35..92cd7b72e 100644
--- a/.github/workflows/issue_moderator.yml
+++ b/.github/workflows/issue_moderator.yml
@@ -35,7 +35,7 @@ jobs:
},
{
"type": "both",
- "regex": ".*(mangago|mangafox|hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|heroscan|manhwahot|leitor\\.?net|manga\\s*livre|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|read\\s*comic\\s*online|coco\\s*manhua|hitomi\\.la).*",
+ "regex": ".*(mangago|mangafox|hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|heroscan|manhwahot|leitor\\.?net|manga\\s*livre|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|read\\s*comic\\s*online|coco\\s*manhua|hitomi\\.la|copymanga).*",
"ignoreCase": true,
"message": "{match} will not be added back as it is too difficult to maintain. Read #3475 for more information"
},
diff --git a/src/zh/copymanga/AndroidManifest.xml b/src/zh/copymanga/AndroidManifest.xml
deleted file mode 100644
index 30deb7f79..000000000
--- a/src/zh/copymanga/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/src/zh/copymanga/build.gradle b/src/zh/copymanga/build.gradle
deleted file mode 100644
index 280ddfbab..000000000
--- a/src/zh/copymanga/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlinx-serialization'
-
-ext {
- extName = 'CopyManga'
- pkgNameSuffix = 'zh.copymanga'
- extClass = '.CopyManga'
- extVersionCode = 31
-}
-
-dependencies {
- implementation 'com.luhuiguo:chinese-utils:1.0'
-}
-
-apply from: "$rootDir/common.gradle"
-
-android {
- packagingOptions {
- exclude '/pinyin.txt'
- exclude '/polyphone.txt'
- exclude '/trad.txt'
- exclude '/traditional.txt'
- exclude '/unknown.txt'
- }
-}
diff --git a/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index d71429b79..000000000
Binary files a/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 5bb6d9327..000000000
Binary files a/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 90b447b9d..000000000
Binary files a/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 4f85d5cd4..000000000
Binary files a/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 387681a58..000000000
Binary files a/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/zh/copymanga/res/web_hi_res_512.png b/src/zh/copymanga/res/web_hi_res_512.png
deleted file mode 100644
index f2d6b4ab5..000000000
Binary files a/src/zh/copymanga/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt b/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt
deleted file mode 100644
index 6d1a9ad9f..000000000
--- a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt
+++ /dev/null
@@ -1,356 +0,0 @@
-package eu.kanade.tachiyomi.extension.zh.copymanga
-
-import android.app.Application
-import android.content.SharedPreferences
-import android.util.Log
-import android.widget.Toast
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.PreferenceScreen
-import androidx.preference.SwitchPreferenceCompat
-import eu.kanade.tachiyomi.extension.zh.copymanga.MangaDto.Companion.parseChapterGroups
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.asObservableSuccess
-import eu.kanade.tachiyomi.source.ConfigurableSource
-import eu.kanade.tachiyomi.source.model.Filter
-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.json.Json
-import kotlinx.serialization.json.decodeFromStream
-import okhttp3.Headers
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import rx.Observable
-import rx.Single
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import kotlin.concurrent.thread
-
-class CopyManga : HttpSource(), ConfigurableSource {
- override val name = "拷贝漫画"
- override val lang = "zh"
- override val supportsLatest = true
-
- private val json: Json by injectLazy()
-
- private val preferences: SharedPreferences =
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
-
- private var domain = DOMAINS[preferences.getString(DOMAIN_PREF, "0")!!.toInt().coerceIn(0, DOMAINS.size - 1)]
- override val baseUrl = WWW_PREFIX + domain
- private var apiUrl = API_PREFIX + domain // www. 也可以
-
- override val client: OkHttpClient = network.client.newBuilder()
- .addInterceptor(NonblockingRateLimitInterceptor(2, 4)) // 2 requests per 4 seconds
- .build()
-
- private fun Headers.Builder.setUserAgent(userAgent: String) = set("User-Agent", userAgent)
- private fun Headers.Builder.setRegion(useOverseasCdn: Boolean) = set("region", if (useOverseasCdn) "0" else "1")
- private fun Headers.Builder.setReferer() = set("Referer", WWW_PREFIX + domain)
- private fun Headers.Builder.setVersion(version: String) = set("version", version)
-
- override fun headersBuilder() = Headers.Builder()
- .setUserAgent(preferences.getString(USER_AGENT_PREF, DEFAULT_USER_AGENT)!!)
- .setRegion(preferences.getBoolean(OVERSEAS_CDN_PREF, false))
- .setReferer()
- .add("platform", "1")
- .setVersion(preferences.getString(VERSION_PREF, DEFAULT_VERSION)!!)
-
- private var apiHeaders = headersBuilder().build()
-
- private var useWebp = preferences.getBoolean(WEBP_PREF, true)
-
- init {
- MangaDto.convertToSc = preferences.getBoolean(SC_TITLE_PREF, false)
- }
-
- override fun popularMangaRequest(page: Int): Request {
- val offset = PAGE_SIZE * (page - 1)
- return GET("$apiUrl/api/v3/recs?pos=3200102&limit=$PAGE_SIZE&offset=$offset", apiHeaders)
- }
-
- override fun popularMangaParse(response: Response): MangasPage {
- val page: ListDto = response.parseAs()
- val hasNextPage = page.offset + page.limit < page.total
- return MangasPage(page.list.map { it.toSManga() }, hasNextPage)
- }
-
- override fun latestUpdatesRequest(page: Int): Request {
- val offset = PAGE_SIZE * (page - 1)
- return GET("$apiUrl/api/v3/update/newest?limit=$PAGE_SIZE&offset=$offset", apiHeaders)
- }
-
- override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val offset = PAGE_SIZE * (page - 1)
- val builder = apiUrl.toHttpUrl().newBuilder()
- .addQueryParameter("limit", "$PAGE_SIZE")
- .addQueryParameter("offset", "$offset")
- if (query.isNotBlank()) {
- builder.addPathSegments("api/v3/search/comic")
- .addQueryParameter("q", query)
- filters.filterIsInstance().firstOrNull()?.addQuery(builder)
- } else {
- builder.addPathSegments("api/v3/comics")
- filters.filterIsInstance().forEach {
- if (it !is SearchFilter) it.addQuery(builder)
- }
- }
- return Request.Builder().url(builder.build()).headers(apiHeaders).build()
- }
-
- override fun searchMangaParse(response: Response): MangasPage {
- val page: ListDto = response.parseAs()
- val hasNextPage = page.offset + page.limit < page.total
- return MangasPage(page.list.map { it.toSManga() }, hasNextPage)
- }
-
- // 让 WebView 打开网页而不是 API
- override fun mangaDetailsRequest(manga: SManga) = GET(WWW_PREFIX + domain + manga.url, apiHeaders)
-
- private fun realMangaDetailsRequest(manga: SManga) =
- GET("$apiUrl/api/v3/comic2/${manga.url.removePrefix(MangaDto.URL_PREFIX)}", apiHeaders)
-
- override fun fetchMangaDetails(manga: SManga): Observable =
- client.newCall(realMangaDetailsRequest(manga)).asObservableSuccess().map { mangaDetailsParse(it) }
-
- override fun mangaDetailsParse(response: Response): SManga =
- response.parseAs().toSMangaDetails()
-
- override fun fetchChapterList(manga: SManga): Observable> = Single.create> {
- val result = ArrayList()
- val groups = manga.description?.parseChapterGroups() ?: run {
- val response = client.newCall(realMangaDetailsRequest(manga)).execute()
- response.parseAs().groups!!.values
- }
- val mangaSlug = manga.url.removePrefix(MangaDto.URL_PREFIX)
- result.fetchChapterGroup(mangaSlug, "default", "")
- for (group in groups) {
- result.fetchChapterGroup(mangaSlug, group.path_word, group.name)
- }
- it.onSuccess(result)
- }.toObservable()
-
- private fun ArrayList.fetchChapterGroup(manga: String, key: String, name: String) {
- val result = ArrayList(0)
- var offset = 0
- var hasNextPage = true
- while (hasNextPage) {
- val response = client.newCall(GET("$apiUrl/api/v3/comic/$manga/group/$key/chapters?limit=$CHAPTER_PAGE_SIZE&offset=$offset", apiHeaders)).execute()
- val chapters: ListDto = response.parseAs()
- result.ensureCapacity(chapters.total)
- chapters.list.mapTo(result) { it.toSChapter(name) }
- offset += CHAPTER_PAGE_SIZE
- hasNextPage = offset < chapters.total
- }
- addAll(result.asReversed())
- }
-
- override fun chapterListRequest(manga: SManga) = throw UnsupportedOperationException("Not used.")
- override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used.")
-
- // 新版 API 中间是 /chapter2/ 并且返回值需要排序
- override fun pageListRequest(chapter: SChapter) = GET("$apiUrl/api/v3${chapter.url}", apiHeaders)
-
- override fun pageListParse(response: Response): List {
- val result: ChapterPageListWrapperDto = response.parseAs()
- if (result.show_app) {
- throw Exception("访问受限,请尝试在插件设置中修改 User Agent")
- }
- return result.chapter.contents.mapIndexed { i, it ->
- Page(i, imageUrl = it.url)
- }
- }
-
- override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.")
-
- override fun imageRequest(page: Page): Request {
- val imageUrl = page.imageUrl!!
- return if (useWebp && imageUrl.endsWith(".jpg")) {
- GET(imageUrl.removeSuffix(".jpg") + ".webp")
- } else {
- GET(imageUrl)
- }
- }
-
- private inline fun Response.parseAs(): T = use {
- if (header("Content-Type") != "application/json") {
- throw Exception("访问受限,请尝试在插件设置中修改 User Agent")
- } else if (code != 200) {
- throw Exception(json.decodeFromStream(body!!.byteStream()).message)
- }
- json.decodeFromStream>(body!!.byteStream()).results
- }
-
- private var genres: Array = emptyArray()
- private var isFetchingGenres = false
-
- override fun getFilterList(): FilterList {
- val genreFilter = if (genres.isEmpty()) {
- fetchGenres()
- Filter.Header("点击“重置”尝试刷新题材分类")
- } else {
- GenreFilter(genres)
- }
- return FilterList(
- SearchFilter(),
- Filter.Separator(),
- Filter.Header("分类(搜索文本时无效)"),
- genreFilter,
- RegionFilter(),
- StatusFilter(),
- SortFilter(),
- )
- }
-
- private fun fetchGenres() {
- if (genres.isNotEmpty() || isFetchingGenres) return
- isFetchingGenres = true
- thread {
- try {
- val response = client.newCall(GET("$apiUrl/api/v3/theme/comic/count?limit=500", apiHeaders)).execute()
- val list = response.parseAs>().list
- val result = ArrayList(list.size + 1).apply { add(Param("全部", "")) }
- genres = list.mapTo(result) { it.toParam() }.toTypedArray()
- } catch (e: Exception) {
- Log.e("CopyManga", "failed to fetch genres", e)
- } finally {
- isFetchingGenres = false
- }
- }
- }
-
- var fetchVersionState = 0 // 0 = not yet or failed, 1 = fetching, 2 = fetched
-
- override fun setupPreferenceScreen(screen: PreferenceScreen) {
- ListPreference(screen.context).apply {
- key = DOMAIN_PREF
- title = "网址域名"
- summary = "连接不稳定时可以尝试切换"
- entries = DOMAINS
- entryValues = DOMAIN_INDICES
- setDefaultValue("0")
- setOnPreferenceChangeListener { _, newValue ->
- domain = DOMAINS[(newValue as String).toInt()]
- apiUrl = API_PREFIX + domain
- apiHeaders = apiHeaders.newBuilder().setReferer().build()
- true
- }
- }.let { screen.addPreference(it) }
-
- EditTextPreference(screen.context).apply {
- key = USER_AGENT_PREF
- title = "User Agent (UA)"
- summary = "可以使用 Windows/macOS/iOS 上浏览器的 UA,不要使用安卓浏览器和 Windows Chrome 103(“在 WebView 中打开”需要重启应用刷新)"
- setDefaultValue(DEFAULT_USER_AGENT)
- setOnPreferenceChangeListener { _, newValue ->
- apiHeaders = apiHeaders.newBuilder().setUserAgent(newValue as String).build()
- true
- }
- }.let { screen.addPreference(it) }
-
- EditTextPreference(screen.context).apply {
- key = UA_CHECKER
- title = "获取浏览器 UA 的链接"
- summary = "点击后可以在弹出的对话框中复制链接"
- setDefaultValue(UA_CHECKER)
- setOnPreferenceChangeListener { _, _ -> false }
- }.let { screen.addPreference(it) }
-
- SwitchPreferenceCompat(screen.context).apply {
- title = "更新网页版本号"
- summary = "点击尝试更新网页版本号,当前为:${preferences.getString(VERSION_PREF, DEFAULT_VERSION)}"
- setOnPreferenceChangeListener { _, _ ->
- if (fetchVersionState == 1) {
- Toast.makeText(screen.context, "已经在尝试更新,请勿反复点击", Toast.LENGTH_SHORT).show()
- return@setOnPreferenceChangeListener false
- } else if (fetchVersionState == 2) {
- Toast.makeText(screen.context, "版本号已经成功更新,返回重进刷新", Toast.LENGTH_SHORT).show()
- return@setOnPreferenceChangeListener false
- }
- Toast.makeText(screen.context, "开始尝试更新网页版本号", Toast.LENGTH_SHORT).show()
- fetchVersionState = 1
- thread {
- try {
- val headers = apiHeaders.newBuilder().setUserAgent(System.getProperty("http.agent")!!).build()
- val html = client.newCall(GET("https://www.copymanga.org/h5", headers)).execute().body!!.string()
- val jsRegex = Regex("""https\S+?index\.\w+?\.js""")
- val jsUrl = jsRegex.find(html)!!.value
- val js = client.newCall(GET(jsUrl, headers)).execute().body!!.string()
- val versionRegex = Regex("""VERSION:"([\d.]+?)"""", RegexOption.IGNORE_CASE)
- val version = versionRegex.find(js)!!.groupValues[1]
- preferences.edit().putString(VERSION_PREF, version).apply()
- apiHeaders = apiHeaders.newBuilder().setVersion(version).build()
- fetchVersionState = 2
- } catch (e: Throwable) {
- fetchVersionState = 0
- Log.e("CopyManga", "failed to fetch version", e)
- }
- }
- false
- }
- }.let { screen.addPreference(it) }
-
- SwitchPreferenceCompat(screen.context).apply {
- key = OVERSEAS_CDN_PREF
- title = "使用“港台及海外线路”"
- summary = "连接不稳定时可以尝试切换,关闭时使用“大陆用户线路”,已阅读章节需要清空缓存才能生效"
- setDefaultValue(false)
- setOnPreferenceChangeListener { _, newValue ->
- apiHeaders = apiHeaders.newBuilder().setRegion(newValue as Boolean).build()
- true
- }
- }.let { screen.addPreference(it) }
-
- SwitchPreferenceCompat(screen.context).apply {
- key = WEBP_PREF
- title = "使用 WebP 图片格式"
- summary = "默认开启,可以节省网站流量"
- setDefaultValue(true)
- setOnPreferenceChangeListener { _, newValue ->
- useWebp = newValue as Boolean
- true
- }
- }.let { screen.addPreference(it) }
-
- SwitchPreferenceCompat(screen.context).apply {
- key = SC_TITLE_PREF
- title = "将作品标题转换为简体中文"
- summary = "修改后,已添加漫画需要迁移才能更新标题"
- setDefaultValue(false)
- setOnPreferenceChangeListener { _, newValue ->
- MangaDto.convertToSc = newValue as Boolean
- true
- }
- }.let { screen.addPreference(it) }
- }
-
- companion object {
- private const val DOMAIN_PREF = "domain"
- private const val OVERSEAS_CDN_PREF = "changeCDN"
- private const val SC_TITLE_PREF = "showSCTitle"
- private const val WEBP_PREF = "webp"
- private const val USER_AGENT_PREF = "userAgent"
- private const val VERSION_PREF = "version"
- // private const val CHROME_VERSION_PREF = "chromeVersion" // default value was "103"
-
- private const val WWW_PREFIX = "https://www."
- private const val API_PREFIX = "https://api."
- private val DOMAINS = arrayOf("copymanga.org", "copymanga.info", "copymanga.net")
- private val DOMAIN_INDICES = arrayOf("0", "1", "2")
- private const val DEFAULT_USER_AGENT = ""
- private const val DEFAULT_VERSION = "2022.06.29"
- private const val UA_CHECKER = "https://tool.lu/useragent"
-
- private const val PAGE_SIZE = 20
- private const val CHAPTER_PAGE_SIZE = 500
- }
-}
diff --git a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaDto.kt b/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaDto.kt
deleted file mode 100644
index d99bd80c3..000000000
--- a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaDto.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package eu.kanade.tachiyomi.extension.zh.copymanga
-
-import com.luhuiguo.chinese.ChineseUtils
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import kotlinx.serialization.Serializable
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-@Serializable
-class MangaDto(
- val name: String,
- val path_word: String,
- val author: List,
- val cover: String,
- val region: ValueDto? = null,
- val status: ValueDto? = null,
- val theme: List? = null,
- val brief: String? = null,
-) {
- fun toSManga() = SManga.create().apply {
- url = URL_PREFIX + path_word
- title = if (convertToSc) ChineseUtils.toSimplified(name) else name
- author = this@MangaDto.author.joinToString { it.name }
- thumbnail_url = cover.removeSuffix(".328x422.jpg")
- }
-
- fun toSMangaDetails(groups: ChapterGroups) = toSManga().apply {
- description = brief + groups.toDescription()
- genre = buildList(theme!!.size + 1) {
- add(region!!.display)
- theme.mapTo(this) { it.name }
- }.joinToString { ChineseUtils.toSimplified(it) }
- status = when (this@MangaDto.status!!.value) {
- 0 -> SManga.ONGOING
- 1 -> SManga.COMPLETED
- else -> SManga.UNKNOWN
- }
- initialized = true
- }
-
- companion object {
- internal var convertToSc = false
-
- const val URL_PREFIX = "/comic/"
-
- private const val CHAPTER_GROUP_DELIMITER = ","
- private const val CHAPTER_GROUP_PREFIX = "\n\n【其他版本:"
- private const val CHAPTER_GROUP_POSTFIX = "】"
- private const val NO_CHAPTER_GROUP = "无"
-
- private fun ChapterGroups.toDescription(): String {
- if (size <= 1) return CHAPTER_GROUP_PREFIX + NO_CHAPTER_GROUP + CHAPTER_GROUP_POSTFIX
- val groups = ArrayList(size - 1)
- for ((key, group) in this) {
- if (key != "default") groups.add(group)
- }
- return groups.joinToString(CHAPTER_GROUP_DELIMITER, CHAPTER_GROUP_PREFIX, CHAPTER_GROUP_POSTFIX) {
- it.name + '#' + it.path_word
- }
- }
-
- fun String.parseChapterGroups(): List? {
- val index = lastIndexOf(CHAPTER_GROUP_PREFIX)
- if (index < 0) return null
- val groups = substring(index + CHAPTER_GROUP_PREFIX.length, length - CHAPTER_GROUP_POSTFIX.length)
- if (groups == NO_CHAPTER_GROUP) return emptyList()
- return groups.split(CHAPTER_GROUP_DELIMITER).map {
- val delimiterIndex = it.indexOf('#')
- KeywordDto(it.substring(0, delimiterIndex), it.substring(delimiterIndex + 1, it.length))
- }
- }
- }
-}
-
-@Serializable
-class ChapterDto(
- val uuid: String,
- val name: String,
- val comic_path_word: String,
- val datetime_created: String,
-) {
- fun toSChapter(group: String) = SChapter.create().apply {
- url = "/comic/$comic_path_word/chapter/$uuid"
- name = if (group.isEmpty()) this@ChapterDto.name else group + ':' + this@ChapterDto.name
- date_upload = dateFormat.parse(datetime_created)?.time ?: 0
- }
-
- companion object {
- val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
- }
-}
-
-@Serializable
-class KeywordDto(val name: String, val path_word: String) {
- fun toParam() = Param(ChineseUtils.toSimplified(name), path_word)
-}
-
-@Serializable
-class ValueDto(val value: Int, val display: String)
-
-@Serializable
-class MangaWrapperDto(val comic: MangaDto, val groups: ChapterGroups? = null) {
- fun toSManga() = comic.toSManga()
- fun toSMangaDetails() = comic.toSMangaDetails(groups!!)
-}
-
-typealias ChapterGroups = LinkedHashMap
-
-@Serializable
-class ChapterPageListDto(val contents: List)
-
-@Serializable
-class UrlDto(val url: String)
-
-@Serializable
-class ChapterPageListWrapperDto(val chapter: ChapterPageListDto, val show_app: Boolean)
-
-@Serializable
-class ListDto(
- val total: Int,
- val limit: Int,
- val offset: Int,
- val list: List,
-)
-
-@Serializable
-class ResultDto(val results: T)
-
-@Serializable
-class ResultMessageDto(val code: Int, val message: String)
diff --git a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaFilters.kt b/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaFilters.kt
deleted file mode 100644
index 74ef8726c..000000000
--- a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyMangaFilters.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package eu.kanade.tachiyomi.extension.zh.copymanga
-
-import eu.kanade.tachiyomi.source.model.Filter
-import okhttp3.HttpUrl
-
-class Param(val name: String, val value: String)
-
-open class CopyMangaFilter(name: String, private val key: String, private val params: Array) :
- Filter.Select(name, params.map { it.name }.toTypedArray()) {
- fun addQuery(builder: HttpUrl.Builder) {
- val param = params[state].value
- if (param.isNotEmpty())
- builder.addQueryParameter(key, param)
- }
-}
-
-class SearchFilter : CopyMangaFilter("文本搜索范围", "q_type", SEARCH_FILTER_VALUES)
-
-private val SEARCH_FILTER_VALUES = arrayOf(
- Param("全部", ""),
- Param("名称", "name"),
- Param("作者", "author"),
- Param("汉化组", "local"),
-)
-
-class GenreFilter(genres: Array) : CopyMangaFilter("题材", "theme", genres)
-
-class RegionFilter : CopyMangaFilter("地区", "region", REGION_VALUES)
-
-private val REGION_VALUES = arrayOf(
- Param("全部", ""),
- Param("日本", "0"),
- Param("韩国", "1"),
- Param("欧美", "2"),
-)
-
-class StatusFilter : CopyMangaFilter("状态", "status", STATUS_VALUES)
-
-private val STATUS_VALUES = arrayOf(
- Param("全部", ""),
- Param("连载中", "0"),
- Param("已完结", "1"),
- Param("短篇", "2"),
-)
-
-class SortFilter : CopyMangaFilter("排序", "ordering", SORT_VALUES)
-
-private val SORT_VALUES = arrayOf(
- Param("热门", "-popular"),
- Param("热门(逆序)", "popular"),
- Param("更新时间", "-datetime_updated"),
- Param("更新时间(逆序)", "datetime_updated"),
-)
diff --git a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/NonblockingRateLimitInterceptor.kt b/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/NonblockingRateLimitInterceptor.kt
deleted file mode 100644
index ccdf8dd92..000000000
--- a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/NonblockingRateLimitInterceptor.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package eu.kanade.tachiyomi.extension.zh.copymanga
-
-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 NonblockingRateLimitInterceptor(
- private val permits: Int,
- period: Long = 1,
- unit: TimeUnit = TimeUnit.SECONDS,
-) : Interceptor {
-
- private val requestQueue = ArrayList(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())
- }
-}