Fixed bug caused by updated hitomi server structure

Version 4.0
This commit is contained in:
tom5079
2019-11-02 20:25:03 +09:00
parent 043f7bedd8
commit 3b682667e1
22 changed files with 193 additions and 143 deletions

View File

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

View File

@@ -13,8 +13,8 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 27 versionCode 29
versionName "3.2" versionName "4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -26,7 +26,7 @@ android {
} }
buildTypes.each { buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'false') it.buildConfigField('boolean', 'PRERELEASE', 'false')
it.buildConfigField('boolean', 'CENSOR', 'true') it.buildConfigField('boolean', 'CENSOR', 'false')
} }
} }
kotlinOptions { kotlinOptions {
@@ -47,19 +47,20 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' 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-coroutines-android:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0" 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.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.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.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.android.support:multidex:1.0.3'
implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.1.0-alpha09' implementation 'com.google.android.material:material:1.2.0-alpha01'
implementation 'com.google.firebase:firebase-core:17.1.0' implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-perf:19.0.0' implementation 'com.google.firebase:firebase-perf:19.0.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4' implementation 'com.github.clans:fab:1.6.4'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":27,"versionName":"3.2","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.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import xyz.quaver.hitomi.fetchNozomi
import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.LockActivity
@@ -50,8 +49,6 @@ class ExampleInstrumentedTest {
// Context of the app under test. // Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("xyz.quaver.pupil", appContext.packageName) assertEquals("xyz.quaver.pupil", appContext.packageName)
Log.d("Pupil", fetchNozomi().first.size.toString())
} }
@Test @Test
@@ -70,7 +67,7 @@ class ExampleInstrumentedTest {
val data: ByteArray 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("User-Agent", user_agent)
setRequestProperty("Cookie", cookie) setRequestProperty("Cookie", cookie)

View File

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

View File

@@ -114,7 +114,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
.parse(Reader.serializer(), readerCache.invoke().readText()) .parse(Reader.serializer(), readerCache.invoke().readText())
with(galleryblock_progressbar) { with(galleryblock_progressbar) {
max = reader.readerItems.size max = reader.galleryInfo.size
progress = imageCache.invoke().list()?.size ?: 0 progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE visibility = View.VISIBLE
@@ -139,7 +139,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
if (visibility == View.GONE) { if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable) val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText()) .parse(Reader.serializer(), readerCache.invoke().readText())
max = reader.readerItems.size max = reader.galleryInfo.size
visibility = View.VISIBLE visibility = View.VISIBLE
} }

View File

@@ -109,11 +109,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
} }
Glide.with(context) Glide.with(context)
.load(gallery.thumbnails.firstOrNull()) .load(gallery.cover)
.apply { .apply {
if (BuildConfig.CENSOR) if (BuildConfig.CENSOR)
override(5, 8) override(5, 8)
}.into(gallery_thumbnail) }.into(gallery_cover)
addDetails(gallery) addDetails(gallery)
addThumbnails(gallery) addThumbnails(gallery)

View File

@@ -371,7 +371,7 @@ class MainActivity : AppCompatActivity() {
} }
private fun checkPermissions() { 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) ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
} }

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.*
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.* import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.hash
import xyz.quaver.pupil.util.hashWithSalt
class PatternLockFragment : Fragment(), PatternLockViewListener { class PatternLockFragment : Fragment(), PatternLockViewListener {

View File

@@ -249,16 +249,16 @@ class ReaderActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
title = it.title title = it.title
with(reader_download_progressbar) { with(reader_download_progressbar) {
max = it.readerItems.size max = it.galleryInfo.size
progress = 0 progress = 0
} }
with(reader_progressbar) { with(reader_progressbar) {
max = it.readerItems.size max = it.galleryInfo.size
progress = 0 progress = 0
} }
gallerySize = it.readerItems.size gallerySize = it.galleryInfo.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}" menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.galleryInfo.size}"
} }
} }
onProgressHandler = { onProgressHandler = {

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,7 @@
android:id="@+id/galleryblock_download" android:id="@+id/galleryblock_download"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp" android:padding="8dp"
android:gravity="center" android:gravity="center"
android:background="@android:color/holo_blue_dark" android:background="@android:color/holo_blue_dark"
@@ -55,6 +56,7 @@
android:id="@+id/galleryblock_delete" android:id="@+id/galleryblock_delete"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp" android:padding="8dp"
android:gravity="center" android:gravity="center"
android:background="@android:color/holo_red_dark" android:background="@android:color/holo_red_dark"

View File

@@ -5,16 +5,14 @@ buildscript {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { maven { url 'https://maven.fabric.io/public' }
url 'https://maven.fabric.io/public'
}
} }
dependencies { 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-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath 'io.fabric.tools:gradle:1.29.0' 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 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:" 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 //common.js
var adapose = false var adapose = false
const val numberOfFrontends = 2 const val numberOfFrontends = 3
const val domain = "ltn.hitomi.la" const val domain = "ltn.hitomi.la"
const val galleryblockdir = "galleryblock" const val galleryblockdir = "galleryblock"
const val nozomiextension = ".nozomi" const val nozomiextension = ".nozomi"
@@ -37,20 +51,22 @@ fun subdomainFromGalleryID(g: Int) : String {
fun subdomainFromURL(url: String, base: String? = null) : String { fun subdomainFromURL(url: String, base: String? = null) : String {
var retval = "a" var retval = "a"
if (base != null) if (!base.isNullOrEmpty())
retval = base retval = base
val r = Regex("""/\d*(\d)/""") val r = Regex("""/galleries/\d*(\d)/""")
val m = r.find(url) 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() val g = m.groupValues[1].toIntOrNull(b) ?: return retval
g ?: return retval
if (g == 1)
g = 0
retval = subdomainFromGalleryID(g) + 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 { 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 package xyz.quaver.hitomi
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.net.URL
import java.net.URLDecoder import java.net.URLDecoder
data class Gallery( data class Gallery(
@@ -35,7 +34,8 @@ data class Gallery(
val thumbnails: List<String> val thumbnails: List<String>
) )
fun getGallery(galleryID: Int) : Gallery { 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() val doc = Jsoup.connect(url).get()
@@ -46,7 +46,7 @@ fun getGallery(galleryID: Int) : Gallery {
}.toList() }.toList()
val langList = doc.select("#lang-list a").map { 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") val cover = protocol + doc.selectFirst(".cover img").attr("src")
@@ -68,11 +68,9 @@ fun getGallery(galleryID: Int) : Gallery {
href.slice(5 until href.indexOf('-')) href.slice(5 until href.indexOf('-'))
} }
val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/.+)',") val thumbnails = getGalleryInfo(galleryID).map {
.findAll(doc.select("script").last().html()) "$protocol//tn.hitomi.la/smalltn/$galleryID/${it.name}.jpg"
.map { }
protocol + it.groups[1]!!.value
}.toList()
return Gallery(related, langList, cover, title, artists, groups, type, language, series, characters, tags, thumbnails) 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 kotlinx.serialization.Serializable
import org.jsoup.Jsoup import org.jsoup.Jsoup
import sun.rmi.runtime.Log
import java.net.URL import java.net.URL
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.ByteBuffer import java.nio.ByteBuffer
@@ -69,6 +68,7 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
@Serializable @Serializable
data class GalleryBlock( data class GalleryBlock(
val id: Int, val id: Int,
val galleryUrl: String,
val thumbnails: List<String>, val thumbnails: List<String>,
val title: String, val title: String,
val artists: List<String>, val artists: List<String>,
@@ -83,6 +83,8 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
try { try {
val doc = Jsoup.connect(url).get() 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 thumbnails = doc.select("img").map { protocol + it.attr("data-src") }
val title = doc.selectFirst("h1.lillie > a").text() val title = doc.selectFirst("h1.lillie > a").text()
@@ -100,7 +102,7 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
href.slice(5 until href.indexOf("-all")) 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) { } catch (e: Exception) {
return null return null
} }

View File

@@ -17,72 +17,28 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import org.jsoup.Jsoup import org.jsoup.Jsoup
import xyz.quaver.hiyobi.HiyobiReader
import java.net.URL
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html" fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp" 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 @Serializable
data class GalleryInfo( data class GalleryInfo(
val width: Int, val width: Int,
val hash: String?,
val haswebp: Int, val haswebp: Int,
val name: String, val name: String,
val height: Int val height: Int
) )
@Serializable
data class ReaderItem(
val url: String,
val galleryInfo: GalleryInfo?
)
@Serializable @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 //Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader { fun getReader(galleryID: Int) : Reader {
val readerUrl = "https://hitomi.la/reader/$galleryID.html" val readerUrl = "https://hitomi.la/reader/$galleryID.html"
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
val doc = Jsoup.connect(readerUrl).get() val doc = Jsoup.connect(readerUrl).get()
val title = doc.title() return Reader(doc.title(), getGalleryInfo(galleryID))
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)
})
} }

View File

@@ -18,18 +18,17 @@ package xyz.quaver.hiyobi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.content import kotlinx.serialization.list
import org.jsoup.Jsoup import org.jsoup.Jsoup
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.ReaderItem import xyz.quaver.hitomi.protocol
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia" 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" 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 = "" var cookie: String = ""
get() { get() {
if (field.isEmpty()) if (field.isEmpty())
@@ -38,6 +37,12 @@ var cookie: String = ""
return field return field
} }
data class Images(
val path: String,
val no: Int,
val name: String
)
fun renewCookie() : String { fun renewCookie() : String {
val url = "https://$hiyobi/" val url = "https://$hiyobi/"
@@ -59,7 +64,8 @@ fun getReader(galleryID: Int) : Reader {
val title = Jsoup.connect(reader).get().title() 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) { with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent) setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie) setRequestProperty("Cookie", cookie)
@@ -70,8 +76,8 @@ fun getReader(galleryID: Int) : Reader {
} }
) )
return Reader(title, json.jsonArray.map { return Reader(title, galleryInfo)
val name = it.jsonObject["name"]!!.content
ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
})
} }
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 package xyz.quaver.hitomi
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class UnitTest { class UnitTest {
@Test @Test
fun test() { fun test() {
assertEquals(
"6/2d/c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6",
fullPathFromHash("c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6")
)
} }
@Test @Test
@@ -63,7 +67,7 @@ class UnitTest {
@Test @Test
fun test_getGallery() { fun test_getGallery() {
val gallery = getGallery(1405267) val gallery = getGallery(1510566)
print(gallery) print(gallery)
} }
@@ -77,6 +81,15 @@ class UnitTest {
@Test @Test
fun test_hiyobi() { 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)
} }
} }