Compare commits
6
Commits
de2321ce8f
...
e6eba042b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6eba042b9 | ||
|
|
aeb9b47228 | ||
|
|
8b2ea9a418 | ||
|
|
97bca3eecc | ||
|
|
d7c64f540b | ||
|
|
793ba815f7 |
No files matched your search
@@ -1,101 +1,25 @@
|
||||
<p align="right">
|
||||
<strong>English</strong>
|
||||
<span> | </span>
|
||||
<a href="/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="/docs/README/ja.md">
|
||||
日本語
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="https://github.com/FooIbar/EhViewer-art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/FooIbar/EhViewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/FooIbar/EhViewer">
|
||||
<img src="https://www.codefactor.io/repository/github/FooIbar/EhViewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/FooIbar/EhViewer" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/FooIbar/EhViewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/FooIbar/EhViewer" alt="Issues">
|
||||
</a>
|
||||
Standalone fork of
|
||||
<a href="https://codeberg.org/EhViewer/EhViewer">EhViewer (codeberg)</a>
|
||||
maintained at <a href="https://gitea.yrkaierx.com/yenru0/EhViewer">gitea.yrkaierx.com/yenru0/EhViewer</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
# Changes over upstream
|
||||
|
||||
# Description
|
||||
- Built-in hosts, Cloudflare CDN fallback and domain fronting with SNI spoofing for censored networks (`EhHostsConfig.kt`, `EhDns.kt`, `EhSSLSocketFactory.kt`)
|
||||
- Cronet engine disabled by default (bypasses the anti-DPI OkHttp stack; `enable_quic` re-enables it)
|
||||
- Update check disabled by default (manual check still available)
|
||||
- API request timeout raised to 30 s
|
||||
- Own application id (`yenru0.yrkaierx.ehviewer`), installs side by side with upstream builds
|
||||
|
||||
A modern EhViewer fork dedicated to high-performance
|
||||
# Upstream
|
||||
|
||||
with [Material Design 3](https://m3.material.io/)
|
||||
and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview) Support
|
||||
|
||||
# Download
|
||||
|
||||
| Flavor | Minimum Android Version | Notes |
|
||||
|---------|-------------------------|--------------------------------|
|
||||
| Default | 8.0 | Full support |
|
||||
| Nougat | 7.0 | Limited support, no guarantees |
|
||||
|
||||
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||
<img alt="Get it on GitHub" src="https://github.com/FooIbar/EhViewer-art/blob/master/get-it-on-github.svg" width="200px"/>
|
||||
</a>
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
# Thanks
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [Arrow](https://arrow-kt.io/)
|
||||
- [AOSP & AndroidX](https://source.android.com/)
|
||||
- [Kotlin & KotlinX](https://kotlinlang.org/)
|
||||
- [Material Icons](https://github.com/google/material-design-icons)
|
||||
- [Ktor](https://ktor.io/)
|
||||
- [Coil](https://coil-kt.github.io/coil/)
|
||||
- [Compose Destinations](https://composedestinations.rafaelcosta.xyz/)
|
||||
- [libarchive](https://www.libarchive.org/)
|
||||
Synced from [EhViewer/EhViewer on codeberg](https://codeberg.org/EhViewer/EhViewer), the continuation of FooIbar's EhViewer. Feature work happens there; this fork tracks it and carries the network/resilience patches above.
|
||||
|
||||
# License
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ android {
|
||||
val snapshot = !hasProperty("release")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
applicationId = "yenru0.yrkaierx.ehviewer"
|
||||
versionCode = 180063
|
||||
versionName = if (snapshot) {
|
||||
"1.15.0-SNAPSHOT"
|
||||
|
||||
@@ -219,7 +219,7 @@ class EhApplication : Application(), SingletonImageLoader.Factory {
|
||||
private set
|
||||
|
||||
val ktorClient by lazy {
|
||||
if (isCronetAvailable && Settings.enableCronet.value) {
|
||||
if (isCronetAvailable && Settings.enableQuic.value) {
|
||||
HttpClient(Cronet) {
|
||||
engine { configureClient(Settings.enableQuic.value) }
|
||||
configureCommon()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import com.hippo.ehviewer.client.hosts
|
||||
import com.hippo.ehviewer.client.hostsDsl
|
||||
|
||||
val builtInHosts = hostsDsl {
|
||||
hosts("exhentai.org") {
|
||||
"178.175.128.251" blockedInCN false
|
||||
"178.175.128.252" blockedInCN false
|
||||
"178.175.128.253" blockedInCN false
|
||||
"178.175.128.254" blockedInCN false
|
||||
"178.175.129.251" blockedInCN false
|
||||
"178.175.129.252" blockedInCN false
|
||||
"178.175.129.253" blockedInCN false
|
||||
"178.175.129.254" blockedInCN false
|
||||
"178.175.132.19" blockedInCN false
|
||||
"178.175.132.20" blockedInCN false
|
||||
"178.175.132.21" blockedInCN false
|
||||
"178.175.132.22" blockedInCN false
|
||||
}
|
||||
hosts("api.github.com") {
|
||||
"140.82.114.5" blockedInCN false
|
||||
"140.82.114.6" blockedInCN false
|
||||
"140.82.116.5" blockedInCN false
|
||||
"140.82.116.6" blockedInCN false
|
||||
"140.82.121.5" blockedInCN true
|
||||
"140.82.121.6" blockedInCN true
|
||||
"20.200.245.245" blockedInCN false
|
||||
"20.201.28.148" blockedInCN false
|
||||
"20.205.243.168" blockedInCN false
|
||||
"20.26.156.210" blockedInCN false
|
||||
"20.27.177.116" blockedInCN false
|
||||
"20.87.245.6" blockedInCN false
|
||||
"4.237.22.34" blockedInCN false
|
||||
}
|
||||
hosts("github.com") {
|
||||
"140.82.113.4" blockedInCN false
|
||||
"140.82.114.3" blockedInCN false
|
||||
"140.82.116.4" blockedInCN false
|
||||
"140.82.121.3" blockedInCN true
|
||||
"140.82.121.4" blockedInCN true
|
||||
"20.200.245.247" blockedInCN false
|
||||
"20.201.28.151" blockedInCN false
|
||||
"20.205.243.166" blockedInCN false
|
||||
"20.26.156.215" blockedInCN false
|
||||
"20.27.177.113" blockedInCN false
|
||||
"20.87.245.0" blockedInCN false
|
||||
"4.237.22.38" blockedInCN false
|
||||
}
|
||||
hosts("raw.githubusercontent.com") {
|
||||
"185.199.108.133" blockedInCN false
|
||||
"185.199.109.133" blockedInCN false
|
||||
"185.199.110.133" blockedInCN false
|
||||
"185.199.111.133" blockedInCN false
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val saveCrashLog = boolPref("save_crash_log", true)
|
||||
val readCacheSize = intPref("read_cache_size_2", 640)
|
||||
val enableCronet = boolPref("enable_cronet", true)
|
||||
val enableQuic = boolPref("enable_quic", true)
|
||||
val enableQuic = boolPref("enable_quic", false)
|
||||
val hardwareBitmapThreshold = intPref("hardware_bitmap_threshold", 16384)
|
||||
val preloadThumbAggressively = boolPref("preload_thumb_aggressively", false)
|
||||
val animateItems = boolPref("animate_items", true)
|
||||
@@ -120,7 +120,7 @@ object Settings : DataStorePreferences(null) {
|
||||
// About
|
||||
val backupBeforeUpdate = boolPref("backup_before_update", false)
|
||||
val useCIUpdateChannel = boolPref("ci_update_channel", BuildConfig.SNAPSHOT)
|
||||
val updateIntervalDays = intPref("update_interval_days", 7)
|
||||
val updateIntervalDays = intPref("update_interval_days", 0)
|
||||
|
||||
// Misc
|
||||
val languageFilter = intPref("language_filter", -1)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.builtInHosts
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
import okhttp3.AsyncDns
|
||||
import okhttp3.Dns
|
||||
import okhttp3.android.AndroidAsyncDns
|
||||
|
||||
private typealias HostsMap = MutableMap<String, List<InetAddress>>
|
||||
|
||||
const val CFSUFFIX = ".cdn.cloudflare.net"
|
||||
|
||||
val cloudflaredDomains = listOf(
|
||||
"e-hentai.org",
|
||||
"api.e-hentai.org",
|
||||
"upload.e-hentai.org",
|
||||
"forums.e-hentai.org",
|
||||
"exhentai.org",
|
||||
"s.exhentai.org",
|
||||
"testingcf.jsdelivr.net",
|
||||
)
|
||||
|
||||
val dFEnabledDomains = listOf(
|
||||
"github.com", "api.github.com",
|
||||
"ehgt.org", "gt0.ehgt.org", "gt1.ehgt.org", "gt2.ehgt.org", "gt3.ehgt.org", "ul.ehgt.org",
|
||||
"e-hentai.org", "api.e-hentai.org", "forums.e-hentai.org", "repo.e-hentai.org", "upload.e-hentai.org",
|
||||
"exhentai.org", "s.exhentai.org",
|
||||
)
|
||||
|
||||
val dohSkipDomains = listOf(
|
||||
"www.recaptcha.net",
|
||||
"www.gstatic.cn",
|
||||
"www.gstatic.com",
|
||||
)
|
||||
|
||||
fun hostsDsl(builder: HostsMap.() -> Unit): HostsMap = mutableMapOf<String, List<InetAddress>>().apply(builder)
|
||||
|
||||
fun interface HostMapBuilder {
|
||||
infix fun String.blockedInCN(boolean: Boolean)
|
||||
}
|
||||
|
||||
fun HostsMap.hosts(vararg hosts: String, builder: HostMapBuilder.() -> Unit) = apply {
|
||||
hosts.forEach { host ->
|
||||
fun String.toInetAddress() = InetAddress.getByName(this).let { InetAddress.getByAddress(host, it.address) }
|
||||
mutableListOf<InetAddress>().apply {
|
||||
HostMapBuilder { if (!it) add(toInetAddress()) }.apply(builder)
|
||||
put(host, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val systemDns = if (isAtLeastQ) AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6) else Dns.SYSTEM
|
||||
|
||||
object EhDns : Dns {
|
||||
override fun lookup(hostname: String): List<InetAddress> = when {
|
||||
(hostname in "exhentai.org") ->
|
||||
builtInHosts[hostname] ?: systemDns.lookup("s.exhentai.org$CFSUFFIX")
|
||||
(hostname in cloudflaredDomains) ->
|
||||
builtInHosts[hostname] ?: systemDns.lookup("$hostname$CFSUFFIX")
|
||||
(hostname in dohSkipDomains) ->
|
||||
systemDns.lookup(hostname)
|
||||
else ->
|
||||
builtInHosts[hostname] ?: systemDns.lookup(hostname)
|
||||
}.shuffled()
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import android.util.Log
|
||||
import com.hippo.ehviewer.Settings
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.security.KeyStore
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
const val EXCEPTIONAL_DOMAIN = "hath.network"
|
||||
private val sslSocketFactory: SSLSocketFactory = SSLContext.getInstance("TLS").apply {
|
||||
init(null, null, null)
|
||||
}.socketFactory
|
||||
|
||||
object EhSSLSocketFactory : SSLSocketFactory() {
|
||||
override fun getDefaultCipherSuites(): Array<String> = sslSocketFactory.defaultCipherSuites
|
||||
override fun getSupportedCipherSuites(): Array<String> = sslSocketFactory.supportedCipherSuites
|
||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket = sslSocketFactory.createSocket(s, resolveHost(s, host), port, autoClose) as SSLSocket
|
||||
|
||||
override fun createSocket(host: String, port: Int): Socket = sslSocketFactory.createSocket(host, port)
|
||||
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket = sslSocketFactory.createSocket(host, port, localHost, localPort)
|
||||
override fun createSocket(host: InetAddress, port: Int): Socket = sslSocketFactory.createSocket(host, port)
|
||||
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket = sslSocketFactory.createSocket(address, port, localAddress, localPort)
|
||||
|
||||
private fun resolveHost(socket: Socket, host: String): String = socket.inetAddress.hostAddress.takeIf {
|
||||
host in dFEnabledDomains || EXCEPTIONAL_DOMAIN in host
|
||||
} ?: host
|
||||
}
|
||||
|
||||
fun OkHttpClient.Builder.install(sslSocketFactory: SSLSocketFactory) = apply {
|
||||
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())!!
|
||||
factory.init(null as KeyStore?)
|
||||
val manager = factory.trustManagers!!
|
||||
val trustManager = manager.filterIsInstance<X509TrustManager>().first()
|
||||
sslSocketFactory(sslSocketFactory, trustManager)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ fun <T : HttpClientEngineConfig> HttpClientConfig<T>.configureCommon(redirect: B
|
||||
}
|
||||
install(HttpTimeout) {
|
||||
reset()
|
||||
requestTimeoutMillis = 10_000
|
||||
requestTimeoutMillis = 30_000
|
||||
}
|
||||
install(UserAgent)
|
||||
defaultRequest {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhDns
|
||||
import com.hippo.ehviewer.client.EhSSLSocketFactory
|
||||
import com.hippo.ehviewer.client.install
|
||||
import io.ktor.client.engine.okhttp.OkHttpConfig
|
||||
import okhttp3.AsyncDns
|
||||
import okhttp3.android.AndroidAsyncDns
|
||||
import java.net.Proxy
|
||||
|
||||
fun OkHttpConfig.configureClient() {
|
||||
config {
|
||||
if (isAtLeastQ) {
|
||||
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
|
||||
dns(EhDns)
|
||||
proxy(Proxy.NO_PROXY)
|
||||
if (!Settings.enableQuic.value) {
|
||||
install(EhSSLSocketFactory)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user