Compare commits

..

7 Commits
5.2.1 ... 5.2.5

Author SHA1 Message Date
tom5079
748e023fde 5.2.5 Added logging to fix app crashing 2022-01-04 20:30:45 +09:00
tom5079
30104bacd2 Update README.md 2022-01-04 20:16:41 +09:00
tom5079
f33d1a1bfa 5.2.4 Added logging to fix app crashing 2022-01-04 20:16:04 +09:00
tom5079
3c08331441 5.2.3 Added logging to fix app crashing 2022-01-04 19:57:00 +09:00
tom5079
3eaa38247b 5.2.2 Fixed app crashing 2022-01-04 19:10:58 +09:00
tom5079
304ce643f9 Update README.md 2022-01-03 17:15:56 +09:00
tom5079
b4ad994f95 Create watchdiff.yml 2022-01-03 15:36:00 +09:00
10 changed files with 185 additions and 56 deletions

23
.github/workflows/watchdiff.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
# This is a basic workflow that is manually triggered
name: Watch hitomi.la file changes
on:
schedule:
- cron: "*/10 * * * *"
jobs:
watchdiff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: watchdiff
- name: Download files
run: ./fetch.sh
- name: Commit and Push
id: push
run: |
git config --global user.name 'Watchdiff bot'
git config --global user.email 'tom5079@naver.com'
{ git add . && git commit -m "File update" && git push; } | tail -1 | grep -q "nothing to commit"

View File

@@ -2,7 +2,7 @@
*Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.2.0/Pupil-v5.2.0.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.2.0/Pupil-v5.2.0.apk)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.2.5/Pupil-v5.2.5.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.2.5/Pupil-v5.2.5.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Features

View File

@@ -38,7 +38,7 @@ android {
minSdkVersion 16
targetSdkVersion 30
versionCode 69
versionName "5.2.1"
versionName "5.2.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@@ -129,7 +129,7 @@ dependencies {
implementation "com.google.guava:guava:31.0.1-android"
implementation "xyz.quaver:documentfilex:0.7.1"
implementation "xyz.quaver:documentfilex:0.7.2-DEV"
implementation "xyz.quaver:floatingsearchview:1.1.7"
testImplementation "junit:junit:4.13.1"

View File

@@ -11,7 +11,7 @@
"type": "SINGLE",
"filters": [],
"versionCode": 69,
"versionName": "5.2.1",
"versionName": "5.2.5",
"outputFile": "app-release.apk"
}
]

View File

@@ -25,6 +25,7 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.net.Uri
import android.os.Build
import android.util.Log
@@ -40,14 +41,13 @@ import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import okhttp3.*
import xyz.quaver.io.FileX
import xyz.quaver.pupil.hitomi.evaluations
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.*
import java.io.File
@@ -81,13 +81,77 @@ val client: OkHttpClient
@SuppressLint("StaticFieldLeak")
lateinit var webView: WebView
val _webViewFlow = MutableSharedFlow<Pair<String, String>>(
val _webViewFlow = MutableSharedFlow<Pair<String, String?>>(
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val webViewFlow = _webViewFlow.asSharedFlow()
var webViewReady = false
private set
var webViewFailed = false
private set
private var reloadJob: Job? = null
fun reloadWebView() {
if (reloadJob?.isActive == true) return
reloadJob = CoroutineScope(Dispatchers.IO).launch {
if (evaluations.isEmpty()) {
webViewReady = false
webViewFailed = false
while (evaluations.isNotEmpty()) yield()
runCatching {
URL(
if (isDebugBuild)
"https://tom5079.github.io/Pupil/hitomi-dev.html"
else
"https://tom5079.github.io/Pupil/hitomi.html"
).readText()
}.onFailure {
webViewFailed = true
}.getOrNull()?.let { html ->
launch(Dispatchers.Main) {
webView.loadDataWithBaseURL(
"https://hitomi.la/",
html,
"text/html",
null,
null
)
}
}
}
}
}
private var htmlVersion: String = ""
fun reloadWhenFailedOrUpdate() = CoroutineScope(Dispatchers.Default).launch {
while (true) {
if (
webViewFailed ||
runCatching {
URL(
if (isDebugBuild)
"https://tom5079.github.io/Pupil/hitomi-dev.html.ver"
else
"https://tom5079.github.io/Pupil/hitomi.html.ver"
).readText()
}.getOrNull().let { version ->
(!version.isNullOrEmpty() && version != htmlVersion).also {
if (it) htmlVersion = version!!
}
}
) {
reloadWebView()
}
delay(if (webViewReady && !webViewFailed) 10000 else 1000)
}
}
var isDebugBuild: Boolean = false
private lateinit var userAgent: String
class Pupil : Application() {
@@ -100,6 +164,9 @@ class Pupil : Application() {
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate() {
instance = this
isDebugBuild = applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
WebView.setWebContentsDebuggingEnabled(true)
webView = WebView(this).apply {
with (settings) {
@@ -113,6 +180,29 @@ class Pupil : Application() {
override fun onPageFinished(view: WebView?, url: String?) {
webViewReady = true
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
FirebaseCrashlytics.getInstance().log(
"onReceivedError: ${error?.description}"
)
}
webViewFailed = true
}
}
webChromeClient = object: WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
FirebaseCrashlytics.getInstance().log(
"onConsoleMessage: ${consoleMessage?.message()} (${consoleMessage?.sourceId()}:${consoleMessage?.lineNumber()})"
)
return super.onConsoleMessage(consoleMessage)
}
}
addJavascriptInterface(object {
@@ -120,22 +210,18 @@ class Pupil : Application() {
fun onResult(uid: String, result: String) {
_webViewFlow.tryEmit(uid to result)
}
}, "Callback")
CoroutineScope(Dispatchers.IO).launch {
val html = URL("https://tom5079.github.io/Pupil/hitomi.html").readText()
launch(Dispatchers.Main) {
loadDataWithBaseURL(
"https://hitomi.la/",
html,
"text/html",
null,
null
@JavascriptInterface
fun onError(uid: String, message: String) {
_webViewFlow.tryEmit(uid to null)
Toast.makeText(this@Pupil, message, Toast.LENGTH_LONG).show()
FirebaseCrashlytics.getInstance().recordException(
Exception(message)
)
}
}, "Callback")
}
}
reloadWhenFailedOrUpdate()
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)

View File

@@ -16,37 +16,42 @@
package xyz.quaver.pupil.hitomi
import android.annotation.SuppressLint
import android.util.Log
import android.webkit.WebView
import android.webkit.WebViewClient
import kotlinx.coroutines.*
import android.widget.Toast
import com.google.common.collect.ConcurrentHashMultiset
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import xyz.quaver.json
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.webView
import xyz.quaver.pupil.webViewFlow
import xyz.quaver.pupil.webViewReady
import xyz.quaver.readText
import java.net.URL
import java.nio.charset.Charset
import xyz.quaver.pupil.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
const val protocol = "https:"
suspend inline fun WebView.evaluate(script: String): String = withContext(Dispatchers.Main) {
while (!webViewReady) yield()
val evaluations = Collections.newSetFromMap<String>(ConcurrentHashMap())
suspend fun WebView.evaluate(script: String): String = withContext(Dispatchers.Main) {
while (webViewFailed || !webViewReady) yield()
val uid = UUID.randomUUID().toString()
evaluations.add(uid)
val result: String = suspendCoroutine { continuation ->
evaluateJavascript(script) {
evaluations.remove(uid)
continuation.resume(it)
}
}
@@ -55,15 +60,20 @@ suspend inline fun WebView.evaluate(script: String): String = withContext(Dispat
}
@OptIn(ExperimentalCoroutinesApi::class)
suspend inline fun WebView.evaluatePromise(script: String, then: String = ".then(result => Callback.onResult(%uid, JSON.stringify(result)))"): String = withContext(Dispatchers.Main) {
while (!webViewReady) yield()
suspend fun WebView.evaluatePromise(script: String, then: String = ".then(result => Callback.onResult(%uid, JSON.stringify(result))).catch(err => Callback.onError(%uid, JSON.stringify(error)))"): String? = withContext(Dispatchers.Main) {
while (webViewFailed || !webViewReady) yield()
val uid = UUID.randomUUID().toString()
evaluations.add(uid)
evaluateJavascript((script+then).replace("%uid", "'$uid'"), null)
val flow: Flow<Pair<String, String>> = webViewFlow.transformWhile { (currentUid, result) ->
if (currentUid == uid) emit(currentUid to result)
val flow: Flow<Pair<String, String?>> = webViewFlow.transformWhile { (currentUid, result) ->
if (currentUid == uid) {
evaluations.remove(uid)
emit(currentUid to result)
}
currentUid != uid
}
@@ -72,17 +82,9 @@ suspend inline fun WebView.evaluatePromise(script: String, then: String = ".then
@Suppress("EXPERIMENTAL_API_USAGE")
suspend fun getGalleryInfo(galleryID: Int): GalleryInfo {
val result = webView.evaluatePromise(
"""
new Promise((resolve, reject) => {
$.getScript('https://$domain/galleries/$galleryID.js', () => {
resolve(galleryinfo)
});
})
""".trimIndent()
)
val result = webView.evaluatePromise("get_gallery_info($galleryID)")
return json.decodeFromString(result)
return json.decodeFromString(result!!)
}
//common.js
@@ -105,6 +107,16 @@ suspend fun urlFromUrlFromHash(galleryID: Int, image: GalleryFiles, dir: String?
""".trimIndent()
)
FirebaseCrashlytics.getInstance().log(
"""
url_from_url_from_hash(
${galleryID.toString().js},
${Json.encodeToString(image)},
${dir.js}, ${ext.js}, ${base.js}
)
""".trimIndent()
)
return Json.decodeFromString(result)
}

View File

@@ -52,7 +52,7 @@ suspend fun getGalleryBlock(galleryID: Int) : GalleryBlock {
});
""".trimIndent(),
then = ""
)
)!!
val doc = Jsoup.parse(html)

View File

@@ -16,8 +16,6 @@
package xyz.quaver.pupil.hitomi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
@@ -29,7 +27,7 @@ const val extension = ".html"
@OptIn(ExperimentalSerializationApi::class)
suspend fun getGalleryIDsForQuery(query: String) : Set<Int> {
val result = webView.evaluatePromise("get_galleryids_for_query('$query')")
val result = webView.evaluatePromise("get_galleryids_for_query('$query')") ?: return emptySet()
return Json.decodeFromString(result)
}
@@ -39,14 +37,14 @@ data class Suggestion(val s: String, val t: Int, val u: String, val n: String)
@OptIn(ExperimentalSerializationApi::class)
suspend fun getSuggestionsForQuery(query: String) : List<Suggestion> {
val result = webView.evaluatePromise("get_suggestions_for_query('$query')")
val result = webView.evaluatePromise("get_suggestions_for_query('$query')") ?: return emptyList()
return Json.decodeFromString<List<List<Suggestion>?>>(result)[0]!!
return Json.decodeFromString<List<List<Suggestion>?>>(result)[0] ?: return emptyList()
}
@OptIn(ExperimentalSerializationApi::class)
suspend fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<Int> {
val jsArea = if (area == null) "null" else "'$area'"
return Json.decodeFromString(webView.evaluatePromise("""get_galleryids_from_nozomi($jsArea, '$tag', '$language')"""))
return Json.decodeFromString(webView.evaluatePromise("""get_galleryids_from_nozomi($jsArea, '$tag', '$language')""") ?: return emptySet())
}

View File

@@ -19,6 +19,7 @@
package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.serialization.json.*
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -103,7 +104,15 @@ suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
return this.files.map {
Request.Builder()
.url(imageUrlFromImage(galleryID, it, !lowQuality))
.url(
runCatching {
imageUrlFromImage(galleryID, it, !lowQuality)
}
.onFailure {
FirebaseCrashlytics.getInstance().recordException(it)
}
.getOrDefault("https://a/")
)
.header("Referer", "https://hitomi.la/")
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36")
}

View File

@@ -23,6 +23,7 @@ allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://guardian.github.io/maven/repo-releases/" }