Compare commits

...

10 Commits
5.3.1 ... 5.3.4

Author SHA1 Message Date
tom5079
5ee1bb11a0 Merge remote-tracking branch 'origin/master' 2022-02-01 19:11:25 +09:00
tom5079
c1de45abce use webp by default 2022-02-01 19:10:54 +09:00
tom5079
8805033c8d Update README.md 2022-02-01 17:47:46 +09:00
tom5079
0ed59bb8a9 Merge remote-tracking branch 'origin/master' 2022-02-01 17:46:44 +09:00
tom5079
8163f2fd28 Bug fix 2022-02-01 17:46:35 +09:00
tom5079
521a65c9d2 Update README.md 2022-02-01 17:37:50 +09:00
tom5079
eb98424668 Bug fix 2022-02-01 17:36:44 +09:00
tom5079
961c731743 Merge remote-tracking branch 'origin/master' 2022-02-01 11:45:54 +09:00
tom5079
5188769fb6 Fuck hitomi 2022-02-01 11:45:45 +09:00
tom5079
8f27d9e30f Update README.md 2022-02-01 11:45:35 +09:00
14 changed files with 144 additions and 145 deletions

View File

@@ -1,17 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_31.avd" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
@@ -23,6 +12,6 @@
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-02-01T02:15:22.286886Z" />
<timeTargetWasSelectedWithDropDown value="2022-02-01T08:00:57.223690Z" />
</component>
</project>

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.3.0/Pupil-v5.3.0.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.3.0/Pupil-v5.3.0.apk)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.3.3/Pupil-v5.3.3.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.3.3/Pupil-v5.3.3.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 31
versionCode 69
versionName "5.3.0"
versionName "5.3.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}

View File

@@ -33,4 +33,5 @@
}
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
-keep class xyz.quaver.pupil.** { *; }
-keep class xyz.quaver.pupil.** { *; }
-keep class app.cash.zipline.** { *; }

View File

@@ -12,7 +12,7 @@
"filters": [],
"attributes": [],
"versionCode": 69,
"versionName": "5.3.0",
"versionName": "5.3.4",
"outputFile": "app-release.apk"
}
],

View File

@@ -102,19 +102,19 @@ class ExampleInstrumentedTest {
Log.d("PUPILD", r.take(10).toString())
}
@Test
fun test_getBlock() {
val galleryBlock = getGalleryBlock(2097576)
print(galleryBlock)
}
@Test
fun test_getGallery() {
val gallery = getGallery(2097751)
print(gallery)
}
// @Test
// fun test_getBlock() {
// val galleryBlock = getGalleryBlock(2097576)
//
// print(galleryBlock)
// }
//
// @Test
// fun test_getGallery() {
// val gallery = getGallery(2097751)
//
// print(gallery)
// }
@Test
fun test_getGalleryInfo() {
@@ -125,42 +125,44 @@ class ExampleInstrumentedTest {
@Test
fun test_getReader() {
val reader = getGalleryInfo(1722144)
val reader = getGalleryInfo(2128654)
print(reader)
Log.d("PUPILD", reader.toString())
}
@Test
fun test_getImages() {
val galleryID = 2099306
fun test_getImages() { runBlocking {
val galleryID = 2128654
val images = getGalleryInfo(galleryID).files.map {
imageUrlFromImage(galleryID, it,false)
}
images.forEachIndexed { index, image ->
println("Testing $index/${images.size}: $image")
val response = client.newCall(
Request.Builder()
.url(image)
.header("Referer", "https://hitomi.la/")
.build()
).execute()
Log.d("PUPILD", images.toString())
assertEquals(200, response.code())
// images.forEachIndexed { index, image ->
// println("Testing $index/${images.size}: $image")
// val response = client.newCall(
// Request.Builder()
// .url(image)
// .header("Referer", "https://hitomi.la/")
// .build()
// ).execute()
//
// assertEquals(200, response.code())
//
// println("$index/${images.size} Passed")
// }
} }
println("$index/${images.size} Passed")
}
}
@Test
fun test_urlFromUrlFromHash() {
val url = urlFromUrlFromHash(1531795, GalleryFiles(
212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
), "webp")
print(url)
}
// @Test
// fun test_urlFromUrlFromHash() {
// val url = urlFromUrlFromHash(1531795, GalleryFiles(
// 212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
// ), "webp")
//
// print(url)
// }
// @Test
// suspend fun test_doSearch_extreme() {
@@ -173,9 +175,9 @@ class ExampleInstrumentedTest {
// print(doSearch("-male:yaoi -female:yaoi -female:loli").size)
// }
@Test
fun test_subdomainFromUrl() {
val galleryInfo = getGalleryInfo(1929109).files[2]
print(urlFromUrlFromHash(1929109, galleryInfo, "webp", null, "a"))
}
// @Test
// fun test_subdomainFromUrl() {
// val galleryInfo = getGalleryInfo(1929109).files[2]
// print(urlFromUrlFromHash(1929109, galleryInfo, "webp", null, "a"))
// }
}

View File

@@ -81,10 +81,8 @@ private var version = ""
var runtimeReady = false
private set
lateinit var runtime: QuickJs
private set
class Pupil : Application() {
companion object {
lateinit var instance: Pupil
private set
@@ -92,7 +90,7 @@ class Pupil : Application() {
init {
CoroutineScope(Dispatchers.IO).launch {
withContext(evaluationContext) {
withContext(Dispatchers.Main) {
runtime = QuickJs.create()
}
while (true) {
@@ -101,12 +99,15 @@ class Pupil : Application() {
if (version != newVersion) {
runtimeReady = false
version = newVersion
evaluationContext.cancelChildren()
withContext(evaluationContext) {
Log.d("PUPILD", "UPDATE!")
runtime.evaluate(URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText())
runtimeReady = true
kotlin.runCatching {
URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText()
}.getOrNull()?.also { gg ->
withContext(Dispatchers.Main) {
runtime.evaluate(gg)
version = newVersion
runtimeReady = true
}
}
}
}

View File

@@ -16,16 +16,14 @@
package xyz.quaver.pupil.hitomi
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import xyz.quaver.pupil.client
import xyz.quaver.pupil.runtime
import xyz.quaver.pupil.runtimeReady
import java.io.IOException
import java.net.URL
import java.util.concurrent.Executors
@@ -84,11 +82,11 @@ data class GalleryInfo(
val groups: List<Group>? = null,
val parodys: List<Parody>? = null,
val tags: List<Tag>? = null,
val related: List<Int>,
val languages: List<Language>,
val related: List<Int> = emptyList(),
val languages: List<Language> = emptyList(),
val characters: List<Character>? = null,
val scene_indexes: List<Int>,
val files: List<GalleryFiles>
val scene_indexes: List<Int>? = emptyList(),
val files: List<GalleryFiles> = emptyList()
)
val json = Json {
@@ -130,18 +128,21 @@ const val galleryblockextension = ".html"
const val galleryblockdir = "galleryblock"
const val nozomiextension = ".nozomi"
val evaluationContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job()
val evaluationContext = Dispatchers.Main + Job()
object gg {
suspend fun m(g: Int): Int = withContext(evaluationContext) {
while (!runtimeReady) delay(1000)
runtime.evaluate("gg.m($g)").toString().toInt()
}
suspend fun b(): String = withContext(evaluationContext) {
while (!runtimeReady) delay(1000)
runtime.evaluate("gg.b").toString()
}
suspend fun s(h: String): String = withContext(evaluationContext) {
while (!runtimeReady) delay(1000)
runtime.evaluate("gg.s('$h')").toString()
}
}
@@ -197,14 +198,15 @@ suspend fun rewriteTnPaths(html: String) {
}
suspend fun imageUrlFromImage(galleryID: Int, image: GalleryFiles, noWebp: Boolean) : String {
return when {
noWebp ->
urlFromUrlFromHash(galleryID, image)
// image.hasavif != 0 ->
// urlFromUrlFromHash(galleryID, image, "avif", null, "a")
image.haswebp != 0 ->
urlFromUrlFromHash(galleryID, image, "webp", null, "a")
else ->
urlFromUrlFromHash(galleryID, image)
}
return urlFromUrlFromHash(galleryID, image, "webp", null, "a")
// return when {
// noWebp ->
// urlFromUrlFromHash(galleryID, image)
//// image.hasavif != 0 ->
//// urlFromUrlFromHash(galleryID, image, "avif", null, "a")
// image.haswebp != 0 ->
// urlFromUrlFromHash(galleryID, image, "webp", null, "a")
// else ->
// urlFromUrlFromHash(galleryID, image)
// }
}

View File

@@ -71,7 +71,7 @@ data class GalleryBlock(
val type: String,
val language: String,
val relatedTags: List<String>,
val groups: List<String>
val groups: List<String> = emptyList()
)
suspend fun getGalleryBlock(galleryID: Int) : GalleryBlock {

View File

@@ -220,28 +220,23 @@ class DownloadService : Service() {
CoroutineScope(Dispatchers.IO).launch {
runCatching {
response.also {
if (it.code() != 200) throw IOException(
"$galleryID $index ${response.request().url()} CODE ${it.code()}"
val image = response.also { if (it.code() != 200) throw IOException( "$galleryID $index ${response.request().url()} CODE ${it.code()}" ) }.body()?.use { it.bytes() } ?: throw Exception("Response null")
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
Cache.getInstance(this@DownloadService, galleryID)
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", image)
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
notify(galleryID)
if (isCompleted(galleryID)) {
if (DownloadManager.getInstance(this@DownloadService)
.getDownloadFolder(galleryID) != null
)
}.body()?.use {
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
Cache.getInstance(this@DownloadService, galleryID)
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", it.byteStream())
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
notify(galleryID)
if (isCompleted(galleryID)) {
if (DownloadManager.getInstance(this@DownloadService)
.getDownloadFolder(galleryID) != null
)
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
startId?.let { stopSelf(it) }
}
} ?: throw Exception("Response null")
startId?.let { stopSelf(it) }
}
}.onFailure {
FirebaseCrashlytics.getInstance().recordException(it)
}
@@ -329,7 +324,8 @@ class DownloadService : Service() {
}
if (isCompleted(galleryID)) {
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
if (DownloadManager.getInstance(this@DownloadService).getDownloadFolder(galleryID) != null)
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
notificationManager.cancel(galleryID)
startId?.let { stopSelf(it) }

View File

@@ -43,6 +43,7 @@ import xyz.quaver.io.util.readText
import xyz.quaver.io.util.writeText
import xyz.quaver.pupil.R
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.hitomi.json
import xyz.quaver.pupil.util.byteToString
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager
@@ -118,7 +119,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
if (!metadataFile.exists()) return@forEach
val metadata = metadataFile.readText()?.let {
Json.decodeFromString<Metadata>(it)
json.decodeFromString<Metadata>(it)
} ?: return@forEach
val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id?.toIntOrNull() ?: return@forEach

View File

@@ -21,12 +21,9 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context
import android.content.ContextWrapper
import android.net.Uri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@@ -35,35 +32,50 @@ import okhttp3.Request
import xyz.quaver.io.FileX
import xyz.quaver.io.util.*
import xyz.quaver.pupil.client
import xyz.quaver.pupil.hitomi.GalleryBlock
import xyz.quaver.pupil.hitomi.GalleryInfo
import xyz.quaver.pupil.hitomi.getGalleryBlock
import xyz.quaver.pupil.hitomi.getGalleryInfo
import xyz.quaver.pupil.hitomi.*
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.ConcurrentHashMap
@Serializable
data class OldGalleryBlock(
val code: String,
val id: Int,
val galleryUrl: String,
val thumbnails: List<String>,
val title: String,
val artists: List<String>,
val series: List<String>,
val type: String,
val language: String,
val relatedTags: List<String>
data class OldGalleryInfo(
val language_localname: String? = null,
val language: String? = null,
val date: String? = null,
val files: List<OldGalleryFiles>,
val id: Int? = null,
val type: String? = null,
val title: String? = null
)
@Serializable
data class OldGalleryFiles(
val width: Int,
val hash: String,
val haswebp: Int = 0,
val name: String,
val height: Int,
val hasavif: Int = 0,
val hasavifsmalltn: Int? = 0
)
@Serializable
data class OldMetadata(
var galleryBlock: GalleryBlock? = null,
var reader: OldGalleryInfo? = null,
var imageList: MutableList<String?>? = null
) {
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
}
@Serializable
data class Metadata(
var galleryBlock: GalleryBlock? = null,
var galleryInfo: GalleryInfo? = null,
var imageList: MutableList<String?>? = null
) {
constructor(old: OldMetadata) : this(old.galleryBlock, getGalleryInfo(old.galleryBlock?.id ?: throw Exception()), old.imageList)
fun copy(): Metadata = Metadata(galleryBlock, galleryInfo, imageList?.let { MutableList(it.size) { i -> it[i] } })
}
@@ -90,9 +102,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
var metadata = kotlin.runCatching {
findFile(".metadata")?.readText()?.let { metadata ->
Json.decodeFromString<Metadata>(metadata)
kotlin.runCatching {
json.decodeFromString<Metadata>(metadata)
}.getOrElse {
Metadata(json.decodeFromString<OldMetadata>(metadata))
}
}
}.getOrNull() ?: Metadata()
}.onFailure { it.printStackTrace() }.getOrNull() ?: Metadata()
val downloadFolder: FileX?
get() = DownloadManager.getInstance(this).getDownloadFolder(galleryID)
@@ -179,14 +195,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
@Suppress("BlockingMethodInNonBlockingContext")
fun putImage(index: Int, fileName: String, data: InputStream) {
suspend fun putImage(index: Int, fileName: String, data: ByteArray) = coroutineScope {
val file = cacheFolder.getChild(fileName)
if (!file.exists())
file.createNewFile()
file.outputStream()?.use {
data.copyTo(it)
}
file.writeBytes(data)
setMetadata { metadata -> metadata.imageList!![index] = fileName }
}

View File

@@ -77,8 +77,8 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
"-id-" to { id.toString() },
"-title-" to { title },
"-artist-" to { artists.joinToString() },
"-group-" to { groups.joinToString() }
"-artist-" to { if (artists.isNotEmpty()) artists.joinToString() else "N/A" },
"-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" }
// TODO
)
/**
@@ -100,13 +100,11 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
val galleryID = this.id.toIntOrNull() ?: 0
val lowQuality = Preferences["low_quality", true]
return this.files.map {
Request.Builder()
.url(
runCatching {
imageUrlFromImage(galleryID, it, !lowQuality)
imageUrlFromImage(galleryID, it, false)
}
.onFailure {
FirebaseCrashlytics.getInstance().recordException(it)

View File

@@ -56,12 +56,6 @@
app:key="nomedia"
app:title="@string/settings_nomedia_title"/>
<SwitchPreferenceCompat
app:key="low_quality"
app:title="@string/settings_low_quality"
app:summary="@string/settings_low_quality_summary"
app:defaultValue="true"/>
</PreferenceCategory>
<PreferenceCategory