Compare commits

..

10 Commits

Author SHA1 Message Date
tom5079
f2a2656837 Merge pull request #20 from tom5079/development
Development
2019-11-02 20:30:04 +09:00
tom5079
2011572270 Merge remote-tracking branch 'origin/development' into development 2019-11-02 20:29:24 +09:00
tom5079
3b682667e1 Fixed bug caused by updated hitomi server structure
Version 4.0
2019-11-02 20:25:03 +09:00
tom5079
6da8de6463 Merge pull request #19 from tom5079/master
merge readme
2019-11-02 20:08:28 +09:00
tom5079
039d415871 Update README.md
r/engrish
2019-08-31 23:59:41 +09:00
tom5079
776f53bde0 Update README.md 2019-08-30 22:28:07 +09:00
tom5079
58e535595e Update README.md 2019-08-30 22:27:32 +09:00
tom5079
96ad5f6a6c Update README.md 2019-08-30 22:27:08 +09:00
tom5079
043f7bedd8 Added quick download/delete 2019-08-30 15:24:51 +09:00
tom5079
8a58564812 Version 3.2 2019-08-29 12:10:51 +09:00
29 changed files with 544 additions and 343 deletions

View File

@@ -1,5 +1,8 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
@@ -14,6 +17,7 @@
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -24,6 +28,7 @@
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -35,6 +40,7 @@
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -45,6 +51,7 @@
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -55,6 +62,7 @@
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -65,6 +73,7 @@
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -75,6 +84,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -86,6 +96,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -97,6 +108,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>

View File

@@ -1,2 +1,27 @@
# Pupil
Hitomi.la viewer for Android
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android*
# Screenshot
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.png?raw=true)
*Main Screen*
![Reader Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/reader-screenshot.png?raw=true)
*Reader Screen*
Images are censored to be SFW
# Installation
Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or
Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean)
or Build app yourself
# Manual
[Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean. Consider using translator.
# Contribution
Any kind of contribution is appriciated. Feel free to leave PR!

View File

@@ -13,8 +13,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
versionCode 27
versionName "3.2-beta2"
versionCode 29
versionName "4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
@@ -25,7 +25,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'true')
it.buildConfigField('boolean', 'PRERELEASE', 'false')
it.buildConfigField('boolean', 'CENSOR', 'false')
}
}
@@ -47,18 +47,20 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-rc01'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.biometric:biometric:1.0.0-rc02"
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.google.android.material:material:1.1.0-alpha09'
implementation 'com.google.firebase:firebase-core:17.1.0'
implementation 'com.google.firebase:firebase-perf:19.0.0'
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.2.0-alpha01'
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-perf:19.0.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4'
@@ -67,7 +69,6 @@ dependencies {
transitive = false
}
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
implementation 'com.jsibbold:zoomage:1.3.0'
implementation "ru.noties.markwon:core:${markwonVersion}"
kapt 'com.github.bumptech.glide:compiler:4.9.0'
testImplementation 'junit:junit:4.12'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":25,"versionName":"3.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":29,"versionName":"4.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -26,11 +26,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import xyz.quaver.hitomi.fetchNozomi
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity
@@ -50,8 +49,6 @@ class ExampleInstrumentedTest {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("xyz.quaver.pupil", appContext.packageName)
Log.d("Pupil", fetchNozomi().first.size.toString())
}
@Test
@@ -70,7 +67,7 @@ class ExampleInstrumentedTest {
val data: ByteArray
with(URL(reader.readerItems[0].url).openConnection() as HttpsURLConnection) {
with(URL(createImgList(1426382, reader)[0].path).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xyz.quaver.pupil">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<application
android:name=".Pupil"
@@ -15,7 +17,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:replace="android:theme">
<activity android:name=".ui.LockActivity"/>
<activity

View File

@@ -31,6 +31,9 @@ import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.daimajia.swipe.SwipeLayout
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope
@@ -45,6 +48,7 @@ import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.wordCapitalize
@@ -54,7 +58,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
enum class ViewType {
NEXT,
@@ -64,7 +68,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
private lateinit var favorites: Histories
inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
with(view) {
val resources = context.resources
@@ -110,7 +114,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
.parse(Reader.serializer(), readerCache.invoke().readText())
with(galleryblock_progressbar) {
max = reader.readerItems.size
max = reader.galleryInfo.size
progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE
@@ -135,7 +139,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
max = reader.readerItems.size
max = reader.galleryInfo.size
visibility = View.VISIBLE
}
@@ -276,6 +280,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
var onDownloadClickedHandler: ((Int) -> Unit)? = null
var onDeleteClickedHandler: ((Int) -> Unit)? = null
var showNext = false
var showPrev = false
@@ -301,8 +307,47 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GalleryViewHolder)
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
if (holder is GalleryViewHolder) {
val gallery = galleries[position-(if (showPrev) 1 else 0)]
holder.bind(gallery)
with(holder.view.galleryblock_primary) {
setOnClickListener {
holder.view.performClick()
}
setOnLongClickListener {
holder.view.performLongClick()
}
}
holder.view.galleryblock_download.setOnClickListener {
onDownloadClickedHandler?.invoke(position)
}
holder.view.galleryblock_delete.setOnClickListener {
onDeleteClickedHandler?.invoke(position)
}
mItemManger.bindView(holder.view, position)
holder.view.galleryblock_swipe_layout.addSwipeListener(object: SwipeLayout.SwipeListener {
override fun onStartOpen(layout: SwipeLayout?) {
mItemManger.closeAllExcept(layout)
holder.view.galleryblock_download.text = when(GalleryDownloader.get(gallery.first.id)) {
null -> holder.view.context.getString(R.string.main_download)
else -> holder.view.context.getString(R.string.main_cancel_download)
}
}
override fun onClose(layout: SwipeLayout?) {}
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
override fun onOpen(layout: SwipeLayout?) {}
override fun onStartClose(layout: SwipeLayout?) {}
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
})
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
@@ -328,4 +373,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
else -> ViewType.GALLERY
}.ordinal
}
override fun getSwipeLayoutResourceId(position: Int) = R.id.galleryblock_swipe_layout
}

View File

@@ -18,18 +18,13 @@
package xyz.quaver.pupil.adapters
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.getCachedGallery
@@ -40,7 +35,6 @@ class ReaderAdapter(private val glide: RequestManager,
private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var isFullScreen = false
private var prev : Drawable? = null
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
@@ -64,29 +58,7 @@ class ReaderAdapter(private val glide: RequestManager,
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
if (isFullScreen)
placeholder(prev)
}
.listener(object: RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
) = false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
prev = resource?.constantState?.newDrawable()?.mutate()
return false
}
})
.into(holder.view)
}

View File

@@ -22,19 +22,14 @@ import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import androidx.core.content.ContextCompat
import androidx.gridlayout.widget.GridLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_galleryblock.*
@@ -45,6 +40,7 @@ import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
@@ -113,8 +109,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
}
Glide.with(context)
.load(gallery.thumbnails.firstOrNull())
.into(gallery_thumbnail)
.load(gallery.cover)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}.into(gallery_cover)
addDetails(gallery)
addThumbnails(gallery)

View File

@@ -371,7 +371,7 @@ class MainActivity : AppCompatActivity() {
}
private fun checkPermissions() {
if (this.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE))
if (!hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE))
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
}
@@ -535,6 +535,52 @@ class MainActivity : AppCompatActivity() {
loadBlocks()
}
}
onDownloadClickedHandler = { position ->
val galleryID = galleries[position].first.id
if (!completeFlag.get(galleryID, false)) {
val downloader = GalleryDownloader.get(galleryID)
if (downloader == null)
GalleryDownloader(context, galleryID, true).start()
else {
downloader.cancel()
downloader.clearNotification()
}
}
closeAllItems()
}
onDeleteClickedHandler = { position ->
val galleryID = galleries[position].first.id
CoroutineScope(Dispatchers.Default).launch {
with(GalleryDownloader[galleryID]) {
this?.cancelAndJoin()
this?.clearNotification()
}
val cache = File(cacheDir, "imageCache/${galleryID}")
val data = getCachedGallery(context, galleryID)
cache.deleteRecursively()
data.deleteRecursively()
downloads.remove(galleryID)
if (this@MainActivity.mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
completeFlag.put(galleryID, false)
}
closeAllItems()
}
}
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, v ->

View File

@@ -29,8 +29,6 @@ import com.andrognito.patternlockview.utils.PatternLockUtils
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.hash
import xyz.quaver.pupil.util.hashWithSalt
class PatternLockFragment : Fragment(), PatternLockViewListener {

View File

@@ -249,16 +249,16 @@ class ReaderActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Main).launch {
title = it.title
with(reader_download_progressbar) {
max = it.readerItems.size
max = it.galleryInfo.size
progress = 0
}
with(reader_progressbar) {
max = it.readerItems.size
max = it.galleryInfo.size
progress = 0
}
gallerySize = it.readerItems.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
gallerySize = it.galleryInfo.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.galleryInfo.size}"
}
}
onProgressHandler = {
@@ -282,7 +282,7 @@ class ReaderActivity : AppCompatActivity() {
onErrorHandler = {
Snackbar
.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.reader_help) { _ ->
.setAction(R.string.reader_help) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.error_help))))
}
.show()

View File

@@ -29,13 +29,15 @@ import androidx.core.app.TaskStackBuilder
import androidx.preference.PreferenceManager
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.*
import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReader
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hitomi.urlFromUrlFromHash
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
@@ -83,7 +85,7 @@ class GalleryDownloader(
onNotifyChangedHandler?.invoke(value)
}
private val reader: Deferred<Reader>?
private val reader: Deferred<Reader?>?
private var downloadJob: Job? = null
private lateinit var notificationBuilder: NotificationCompat.Builder
@@ -121,11 +123,8 @@ class GalleryDownloader(
if (cache.exists()) {
val cached = json.parse(serializer, cache.readText())
if (cached.readerItems.isNotEmpty()) {
useHiyobi = when {
cached.readerItems[0].url.contains("hitomi.la") -> false
else -> true
}
if (cached.galleryInfo.isNotEmpty()) {
useHiyobi = availableInHiyobi(galleryID)
onReaderLoadedHandler?.invoke(cached)
@@ -148,7 +147,7 @@ class GalleryDownloader(
}
}
if (reader.readerItems.isNotEmpty()) {
if (reader.galleryInfo.isNotEmpty()) {
//Save cache
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
@@ -159,7 +158,8 @@ class GalleryDownloader(
reader
} catch (e: Exception) {
Crashlytics.logException(e)
Reader("", listOf())
onErrorHandler?.invoke(e)
null
}
}
}
@@ -168,29 +168,36 @@ class GalleryDownloader(
fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await()
val reader = reader!!.await() ?: return@launch
notificationBuilder.setContentTitle(reader.title)
if (reader.readerItems.isEmpty()) {
onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
return@launch
}
val list = ArrayList<String>()
onReaderLoadedHandler?.invoke(reader)
notificationBuilder
.setProgress(reader.readerItems.size, 0, false)
.setContentText("0/${reader.readerItems.size}")
.setProgress(reader.galleryInfo.size, 0, false)
.setContentText("0/${reader.galleryInfo.size}")
reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, it ->
reader.galleryInfo.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, galleryInfo ->
val index = chunkIndex*4+i
async(Dispatchers.IO) {
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
val url = when(useHiyobi) {
true -> createImgList(galleryID, reader)[index].path
false -> when (galleryInfo.haswebp) {
1 -> webpUrlFromUrl(
urlFromUrlFromHash(
galleryID,
galleryInfo,
true
)
)
else -> urlFromUrlFromHash(galleryID, galleryInfo)
}
}
val name = "$index".padStart(4, '0')
val ext = url.split('.').last()
@@ -234,8 +241,8 @@ class GalleryDownloader(
onProgressHandler?.invoke(index)
notificationBuilder
.setProgress(reader.readerItems.size, index, false)
.setContentText("$index/${reader.readerItems.size}")
.setProgress(reader.galleryInfo.size, index, false)
.setContentText("$index/${reader.galleryInfo.size}")
if (download)
notificationManager.notify(galleryID, notificationBuilder.build())

View File

@@ -21,8 +21,6 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.content.ContextCompat
import java.io.File
fun getCachedGallery(context: Context, galleryID: Int): File {

View File

@@ -54,7 +54,7 @@ fun checkUpdate(url: String) : JsonObject? {
fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
return releases["assets"]?.jsonArray?.firstOrNull {
Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
}.let {
if (it == null)
null

View File

@@ -29,7 +29,29 @@
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/lock_button_layout"/>
app:layout_constraintBottom_toTopOf="@id/lock_fingerprint_layout"/>
<LinearLayout
android:id="@+id/lock_fingerprint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/lock_content"
app:layout_constraintBottom_toTopOf="@id/lock_button_layout">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/fingerprint"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
</LinearLayout>
<LinearLayout
android:id="@+id/lock_button_layout"
@@ -37,7 +59,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
app:layout_constraintTop_toBottomOf="@id/lock_content"
app:layout_constraintTop_toBottomOf="@id/lock_fingerprint_layout"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center">
@@ -59,16 +81,6 @@
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/fingerprint"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_password"
android:layout_width="wrap_content"

View File

@@ -40,7 +40,7 @@
android:padding="8dp">
<ImageView
android:id="@+id/gallery_thumbnail"
android:id="@+id/gallery_cover"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
@@ -55,7 +55,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
@@ -66,7 +66,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/gallery_title"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
@@ -83,7 +83,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>

View File

@@ -23,161 +23,203 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
app:cardCornerRadius="8dp"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true">
android:clipChildren="true">
<LinearLayout
<com.daimajia.swipe.SwipeLayout
android:id="@+id/galleryblock_swipe_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/galleryblock_progressbar"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_progress_complete"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:contentDescription="@string/reader_imageview_description"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_thumbnail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:contentDescription="@string/galleryblock_thumbnail_description"
android:adjustViewBounds="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Headline"
android:id="@+id/galleryblock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/galleryblock_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
<TextView
android:id="@+id/galleryblock_series"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/galleryblock_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<TextView
android:id="@+id/galleryblock_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<View
android:id="@+id/galleryblock_padding"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:chipSpacing="2dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
android:background="@android:color/darker_gray"/>
app:drag_edge="right"
app:show_mode="pull_out">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:orientation="horizontal">
android:id="@+id/galleryblock_secondary"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/galleryblock_id"
android:id="@+id/galleryblock_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp"
android:gravity="center"
android:background="@android:color/holo_blue_dark"
android:textColor="@android:color/white"
android:text="@string/main_download"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/galleryblock_favorite"
android:contentDescription="@string/app_name"
android:layout_width="32dp"
android:layout_height="32dp"
app:srcCompat="@drawable/ic_star_empty"/>
<TextView
android:id="@+id/galleryblock_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp"
android:gravity="center"
android:background="@android:color/holo_red_dark"
android:textColor="@android:color/white"
android:text="@string/main_delete"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/galleryblock_primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/galleryblock_progressbar"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_progress_complete"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:contentDescription="@string/reader_imageview_description"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_thumbnail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:contentDescription="@string/galleryblock_thumbnail_description"
android:adjustViewBounds="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Headline"
android:id="@+id/galleryblock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/galleryblock_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
<TextView
android:id="@+id/galleryblock_series"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/galleryblock_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<TextView
android:id="@+id/galleryblock_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<View
android:id="@+id/galleryblock_padding"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:chipSpacing="2dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
android:background="@android:color/darker_gray"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/galleryblock_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/galleryblock_favorite"
android:contentDescription="@string/app_name"
android:layout_width="32dp"
android:layout_height="32dp"
app:srcCompat="@drawable/ic_star_empty"/>
</LinearLayout>
</LinearLayout>
</com.daimajia.swipe.SwipeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -99,7 +99,10 @@
<string name="gallery_tags">タグ</string>
<string name="gallery_thumbnails">サムネイル</string>
<string name="gallery_related">おすすめ</string>
<string name="settings_nomedia_summary">イメージを隠す</string>
<string name="settings_nomedia_title">イメージをギャラリーから見えなくする</string>
<string name="settings_nomedia_summary">イメージをギャラリーから見えなくする</string>
<string name="settings_nomedia_title">イメージを隠す</string>
<string name="reader_help">ヘルプ</string>
<string name="main_delete">削除</string>
<string name="main_download">ダウンロード</string>
<string name="main_cancel_download">キャンセル</string>
</resources>

View File

@@ -99,7 +99,10 @@
<string name="gallery_tags">태그</string>
<string name="gallery_related">관련 갤러리</string>
<string name="gallery_thumbnails">미리보기</string>
<string name="settings_nomedia_summary">이미지 숨기기</string>
<string name="settings_nomedia_title">갤러리에서 이미지 검색이 되지 않도록 합니다</string>
<string name="settings_nomedia_summary">갤러리에서 이미지 검색이 되지 않도록 합니다</string>
<string name="settings_nomedia_title">이미지 숨기기</string>
<string name="reader_help">도움말</string>
<string name="main_delete">삭제</string>
<string name="main_download">다운로드</string>
<string name="main_cancel_download">취소</string>
</resources>

View File

@@ -71,6 +71,10 @@
<string name="main_export_open_folder">Open Folder</string>
<string name="main_export_error">Error occurred during export</string>
<string name="main_download">DOWNLOAD</string>
<string name="main_cancel_download">CANCEL</string>
<string name="main_delete">DELETE</string>
<string name="update_title">Update available</string>
<string name="update_download_started">Download started</string>
<string name="update_notification_description">Downloading apk&#8230;</string>

View File

@@ -5,16 +5,14 @@ buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.1'
classpath 'com.google.gms:google-services:4.3.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'io.fabric.tools:gradle:1.29.0'

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 tom5079
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.quaver
fun availableInHiyobi(galleryID: Int) : Boolean {
return try {
xyz.quaver.hiyobi.getReader(galleryID)
true
} catch (e: Exception) {
false
}
}

View File

@@ -16,11 +16,25 @@
package xyz.quaver.hitomi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import java.net.URL
const val protocol = "https:"
fun getGalleryInfo(galleryID: Int): List<GalleryInfo> {
return Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
Regex("""\[.+]""").find(
URL("$protocol//$domain/galleries/$galleryID.js").readText()
)?.value ?: "[]"
)
}
//common.js
var adapose = false
const val numberOfFrontends = 2
const val numberOfFrontends = 3
const val domain = "ltn.hitomi.la"
const val galleryblockdir = "galleryblock"
const val nozomiextension = ".nozomi"
@@ -37,20 +51,22 @@ fun subdomainFromGalleryID(g: Int) : String {
fun subdomainFromURL(url: String, base: String? = null) : String {
var retval = "a"
if (base != null)
if (!base.isNullOrEmpty())
retval = base
val r = Regex("""/\d*(\d)/""")
val m = r.find(url)
val r = Regex("""/galleries/\d*(\d)/""")
var m = r.find(url)
var b = 10
m ?: return retval
if (m == null) {
b = 16
val r2 = Regex("""/images/[0-9a-f]/([0-9a-f]{2})/""")
m = r2.find(url)
if (m == null)
return retval
}
var g = m.groups[1]!!.value.toIntOrNull()
g ?: return retval
if (g == 1)
g = 0
val g = m.groupValues[1].toIntOrNull(b) ?: return retval
retval = subdomainFromGalleryID(g) + retval
@@ -58,5 +74,22 @@ fun subdomainFromURL(url: String, base: String? = null) : String {
}
fun urlFromURL(url: String, base: String? = null) : String {
return url.replace(Regex("//..?\\.hitomi\\.la/"), "//${subdomainFromURL(url, base)}.hitomi.la/")
}
return url.replace(Regex("""//..?\.hitomi\.la/"""), "//${subdomainFromURL(url, base)}.hitomi.la/")
}
fun fullPathFromHash(hash: String?) : String? {
return when {
hash?.length ?: 0 < 3 -> hash
else -> hash!!.replace(Regex("^.*(..)(.)$"), "$2/$1/$hash")
}
}
fun urlFromHash(galleryID: Int, image: GalleryInfo, oldMethod: Boolean) : String {
return when {
oldMethod or image.hash.isNullOrEmpty() -> "$protocol//a.hitomi.la/galleries/$galleryID/${image.name}"
else -> "$protocol//a.hitomi.la/images/${fullPathFromHash(image.hash)}.${image.name.split('.').last()}"
}
}
fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, oldMethod: Boolean = false) =
urlFromURL(urlFromHash(galleryID, image, oldMethod))

View File

@@ -17,7 +17,6 @@
package xyz.quaver.hitomi
import org.jsoup.Jsoup
import java.net.URL
import java.net.URLDecoder
data class Gallery(
@@ -35,7 +34,8 @@ data class Gallery(
val thumbnails: List<String>
)
fun getGallery(galleryID: Int) : Gallery {
val url = "https://hitomi.la/galleries/$galleryID.html"
val url = Jsoup.connect("https://hitomi.la/galleries/$galleryID.html").get()
.select("a").attr("href")
val doc = Jsoup.connect(url).get()
@@ -46,7 +46,7 @@ fun getGallery(galleryID: Int) : Gallery {
}.toList()
val langList = doc.select("#lang-list a").map {
Pair(it.text(), it.attr("href").replace(".html", ""))
Pair(it.text(), "$protocol//hitomi.la${it.attr("href")}")
}
val cover = protocol + doc.selectFirst(".cover img").attr("src")
@@ -68,11 +68,9 @@ fun getGallery(galleryID: Int) : Gallery {
href.slice(5 until href.indexOf('-'))
}
val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/.+)',")
.findAll(doc.select("script").last().html())
.map {
protocol + it.groups[1]!!.value
}.toList()
val thumbnails = getGalleryInfo(galleryID).map {
"$protocol//tn.hitomi.la/smalltn/$galleryID/${it.name}.jpg"
}
return Gallery(related, langList, cover, title, artists, groups, type, language, series, characters, tags, thumbnails)
}

View File

@@ -18,7 +18,6 @@ package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import sun.rmi.runtime.Log
import java.net.URL
import java.net.URLDecoder
import java.nio.ByteBuffer
@@ -69,6 +68,7 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
@Serializable
data class GalleryBlock(
val id: Int,
val galleryUrl: String,
val thumbnails: List<String>,
val title: String,
val artists: List<String>,
@@ -83,6 +83,8 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
try {
val doc = Jsoup.connect(url).get()
val galleryUrl = doc.selectFirst(".lillie").attr("href")
val thumbnails = doc.select("img").map { protocol + it.attr("data-src") }
val title = doc.selectFirst("h1.lillie > a").text()
@@ -100,7 +102,7 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
href.slice(5 until href.indexOf("-all"))
}
return GalleryBlock(galleryID, thumbnails, title, artists, series, type, language, relatedTags)
return GalleryBlock(galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
} catch (e: Exception) {
return null
}

View File

@@ -17,72 +17,28 @@
package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import org.jsoup.Jsoup
import xyz.quaver.hiyobi.HiyobiReader
import java.net.URL
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
fun webpReaderFromReader(reader: Reader) : Reader {
if (reader is HiyobiReader)
return reader
return Reader(reader.title, reader.readerItems.map {
ReaderItem(
if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url,
it.galleryInfo
)
})
}
@Serializable
data class GalleryInfo(
val width: Int,
val hash: String?,
val haswebp: Int,
val name: String,
val height: Int
)
@Serializable
data class ReaderItem(
val url: String,
val galleryInfo: GalleryInfo?
)
@Serializable
open class Reader(val title: String, val readerItems: List<ReaderItem>)
open class Reader(val title: String, val galleryInfo: List<GalleryInfo>)
//Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader {
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
val doc = Jsoup.connect(readerUrl).get()
val title = doc.title()
val images = doc.select(".img-url").map {
protocol + urlFromURL(it.text())
}
val galleryInfo = ArrayList<GalleryInfo?>()
galleryInfo.addAll(
Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
Regex("""\[.+]""").find(
URL(galleryInfoUrl).readText()
)?.value ?: "[]"
)
)
if (images.size > galleryInfo.size)
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
return Reader(title, (images zip galleryInfo).map {
ReaderItem(it.first, it.second)
})
return Reader(doc.title(), getGalleryInfo(galleryID))
}

View File

@@ -18,18 +18,17 @@ package xyz.quaver.hiyobi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.content
import kotlinx.serialization.list
import org.jsoup.Jsoup
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.ReaderItem
import xyz.quaver.hitomi.protocol
import java.net.URL
import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia"
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
class HiyobiReader(title: String, readerItems: List<ReaderItem>) : Reader(title, readerItems)
var cookie: String = ""
get() {
if (field.isEmpty())
@@ -38,6 +37,12 @@ var cookie: String = ""
return field
}
data class Images(
val path: String,
val no: Int,
val name: String
)
fun renewCookie() : String {
val url = "https://$hiyobi/"
@@ -59,7 +64,8 @@ fun getReader(galleryID: Int) : Reader {
val title = Jsoup.connect(reader).get().title()
val json = Json(JsonConfiguration.Stable).parseJson(
val galleryInfo = Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
@@ -70,8 +76,8 @@ fun getReader(galleryID: Int) : Reader {
}
)
return Reader(title, json.jsonArray.map {
val name = it.jsonObject["name"]!!.content
ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
})
}
return Reader(title, galleryInfo)
}
fun createImgList(galleryID: Int, reader: Reader) =
reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }

View File

@@ -18,12 +18,16 @@
package xyz.quaver.hitomi
import org.junit.Assert.assertEquals
import org.junit.Test
class UnitTest {
@Test
fun test() {
assertEquals(
"6/2d/c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6",
fullPathFromHash("c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6")
)
}
@Test
@@ -63,7 +67,7 @@ class UnitTest {
@Test
fun test_getGallery() {
val gallery = getGallery(1405267)
val gallery = getGallery(1510566)
print(gallery)
}
@@ -77,6 +81,15 @@ class UnitTest {
@Test
fun test_hiyobi() {
xyz.quaver.hiyobi.getReader(1510567)
}
@Test
fun test_urlFromUrlFromHash() {
val url = urlFromUrlFromHash(1510702, GalleryInfo(
210, "56e9e1b8bb72194777ed93fee11b06070b905039dd11348b070bcf1793aaed7b", 1, "6.jpg", 300
))
print(url)
}
}