diff --git a/src/en/zinchanmanga/AndroidManifest.xml b/src/en/zinchanmanga/AndroidManifest.xml new file mode 100644 index 000000000..30deb7f79 --- /dev/null +++ b/src/en/zinchanmanga/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/en/zinchanmanga/build.gradle b/src/en/zinchanmanga/build.gradle new file mode 100644 index 000000000..2e30473f2 --- /dev/null +++ b/src/en/zinchanmanga/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'ZinChanManga' + pkgNameSuffix = 'en.zinchanmanga' + extClass = '.ZinChanManga' + extVersionCode = 1 + isNsfw = true +} + +dependencies { + implementation project(':lib-ratelimit') +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/zinchanmanga/res/mipmap-hdpi/ic_launcher.png b/src/en/zinchanmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..98863e81b Binary files /dev/null and b/src/en/zinchanmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/zinchanmanga/res/mipmap-mdpi/ic_launcher.png b/src/en/zinchanmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..a17a6e171 Binary files /dev/null and b/src/en/zinchanmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/zinchanmanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/zinchanmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..8aed81fd2 Binary files /dev/null and b/src/en/zinchanmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/zinchanmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/zinchanmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..2c0771c26 Binary files /dev/null and b/src/en/zinchanmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/zinchanmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/zinchanmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..ee9bcac78 Binary files /dev/null and b/src/en/zinchanmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/zinchanmanga/res/web_hi_res_512.png b/src/en/zinchanmanga/res/web_hi_res_512.png new file mode 100644 index 000000000..17f70e1d2 Binary files /dev/null and b/src/en/zinchanmanga/res/web_hi_res_512.png differ diff --git a/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanAPI.kt b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanAPI.kt new file mode 100644 index 000000000..b83ddadda --- /dev/null +++ b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanAPI.kt @@ -0,0 +1,102 @@ +package eu.kanade.tachiyomi.extension.en.zinchanmanga + +import kotlinx.serialization.Serializable +import org.jsoup.Jsoup +import java.text.SimpleDateFormat +import java.util.Locale +import kotlinx.serialization.SerialName as N + +@Serializable +data class Data( + private val data: List, + private val err_message: String +) : List by data { + init { require(err_message == "Success") { err_message } } +} + +@Serializable +data class SeriesList( + private val data: Pagination, + private val err_message: String +) : List by data { + init { require(err_message == "Success") { err_message } } + + val pages by lazy { data.pages } +} + +@Serializable +data class Pagination( + @N("total_page") val pages: Int, + private val data: List +) : List by data + +@Serializable +data class Series( + private val id_story: Int, + @N("name_story") val title: String, + private val slug_story: String, + @N("status_story") val status: String? = null, + private val name_genre: String, + private val name_author: String, + @N("thumbnail_story") val cover: String, + private val content_story: String +) { + val url by lazy { + "$slug_story?id=$id_story" + } + + val description by lazy { + Jsoup.parse(content_story).text().takeIf { "Updating" !in it } + } + + val genres by lazy { + name_genre.trim('|').replace("|", ", ") + } + + val authors by lazy { + name_author.replace(",|", "|").trim('|').takeIf { + it != "Updating" && it != "Đang Cập Nhật" + }?.replace("Author: ", "")?.replace(" ", ", ") + } +} + +@Serializable +data class Chapter( + private val id_chapter: Int, + private val name_chapter: String, + private val latest_update_chapter: String, + private val name_extend: String +) { + val params by lazy { + "?id_chapter=$id_chapter&type_story=manga" + } + + val title by lazy { + buildString { + append(name_chapter) + if (name_extend != "") { + append(" - ") + append(name_extend) + } + } + } + + val number by lazy { + name_chapter.substringAfter(' ').toFloatOrNull() ?: -1f + } + + val timestamp by lazy { + dateFormat.parse(latest_update_chapter)?.time ?: 0L + } + + companion object { + private val dateFormat by lazy { + SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT) + } + } +} + +@Serializable +data class PageList( + private val data_chapter: List +) : List by data_chapter diff --git a/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanCert.kt b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanCert.kt new file mode 100644 index 000000000..cc2254f22 --- /dev/null +++ b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanCert.kt @@ -0,0 +1,78 @@ +package eu.kanade.tachiyomi.extension.en.zinchanmanga + +import java.security.KeyStore +import java.security.SecureRandom +import java.security.cert.CertificateFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + +object ZinChanCert { + private val keystore by lazy { + KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) + setCertificateEntry("zin-chan-cert", certificate) + } + } + + private val certificate by lazy { + CertificateFactory.getInstance("X.509").run { + PEM.byteInputStream().use(::generateCertificate) + } + } + + private val managers by lazy { + TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm() + ).apply { init(keystore) }.trustManagers + } + + val factory by lazy { + SSLContext.getInstance("TLS").apply { + init(null, managers, SecureRandom()) + }.socketFactory!! + } + + val manager: X509TrustManager + get() = managers[0] as X509TrustManager + + private const val PEM = """-----BEGIN CERTIFICATE----- +MIIG1TCCBL2gAwIBAgIQbFWr29AHksedBwzYEZ7WvzANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjAw +MTMwMDAwMDAwWhcNMzAwMTI5MjM1OTU5WjBLMQswCQYDVQQGEwJBVDEQMA4GA1UE +ChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBSU0EgRG9tYWluIFNlY3VyZSBT +aXRlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAhmlzfqO1Mdgj +4W3dpBPTVBX1AuvcAyG1fl0dUnw/MeueCWzRWTheZ35LVo91kLI3DDVaZKW+TBAs +JBjEbYmMwcWSTWYCg5334SF0+ctDAsFxsX+rTDh9kSrG/4mp6OShubLaEIUJiZo4 +t873TuSd0Wj5DWt3DtpAG8T35l/v+xrN8ub8PSSoX5Vkgw+jWf4KQtNvUFLDq8mF +WhUnPL6jHAADXpvs4lTNYwOtx9yQtbpxwSt7QJY1+ICrmRJB6BuKRt/jfDJF9Jsc +RQVlHIxQdKAJl7oaVnXgDkqtk2qddd3kCDXd74gv813G91z7CjsGyJ93oJIlNS3U +gFbD6V54JMgZ3rSmotYbz98oZxX7MKbtCm1aJ/q+hTv2YK1yMxrnfcieKmOYBbFD +hnW5O6RMA703dBK92j6XRN2EttLkQuujZgy+jXRKtaWMIlkNkWJmOiHmErQngHvt +iNkIcjJumq1ddFX4iaTI40a6zgvIBtxFeDs2RfcaH73er7ctNUUqgQT5rFgJhMmF +x76rQgB5OZUkodb5k2ex7P+Gu4J86bS15094UuYcV09hVeknmTh5Ex9CBKipLS2W +2wKBakf+aVYnNCU6S0nASqt2xrZpGC1v7v6DhuepyyJtn3qSV2PoBiU5Sql+aARp +wUibQMGm44gjyNDqDlVp+ShLQlUH9x8CAwEAAaOCAXUwggFxMB8GA1UdIwQYMBaA +FFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBTI2XhootkZaNU9ct5fCj7c +tYaGpjAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUE +FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQIC +TjAIBgZngQwBAgEwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1 +c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYG +CCsGAQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3Qu +Y29tL1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRw +Oi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAVDwoIzQDV +ercT0eYqZjBNJ8VNWwVFlQOtZERqn5iWnEVaLZZdzxlbvz2Fx0ExUNuUEgYkIVM4 +YocKkCQ7hO5noicoq/DrEYH5IuNcuW1I8JJZ9DLuB1fYvIHlZ2JG46iNbVKA3ygA +Ez86RvDQlt2C494qqPVItRjrz9YlJEGT0DrttyApq0YLFDzf+Z1pkMhh7c+7fXeJ +qmIhfJpduKc8HEQkYQQShen426S3H0JrIAbKcBCiyYFuOhfyvuwVCFDfFvrjADjd +4jX1uQXd161IyFRbm89s2Oj5oU1wDYz5sx+hoCuh6lSs+/uPuWomIq3y1GDFNafW ++LsHBU16lQo5Q2yh25laQsKRgyPmMpHJ98edm6y2sHUabASmRHxvGiuwwE25aDU0 +2SAeepyImJ2CzB80YG7WxlynHqNhpE7xfC7PzQlLgmfEHdU+tHFeQazRQnrFkW2W +kqRGIq7cKRnyypvjPMkjeiV9lRdAM9fSJvsB3svUuu1coIG1xxI1yegoGM4r5QP4 +RGIVvYaiI76C0djoSbQ/dkIUUXQuB8AL5jyH34g3BZaaXyvpmnV4ilppMXVAnAYG +ON51WhJ6W0xNdNJwzYASZYH+tmCWI+N60Gv2NNMGHwMZ7e9bXgzUCZH5FaBFDGR5 +S9VWqHB73Q+OyIVvIbKYcSc2w/aSuFKGSA== +-----END CERTIFICATE-----""" +} diff --git a/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanGenre.kt b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanGenre.kt new file mode 100644 index 000000000..c69f09a1b --- /dev/null +++ b/src/en/zinchanmanga/src/eu/kanade/tachiyomi/extension/en/zinchanmanga/ZinChanGenre.kt @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.extension.en.zinchanmanga + +import eu.kanade.tachiyomi.source.model.Filter + +class ZinChanGenre(values: Array) : Filter.Select("Genres", values) { + override fun toString() = (state + 27).toString() + + companion object { + object Note : Filter.Header("NOTE: can't combine with text search!") + + val genres: Array + get() = arrayOf( + "