mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Add EH code.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,4 +6,5 @@
|
|||||||
.idea/
|
.idea/
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
*/build
|
*/build
|
||||||
|
/mainframer.sh
|
||||||
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
custom.gradle
|
||||||
|
google-services.json
|
@ -28,18 +28,19 @@ ext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 25
|
||||||
buildToolsVersion "25.0.1"
|
buildToolsVersion "25.0.1"
|
||||||
publishNonDefault true
|
publishNonDefault true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "eu.kanade.tachiyomi"
|
applicationId "eu.kanade.tachiyomi.eh"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 17
|
versionCode 4000
|
||||||
versionName "0.4.1"
|
versionName "v4.0.0-EH"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
@ -190,15 +191,25 @@ dependencies {
|
|||||||
compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
|
compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
|
||||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||||
|
|
||||||
|
//Firebase (EH)
|
||||||
|
final firebase_version = '10.0.1'
|
||||||
|
releaseCompile "com.google.firebase:firebase-core:$firebase_version"
|
||||||
|
releaseCompile "com.google.firebase:firebase-messaging:$firebase_version"
|
||||||
|
releaseCompile "com.google.firebase:firebase-crash:$firebase_version"
|
||||||
|
|
||||||
|
//SnappyDB (EH)
|
||||||
|
compile 'io.paperdb:paperdb:2.0'
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testCompile 'junit:junit:4.12'
|
//Paper DB screws up tests
|
||||||
|
/*testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.assertj:assertj-core:1.7.1'
|
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
|
|
||||||
final robolectric_version = '3.1.4'
|
final robolectric_version = '3.1.4'
|
||||||
testCompile "org.robolectric:robolectric:$robolectric_version"
|
testCompile "org.robolectric:robolectric:$robolectric_version"
|
||||||
testCompile "org.robolectric:shadows-multidex:$robolectric_version"
|
testCompile "org.robolectric:shadows-multidex:$robolectric_version"
|
||||||
testCompile "org.robolectric:shadows-play-services:$robolectric_version"
|
testCompile "org.robolectric:shadows-play-services:$robolectric_version"*/
|
||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
}
|
}
|
||||||
@ -216,3 +227,6 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Firebase (EH)
|
||||||
|
//apply plugin: 'com.google.gms.google-services'
|
||||||
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@ -95,4 +95,7 @@
|
|||||||
-dontwarn org.yaml.snakeyaml.**
|
-dontwarn org.yaml.snakeyaml.**
|
||||||
|
|
||||||
# Duktape
|
# Duktape
|
||||||
-keep class com.squareup.duktape.** { *; }
|
-keep class com.squareup.duktape.** { *; }
|
||||||
|
|
||||||
|
# [EH]
|
||||||
|
-keep class exh.** { *; }
|
@ -98,6 +98,13 @@
|
|||||||
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||||
android:value="GlideModule" />
|
android:value="GlideModule" />
|
||||||
|
|
||||||
|
<!-- EH -->
|
||||||
|
<activity
|
||||||
|
android:name="exh.ui.login.LoginActivity"
|
||||||
|
android:label="@string/label_login"
|
||||||
|
android:parentActivityName=".ui.setting.SettingsActivity" >
|
||||||
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -8,6 +8,7 @@ import com.evernote.android.job.JobManager
|
|||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
||||||
import eu.kanade.tachiyomi.util.LocaleHelper
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
|
import io.paperdb.Paper
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -33,6 +34,7 @@ open class App : Application() {
|
|||||||
|
|
||||||
setupAcra()
|
setupAcra()
|
||||||
setupJobManager()
|
setupJobManager()
|
||||||
|
Paper.init(this) //Setup metadata DB (EH)
|
||||||
|
|
||||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||||
}
|
}
|
||||||
|
@ -144,4 +144,13 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun lang() = prefs.getString(keys.lang, "")
|
fun lang() = prefs.getString(keys.lang, "")
|
||||||
|
|
||||||
|
//EH
|
||||||
|
fun enableExhentai() = rxPrefs.getBoolean("enable_exhentai", false)
|
||||||
|
|
||||||
|
fun secureEXH() = rxPrefs.getBoolean("secure_exh", true)
|
||||||
|
|
||||||
|
//EH Cookies
|
||||||
|
fun memberIdVal() = rxPrefs.getString("eh_ipb_member_id", null)
|
||||||
|
fun passHashVal() = rxPrefs.getString("eh_ipb_pass_hash", null)
|
||||||
|
fun igneousVal() = rxPrefs.getString("eh_igneous", null)
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,12 @@ import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.data.source.online.YamlOnlineSource
|
import eu.kanade.tachiyomi.data.source.online.YamlOnlineSource
|
||||||
|
import eu.kanade.tachiyomi.data.source.online.all.EHentai
|
||||||
|
import eu.kanade.tachiyomi.data.source.online.all.EHentaiMetadata
|
||||||
import eu.kanade.tachiyomi.data.source.online.english.*
|
import eu.kanade.tachiyomi.data.source.online.english.*
|
||||||
import eu.kanade.tachiyomi.data.source.online.german.WieManga
|
import eu.kanade.tachiyomi.data.source.online.german.WieManga
|
||||||
import eu.kanade.tachiyomi.data.source.online.russian.Mangachan
|
import eu.kanade.tachiyomi.data.source.online.russian.Mangachan
|
||||||
@ -14,11 +18,14 @@ import eu.kanade.tachiyomi.data.source.online.russian.Readmanga
|
|||||||
import eu.kanade.tachiyomi.util.hasPermission
|
import eu.kanade.tachiyomi.util.hasPermission
|
||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class SourceManager(private val context: Context) {
|
open class SourceManager(private val context: Context) {
|
||||||
|
|
||||||
private val sourcesMap = createSources()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
private var sourcesMap = createSources()
|
||||||
|
|
||||||
open fun get(sourceKey: Int): Source? {
|
open fun get(sourceKey: Int): Source? {
|
||||||
return sourcesMap[sourceKey]
|
return sourcesMap[sourceKey]
|
||||||
@ -27,19 +34,39 @@ open class SourceManager(private val context: Context) {
|
|||||||
fun getOnlineSources() = sourcesMap.values.filterIsInstance(OnlineSource::class.java)
|
fun getOnlineSources() = sourcesMap.values.filterIsInstance(OnlineSource::class.java)
|
||||||
|
|
||||||
private fun createOnlineSourceList(): List<Source> = listOf(
|
private fun createOnlineSourceList(): List<Source> = listOf(
|
||||||
Batoto(1),
|
Batoto(101),
|
||||||
Mangahere(2),
|
Mangahere(102),
|
||||||
Mangafox(3),
|
Mangafox(103),
|
||||||
Kissmanga(4),
|
Kissmanga(104),
|
||||||
Readmanga(5),
|
Readmanga(105),
|
||||||
Mintmanga(6),
|
Mintmanga(106),
|
||||||
Mangachan(7),
|
Mangachan(107),
|
||||||
Readmangatoday(8),
|
Readmangatoday(108),
|
||||||
Mangasee(9),
|
Mangasee(109),
|
||||||
WieManga(10)
|
WieManga(110)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun createEHSources(): List<Source> {
|
||||||
|
val exSrcs = mutableListOf(
|
||||||
|
EHentai(1, false, context),
|
||||||
|
EHentaiMetadata(3, false, context)
|
||||||
|
)
|
||||||
|
if(prefs.enableExhentai().getOrDefault()) {
|
||||||
|
exSrcs += EHentai(2, true, context)
|
||||||
|
exSrcs += EHentaiMetadata(4, true, context)
|
||||||
|
}
|
||||||
|
return exSrcs
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
prefs.enableExhentai().asObservable().subscribe {
|
||||||
|
//Refresh sources when ExHentai enabled/disabled change
|
||||||
|
sourcesMap = createSources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createSources(): Map<Int, Source> = hashMapOf<Int, Source>().apply {
|
private fun createSources(): Map<Int, Source> = hashMapOf<Int, Source>().apply {
|
||||||
|
createEHSources().forEach { put(it.id, it) }
|
||||||
createOnlineSourceList().forEach { put(it.id, it) }
|
createOnlineSourceList().forEach { put(it.id, it) }
|
||||||
|
|
||||||
val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
||||||
|
@ -7,7 +7,7 @@ import rx.subjects.Subject
|
|||||||
|
|
||||||
class Page(
|
class Page(
|
||||||
val index: Int,
|
val index: Int,
|
||||||
val url: String = "",
|
var url: String = "",
|
||||||
var imageUrl: String? = null,
|
var imageUrl: String? = null,
|
||||||
@Transient var uri: Uri? = null
|
@Transient var uri: Uri? = null
|
||||||
) : ProgressListener {
|
) : ProgressListener {
|
||||||
|
@ -0,0 +1,343 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.source.online.all
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.network.GET
|
||||||
|
import eu.kanade.tachiyomi.data.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import exh.metadata.*
|
||||||
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import exh.metadata.models.Tag
|
||||||
|
import exh.plusAssign
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import java.util.*
|
||||||
|
import exh.ui.login.LoginActivity
|
||||||
|
|
||||||
|
class EHentai(override val id: Int,
|
||||||
|
val exh: Boolean,
|
||||||
|
val context: Context) : OnlineSource() {
|
||||||
|
|
||||||
|
|
||||||
|
val schema: String
|
||||||
|
get() = if(prefs.secureEXH().getOrDefault())
|
||||||
|
"https"
|
||||||
|
else
|
||||||
|
"http"
|
||||||
|
|
||||||
|
override val baseUrl: String
|
||||||
|
get() = if(exh)
|
||||||
|
"$schema://exhentai.org"
|
||||||
|
else
|
||||||
|
"http://g.e-hentai.org"
|
||||||
|
|
||||||
|
override val lang = "all"
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
val metadataHelper = MetadataHelper()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gallery list entry
|
||||||
|
*/
|
||||||
|
data class ParsedManga(val fav: String?, val manga: Manga)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a list of galleries
|
||||||
|
*/
|
||||||
|
fun genericMangaParse(response: Response, page: MangasPage? = null)
|
||||||
|
= with(response.asJsoup()) {
|
||||||
|
//Parse mangas
|
||||||
|
val parsedMangas = select(".gtr0,.gtr1").map {
|
||||||
|
ParsedManga(
|
||||||
|
fav = it.select(".itd .it3 > .i[id]").first()?.attr("title"),
|
||||||
|
manga = Manga.create(id).apply {
|
||||||
|
//Get title
|
||||||
|
it.select(".itd .it5 a").first()?.apply {
|
||||||
|
title = text()
|
||||||
|
setUrlWithoutDomain(addParam(attr("href"), "nw", "always"))
|
||||||
|
}
|
||||||
|
//Get image
|
||||||
|
it.select(".itd .it2").first()?.apply {
|
||||||
|
children().first()?.let {
|
||||||
|
thumbnail_url = it.attr("src")
|
||||||
|
} ?: let {
|
||||||
|
text().split("~").apply {
|
||||||
|
thumbnail_url = "http://${this[1]}/${this[2]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
//Add to page if required
|
||||||
|
page?.let { page ->
|
||||||
|
page.mangas += parsedMangas.map { it.manga }
|
||||||
|
select("a[onclick=return false]").last()?.let {
|
||||||
|
if(it.text() == ">") page.nextPageUrl = it.attr("href")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Return parsed mangas anyways
|
||||||
|
parsedMangas
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>>
|
||||||
|
= Observable.just(listOf(Chapter.create().apply {
|
||||||
|
manga_id = manga.id
|
||||||
|
url = manga.url
|
||||||
|
name = "Chapter"
|
||||||
|
chapter_number = 1f
|
||||||
|
}))
|
||||||
|
|
||||||
|
override fun fetchPageListFromNetwork(chapter: Chapter)
|
||||||
|
= fetchChapterPage(chapter, 0).map {
|
||||||
|
it.mapIndexed { i, s ->
|
||||||
|
Page(i, s)
|
||||||
|
}
|
||||||
|
}!!
|
||||||
|
|
||||||
|
private fun fetchChapterPage(chapter: Chapter, id: Int): Observable<List<String>> {
|
||||||
|
val urls = mutableListOf<String>()
|
||||||
|
return chapterPageCall(chapter, id).flatMap {
|
||||||
|
val jsoup = it.asJsoup()
|
||||||
|
urls += parseChapterPage(jsoup)
|
||||||
|
if(nextPageUrl(jsoup) != null) {
|
||||||
|
fetchChapterPage(chapter, id + 1)
|
||||||
|
} else {
|
||||||
|
Observable.just(urls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun parseChapterPage(response: Element)
|
||||||
|
= with(response) {
|
||||||
|
select(".gdtm a").map {
|
||||||
|
Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
|
||||||
|
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
||||||
|
}
|
||||||
|
private fun chapterPageCall(chapter: Chapter, pn: Int) = client.newCall(chapterPageRequest(chapter, pn)).asObservableSuccess()
|
||||||
|
private fun chapterPageRequest(chapter: Chapter, pn: Int) = GET("$baseUrl${chapter.url}?p=$pn", headers)
|
||||||
|
|
||||||
|
private fun nextPageUrl(element: Element): String?
|
||||||
|
= element.select("a[onclick=return false]").last()?.let {
|
||||||
|
return if (it.text() == ">") it.attr("href") else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildGenreString(filters: List<OnlineSource.Filter>): String {
|
||||||
|
val genreString = StringBuilder()
|
||||||
|
for (genre in GENRE_LIST) {
|
||||||
|
genreString += "&f_"
|
||||||
|
genreString += genre
|
||||||
|
genreString += "="
|
||||||
|
genreString += if (filters.isEmpty()
|
||||||
|
|| !filters
|
||||||
|
.map { it.id }
|
||||||
|
.find { it == genre }
|
||||||
|
.isNullOrEmpty())
|
||||||
|
"1"
|
||||||
|
else
|
||||||
|
"0"
|
||||||
|
}
|
||||||
|
return genreString.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaInitialUrl() = if(exh)
|
||||||
|
latestUpdatesInitialUrl()
|
||||||
|
else
|
||||||
|
"$baseUrl/toplist.php?tl=15"
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response, page: MangasPage) {
|
||||||
|
genericMangaParse(response, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaInitialUrl(query: String, filters: List<Filter>)
|
||||||
|
= "$baseUrl$QUERY_PREFIX${buildGenreString(filters)}&f_search=${URLEncoder.encode(query, "UTF-8")}"
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) {
|
||||||
|
genericMangaParse(response, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesInitialUrl() = baseUrl
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response, page: MangasPage) {
|
||||||
|
genericMangaParse(response, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse gallery page to metadata model
|
||||||
|
*/
|
||||||
|
override fun mangaDetailsParse(response: Response, manga: Manga) = with(response.asJsoup()) {
|
||||||
|
val metdata = ExGalleryMetadata()
|
||||||
|
with(metdata) {
|
||||||
|
url = manga.url
|
||||||
|
exh = this@EHentai.exh
|
||||||
|
title = select("#gn").text().nullIfBlank()
|
||||||
|
altTitle = select("#gj").text().nullIfBlank()
|
||||||
|
|
||||||
|
thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()
|
||||||
|
|
||||||
|
genre = select(".ic").attr("alt").nullIfBlank()
|
||||||
|
|
||||||
|
uploader = select("#gdn").text().nullIfBlank()
|
||||||
|
|
||||||
|
//Parse the table
|
||||||
|
select("#gdd tr").forEach {
|
||||||
|
it.select(".gdt1")
|
||||||
|
.text()
|
||||||
|
.nullIfBlank()
|
||||||
|
?.trim()
|
||||||
|
?.let { left ->
|
||||||
|
it.select(".gdt2")
|
||||||
|
.text()
|
||||||
|
.nullIfBlank()
|
||||||
|
?.trim()
|
||||||
|
?.let { right ->
|
||||||
|
ignore {
|
||||||
|
when (left.removeSuffix(":")
|
||||||
|
.toLowerCase()) {
|
||||||
|
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||||
|
"visible" -> visible = right.nullIfBlank()
|
||||||
|
"language" -> {
|
||||||
|
language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank()
|
||||||
|
translated = right.endsWith(TR_SUFFIX, true)
|
||||||
|
}
|
||||||
|
"file size" -> size = parseHumanReadableByteCount(right)?.toLong()
|
||||||
|
"length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt()
|
||||||
|
"favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse ratings
|
||||||
|
ignore {
|
||||||
|
averageRating = select("#rating_label")
|
||||||
|
.text()
|
||||||
|
.removePrefix("Average:")
|
||||||
|
.trim()
|
||||||
|
.nullIfBlank()
|
||||||
|
?.toDouble()
|
||||||
|
ratingCount = select("#rating_count")
|
||||||
|
.text()
|
||||||
|
.trim()
|
||||||
|
.nullIfBlank()
|
||||||
|
?.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse tags
|
||||||
|
tags.clear()
|
||||||
|
select("#taglist tr").forEach {
|
||||||
|
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||||
|
val currentTags = it.select("div").map {
|
||||||
|
Tag(it.text().trim(),
|
||||||
|
it.hasClass("gtl"))
|
||||||
|
}
|
||||||
|
tags.put(namespace, ArrayList(currentTags))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save metadata
|
||||||
|
metadataHelper.writeGallery(this)
|
||||||
|
|
||||||
|
//Copy metadata to manga
|
||||||
|
copyTo(manga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response, chapters: MutableList<Chapter>) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response, pages: MutableList<Page>) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy and paste from OnlineSource as we need the page argument
|
||||||
|
override public fun fetchImageUrl(page: Page): Observable<Page> {
|
||||||
|
page.status = Page.LOAD_PAGE
|
||||||
|
return client
|
||||||
|
.newCall(imageUrlRequest(page))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { imageUrlParse(it, page) }
|
||||||
|
.doOnError { page.status = Page.ERROR }
|
||||||
|
.onErrorReturn { null }
|
||||||
|
.doOnNext { page.imageUrl = it }
|
||||||
|
.map { page }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun imageUrlParse(response: Response, page: Page): String {
|
||||||
|
with(response.asJsoup()) {
|
||||||
|
val currentImage = select("img[onerror]").attr("src")
|
||||||
|
//Each press of the retry button will choose another server
|
||||||
|
select("#loadfail").attr("onclick").nullIfBlank()?.let {
|
||||||
|
page.url = addParam(page.url, "nl", it.substring(it.indexOf('\'') + 1 .. it.lastIndexOf('\'') - 1))
|
||||||
|
}
|
||||||
|
return currentImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cookiesHeader by lazy {
|
||||||
|
val cookies: MutableMap<String, String> = HashMap()
|
||||||
|
if(prefs.enableExhentai().getOrDefault()) {
|
||||||
|
cookies.put(LoginActivity.MEMBER_ID_COOKIE, prefs.memberIdVal().getOrDefault())
|
||||||
|
cookies.put(LoginActivity.PASS_HASH_COOKIE, prefs.passHashVal().getOrDefault())
|
||||||
|
cookies.put(LoginActivity.IGNEOUS_COOKIE, prefs.igneousVal().getOrDefault())
|
||||||
|
}
|
||||||
|
buildCookies(cookies)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Headers
|
||||||
|
override fun headersBuilder()
|
||||||
|
= super.headersBuilder().add("Cookie", cookiesHeader)!!
|
||||||
|
|
||||||
|
fun buildCookies(cookies: Map<String, String>)
|
||||||
|
= cookies.entries.map {
|
||||||
|
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
||||||
|
}.joinToString(separator = "; ", postfix = ";")
|
||||||
|
|
||||||
|
fun addParam(url: String, param: String, value: String)
|
||||||
|
= Uri.parse(url)
|
||||||
|
.buildUpon()
|
||||||
|
.appendQueryParameter(param, value)
|
||||||
|
.toString()
|
||||||
|
|
||||||
|
override val client = super.client.newBuilder()
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
val newReq = chain
|
||||||
|
.request()
|
||||||
|
.newBuilder()
|
||||||
|
.addHeader("Cookie", cookiesHeader)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
chain.proceed(newReq)
|
||||||
|
}.build()!!
|
||||||
|
|
||||||
|
//Filters
|
||||||
|
val generatedFilters = GENRE_LIST.map { Filter(it, it) }
|
||||||
|
override fun getFilterList() = generatedFilters
|
||||||
|
|
||||||
|
override val name = if(exh)
|
||||||
|
"ExHentai"
|
||||||
|
else
|
||||||
|
"E-Hentai"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val QUERY_PREFIX = "?f_apply=Apply+Filter"
|
||||||
|
val GENRE_LIST = arrayOf("doujinshi", "manga", "artistcg", "gamecg", "western", "non-h", "imageset", "cosplay", "asianporn", "misc")
|
||||||
|
val TR_SUFFIX = "TR"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.source.online.all
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
|
import exh.metadata.MetadataHelper
|
||||||
|
import exh.metadata.copyTo
|
||||||
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline metadata store source
|
||||||
|
*/
|
||||||
|
|
||||||
|
class EHentaiMetadata(override val id: Int,
|
||||||
|
val exh: Boolean,
|
||||||
|
val context: Context) : OnlineSource() {
|
||||||
|
|
||||||
|
val metadataHelper = MetadataHelper()
|
||||||
|
|
||||||
|
val internalEx = EHentai(id - 2, exh, context)
|
||||||
|
|
||||||
|
override val baseUrl: String
|
||||||
|
get() = throw UnsupportedOperationException()
|
||||||
|
override val lang: String
|
||||||
|
get() = "advanced"
|
||||||
|
override val supportsLatest: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override fun popularMangaInitialUrl(): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response, page: MangasPage) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaInitialUrl(query: String, filters: List<Filter>): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesInitialUrl(): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response, page: MangasPage) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response, manga: Manga) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response, chapters: MutableList<Chapter>) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response, pages: MutableList<Page>) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>>
|
||||||
|
= Observable.just(listOf(Chapter.create().apply {
|
||||||
|
manga_id = manga.id
|
||||||
|
url = manga.url
|
||||||
|
name = "ONLINE - Chapter"
|
||||||
|
chapter_number = 1f
|
||||||
|
}))
|
||||||
|
|
||||||
|
override fun fetchPageListFromNetwork(chapter: Chapter) = internalEx.fetchPageListFromNetwork(chapter)
|
||||||
|
|
||||||
|
override fun fetchImageUrl(page: Page) = internalEx.fetchImageUrl(page)
|
||||||
|
|
||||||
|
fun List<ExGalleryMetadata>.mapToManga() = filter { it.exh == exh }
|
||||||
|
.map {
|
||||||
|
Manga.create(id).apply {
|
||||||
|
it.copyTo(this)
|
||||||
|
source = this@EHentaiMetadata.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortedByTimeGalleries() = metadataHelper.getAllGalleries().sortedByDescending {
|
||||||
|
it.datePosted ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchPopularManga(page: MangasPage)
|
||||||
|
= Observable.fromCallable {
|
||||||
|
page.mangas.addAll(metadataHelper.getAllGalleries().sortedByDescending {
|
||||||
|
it.ratingCount ?: 0
|
||||||
|
}.mapToManga())
|
||||||
|
page
|
||||||
|
}!!
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>)
|
||||||
|
= Observable.fromCallable {
|
||||||
|
page.mangas.addAll(sortedByTimeGalleries().filter { manga ->
|
||||||
|
filters.isEmpty() || filters.filter { it.id == manga.genre }.isNotEmpty()
|
||||||
|
}.mapToManga())
|
||||||
|
page
|
||||||
|
}!!
|
||||||
|
|
||||||
|
override fun fetchLatestUpdates(page: MangasPage)
|
||||||
|
= Observable.fromCallable {
|
||||||
|
page.mangas.addAll(sortedByTimeGalleries().mapToManga())
|
||||||
|
page
|
||||||
|
}!!
|
||||||
|
|
||||||
|
override fun fetchMangaDetails(manga: Manga) = Observable.fromCallable {
|
||||||
|
//Hack to convert the gallery into an online gallery when favoriting it or reading it
|
||||||
|
metadataHelper.fetchMetadata(manga.url, exh).copyTo(manga)
|
||||||
|
manga
|
||||||
|
}!!
|
||||||
|
|
||||||
|
override fun getFilterList() = internalEx.getFilterList()
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = if(exh) {
|
||||||
|
"ExHentai"
|
||||||
|
} else {
|
||||||
|
"E-Hentai"
|
||||||
|
} + " - METADATA"
|
||||||
|
|
||||||
|
}
|
@ -23,7 +23,7 @@ interface GithubService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET("/repos/inorichi/tachiyomi/releases/latest")
|
@GET("/repos/NerdNumber9/tachiyomi/releases/latest")
|
||||||
fun getLatestVersion(): Observable<GithubRelease>
|
fun getLatestVersion(): Observable<GithubRelease>
|
||||||
|
|
||||||
}
|
}
|
@ -66,6 +66,7 @@ class SettingsActivity : BaseActivity(),
|
|||||||
"tracking_screen" -> SettingsTrackingFragment.newInstance(key)
|
"tracking_screen" -> SettingsTrackingFragment.newInstance(key)
|
||||||
"advanced_screen" -> SettingsAdvancedFragment.newInstance(key)
|
"advanced_screen" -> SettingsAdvancedFragment.newInstance(key)
|
||||||
"about_screen" -> SettingsAboutFragment.newInstance(key)
|
"about_screen" -> SettingsAboutFragment.newInstance(key)
|
||||||
|
"eh_screen" -> SettingsEhFragment.newInstance(key) //EH
|
||||||
else -> SettingsFragment.newInstance(key)
|
else -> SettingsFragment.newInstance(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.preference.XpPreferenceFragment
|
||||||
|
import android.view.View
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.util.plusAssign
|
||||||
|
import exh.ui.login.LoginActivity
|
||||||
|
import net.xpece.android.support.preference.SwitchPreference
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EH Settings fragment
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SettingsEhFragment : SettingsFragment() {
|
||||||
|
companion object {
|
||||||
|
fun newInstance(rootKey: String): SettingsEhFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
|
||||||
|
return SettingsEhFragment().apply { arguments = args }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
val enableExhentaiPref by lazy {
|
||||||
|
findPreference("enable_exhentai") as SwitchPreference
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedState)
|
||||||
|
|
||||||
|
subscriptions += preferences
|
||||||
|
.enableExhentai()
|
||||||
|
.asObservable().subscribe {
|
||||||
|
enableExhentaiPref.isChecked = it
|
||||||
|
}
|
||||||
|
|
||||||
|
enableExhentaiPref.setOnPreferenceChangeListener { preference, newVal ->
|
||||||
|
newVal as Boolean
|
||||||
|
if(!newVal) {
|
||||||
|
preferences.enableExhentai().set(false)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
startActivity(Intent(context, LoginActivity::class.java))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ open class SettingsFragment : XpPreferenceFragment() {
|
|||||||
addPreferencesFromResource(R.xml.pref_downloads)
|
addPreferencesFromResource(R.xml.pref_downloads)
|
||||||
addPreferencesFromResource(R.xml.pref_sources)
|
addPreferencesFromResource(R.xml.pref_sources)
|
||||||
addPreferencesFromResource(R.xml.pref_tracking)
|
addPreferencesFromResource(R.xml.pref_tracking)
|
||||||
|
addPreferencesFromResource(R.xml.eh_pref_eh) //EH
|
||||||
addPreferencesFromResource(R.xml.pref_advanced)
|
addPreferencesFromResource(R.xml.pref_advanced)
|
||||||
addPreferencesFromResource(R.xml.pref_about)
|
addPreferencesFromResource(R.xml.pref_about)
|
||||||
|
|
||||||
|
191
app/src/main/java/exh/FavoritesSyncManager.java
Normal file
191
app/src/main/java/exh/FavoritesSyncManager.java
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package exh;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory;
|
||||||
|
//import eu.kanade.tachiyomi.data.source.online.english.EHentai;
|
||||||
|
|
||||||
|
public class FavoritesSyncManager {
|
||||||
|
/*
|
||||||
|
Context context;
|
||||||
|
DatabaseHelper db;
|
||||||
|
|
||||||
|
public FavoritesSyncManager(Context context, DatabaseHelper db) {
|
||||||
|
this.context = context;
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void guiSyncFavorites(final Runnable onComplete) {
|
||||||
|
if(!DialogLogin.isLoggedIn(context, false)) {
|
||||||
|
new AlertDialog.Builder(context).setTitle("Error")
|
||||||
|
.setMessage("You are not logged in! Please log in and try again!")
|
||||||
|
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ProgressDialog dialog = ProgressDialog.show(context, "Downloading Favorites", "Please wait...", true, false);
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Handler mainLooper = new Handler(Looper.getMainLooper());
|
||||||
|
try {
|
||||||
|
syncFavorites();
|
||||||
|
} catch (Exception e) {
|
||||||
|
mainLooper.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
|
.setTitle("Error")
|
||||||
|
.setMessage("There was an error downloading your favorites, please try again later!")
|
||||||
|
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
dialog.dismiss();
|
||||||
|
mainLooper.post(onComplete);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncFavorites() throws IOException {
|
||||||
|
EHentai.FavoritesResponse favResponse = EHentai.fetchFavorites(context);
|
||||||
|
Map<String, List<Manga>> favorites = favResponse.favs;
|
||||||
|
List<Category> ourCategories = new ArrayList<>(db.getCategories().executeAsBlocking());
|
||||||
|
List<Manga> ourMangas = new ArrayList<>(db.getMangas().executeAsBlocking());
|
||||||
|
//Add required categories (categories do not sync upwards)
|
||||||
|
List<Category> categoriesToInsert = new ArrayList<>();
|
||||||
|
for (String theirCategory : favorites.keySet()) {
|
||||||
|
boolean haveCategory = false;
|
||||||
|
for (Category category : ourCategories) {
|
||||||
|
if (category.getName().endsWith(theirCategory)) {
|
||||||
|
haveCategory = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!haveCategory) {
|
||||||
|
Category category = Category.Companion.create(theirCategory);
|
||||||
|
ourCategories.add(category);
|
||||||
|
categoriesToInsert.add(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!categoriesToInsert.isEmpty()) {
|
||||||
|
for(Map.Entry<Category, PutResult> result : db.insertCategories(categoriesToInsert).executeAsBlocking().results().entrySet()) {
|
||||||
|
if(result.getValue().wasInserted()) {
|
||||||
|
result.getKey().setId(result.getValue().insertedId().intValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Build category map
|
||||||
|
Map<String, Category> categoryMap = new HashMap<>();
|
||||||
|
for (Category category : ourCategories) {
|
||||||
|
categoryMap.put(category.getName(), category);
|
||||||
|
}
|
||||||
|
//Insert new mangas
|
||||||
|
List<Manga> mangaToInsert = new ArrayList<>();
|
||||||
|
Map<Manga, Category> mangaToSetCategories = new HashMap<>();
|
||||||
|
for (Map.Entry<String, List<Manga>> entry : favorites.entrySet()) {
|
||||||
|
Category category = categoryMap.get(entry.getKey());
|
||||||
|
for (Manga manga : entry.getValue()) {
|
||||||
|
boolean alreadyHaveManga = false;
|
||||||
|
for (Manga ourManga : ourMangas) {
|
||||||
|
if (ourManga.getUrl().equals(manga.getUrl())) {
|
||||||
|
alreadyHaveManga = true;
|
||||||
|
manga = ourManga;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!alreadyHaveManga) {
|
||||||
|
ourMangas.add(manga);
|
||||||
|
mangaToInsert.add(manga);
|
||||||
|
}
|
||||||
|
mangaToSetCategories.put(manga, category);
|
||||||
|
manga.setFavorite(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<Manga, PutResult> results : db.insertMangas(mangaToInsert).executeAsBlocking().results().entrySet()) {
|
||||||
|
if(results.getValue().wasInserted()) {
|
||||||
|
results.getKey().setId(results.getValue().insertedId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Map.Entry<Manga, Category> entry : mangaToSetCategories.entrySet()) {
|
||||||
|
db.setMangaCategories(Collections.singletonList(MangaCategory.Companion.create(entry.getKey(), entry.getValue())),
|
||||||
|
Collections.singletonList(entry.getKey()));
|
||||||
|
}
|
||||||
|
//Determines what
|
||||||
|
/*Map<Integer, List<Manga>> toUpload = new HashMap<>();
|
||||||
|
for (Manga manga : ourMangas) {
|
||||||
|
if(manga.getFavorite()) {
|
||||||
|
boolean remoteHasManga = false;
|
||||||
|
for (List<Manga> remoteMangas : favorites.values()) {
|
||||||
|
for (Manga remoteManga : remoteMangas) {
|
||||||
|
if (remoteManga.getUrl().equals(manga.getUrl())) {
|
||||||
|
remoteHasManga = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!remoteHasManga) {
|
||||||
|
List<Category> mangaCategories = db.getCategoriesForManga(manga).executeAsBlocking();
|
||||||
|
for (Category category : mangaCategories) {
|
||||||
|
int categoryIndex = favResponse.favCategories.indexOf(category.getName());
|
||||||
|
if (categoryIndex >= 0) {
|
||||||
|
List<Manga> uploadMangas = toUpload.get(categoryIndex);
|
||||||
|
if (uploadMangas == null) {
|
||||||
|
uploadMangas = new ArrayList<>();
|
||||||
|
toUpload.put(categoryIndex, uploadMangas);
|
||||||
|
}
|
||||||
|
uploadMangas.add(manga);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
/********** NON-FUNCTIONAL, modifygids[] CANNOT ADD NEW FAVORITES! (or as of my testing it can't, maybe I'll do more testing)**/
|
||||||
|
/*PreferencesHelper helper = new PreferencesHelper(context);
|
||||||
|
for(Map.Entry<Integer, List<Manga>> entry : toUpload.entrySet()) {
|
||||||
|
FormBody.Builder formBody = new FormBody.Builder()
|
||||||
|
.add("ddact", "fav" + entry.getKey());
|
||||||
|
for(Manga manga : entry.getValue()) {
|
||||||
|
List<String> splitUrl = new ArrayList<>(Arrays.asList(manga.getUrl().split("/")));
|
||||||
|
splitUrl.removeAll(Collections.singleton(""));
|
||||||
|
if(splitUrl.size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
formBody.add("modifygids[]", splitUrl.get(1).trim());
|
||||||
|
}
|
||||||
|
formBody.add("apply", "Apply");
|
||||||
|
Request request = RequestsKt.POST(EHentai.buildFavoritesBase(context, helper.getPrefs()).favoritesBase,
|
||||||
|
EHentai.getHeadersBuilder(helper).build(),
|
||||||
|
formBody.build(),
|
||||||
|
RequestsKt.getDEFAULT_CACHE_CONTROL());
|
||||||
|
Response response = NetworkManager.getInstance().getClient().newCall(request).execute();
|
||||||
|
Util.d("EHentai", response.body().string());
|
||||||
|
}*/
|
||||||
|
// }
|
||||||
|
}
|
3
app/src/main/java/exh/StringBuilderExtensions.kt
Normal file
3
app/src/main/java/exh/StringBuilderExtensions.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package exh
|
||||||
|
|
||||||
|
operator fun StringBuilder.plusAssign(other: String) { append(other) }
|
22
app/src/main/java/exh/metadata/MetadataHelper.kt
Normal file
22
app/src/main/java/exh/metadata/MetadataHelper.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package exh.metadata
|
||||||
|
|
||||||
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import io.paperdb.Paper
|
||||||
|
|
||||||
|
class MetadataHelper {
|
||||||
|
|
||||||
|
fun writeGallery(galleryMetadata: ExGalleryMetadata)
|
||||||
|
= exGalleryBook().write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)
|
||||||
|
|
||||||
|
fun fetchMetadata(url: String, exh: Boolean) = ExGalleryMetadata().apply {
|
||||||
|
this.url = url
|
||||||
|
this.exh = exh
|
||||||
|
return exGalleryBook().read<ExGalleryMetadata>(galleryUniqueIdentifier())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllGalleries() = exGalleryBook().allKeys.map {
|
||||||
|
exGalleryBook().read<ExGalleryMetadata>(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exGalleryBook() = Paper.book("gallery-ex")!!
|
||||||
|
}
|
47
app/src/main/java/exh/metadata/MetadataUtil.kt
Normal file
47
app/src/main/java/exh/metadata/MetadataUtil.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package exh.metadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata utils
|
||||||
|
*/
|
||||||
|
fun humanReadableByteCount(bytes: Long, si: Boolean): String {
|
||||||
|
val unit = if (si) 1000 else 1024
|
||||||
|
if (bytes < unit) return bytes.toString() + " B"
|
||||||
|
val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
|
||||||
|
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
|
||||||
|
return String.format("%.1f %sB", bytes / Math.pow(unit.toDouble(), exp.toDouble()), pre)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val KB_FACTOR: Long = 1000
|
||||||
|
private val KIB_FACTOR: Long = 1024
|
||||||
|
private val MB_FACTOR = 1000 * KB_FACTOR
|
||||||
|
private val MIB_FACTOR = 1024 * KIB_FACTOR
|
||||||
|
private val GB_FACTOR = 1000 * MB_FACTOR
|
||||||
|
private val GIB_FACTOR = 1024 * MIB_FACTOR
|
||||||
|
|
||||||
|
fun parseHumanReadableByteCount(arg0: String): Double? {
|
||||||
|
val spaceNdx = arg0.indexOf(" ")
|
||||||
|
val ret = java.lang.Double.parseDouble(arg0.substring(0, spaceNdx))
|
||||||
|
when (arg0.substring(spaceNdx + 1)) {
|
||||||
|
"GB" -> return ret * GB_FACTOR
|
||||||
|
"GiB" -> return ret * GIB_FACTOR
|
||||||
|
"MB" -> return ret * MB_FACTOR
|
||||||
|
"MiB" -> return ret * MIB_FACTOR
|
||||||
|
"KB" -> return ret * KB_FACTOR
|
||||||
|
"KiB" -> return ret * KIB_FACTOR
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun String?.nullIfBlank(): String? = if(isNullOrBlank())
|
||||||
|
null
|
||||||
|
else
|
||||||
|
this
|
||||||
|
|
||||||
|
fun <T> ignore(expr: () -> T): T? {
|
||||||
|
return try { expr() } catch (t: Throwable) { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K,V> Set<Map.Entry<K,V>>.forEach(action: (K, V) -> Unit) {
|
||||||
|
forEach { action(it.key, it.value) }
|
||||||
|
}
|
94
app/src/main/java/exh/metadata/MetdataCopier.kt
Normal file
94
app/src/main/java/exh/metadata/MetdataCopier.kt
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package exh.metadata
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.util.UrlUtil
|
||||||
|
import exh.metadata.models.ExGalleryMetadata
|
||||||
|
import exh.metadata.models.Tag
|
||||||
|
import exh.plusAssign
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies gallery metadata to a manga object
|
||||||
|
*/
|
||||||
|
|
||||||
|
private const val ARTIST_NAMESPACE = "artist"
|
||||||
|
private const val AUTHOR_NAMESPACE = "author"
|
||||||
|
|
||||||
|
private val ONGOING_SUFFIX = arrayOf(
|
||||||
|
"[ongoing]",
|
||||||
|
"(ongoing)",
|
||||||
|
"{ongoing}"
|
||||||
|
)
|
||||||
|
|
||||||
|
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
||||||
|
|
||||||
|
fun ExGalleryMetadata.copyTo(manga: Manga) {
|
||||||
|
exh?.let {
|
||||||
|
manga.source = if(it)
|
||||||
|
2
|
||||||
|
else
|
||||||
|
1
|
||||||
|
}
|
||||||
|
url?.let { manga.url = it }
|
||||||
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
title?.let { manga.title = it }
|
||||||
|
|
||||||
|
//Set artist (if we can find one)
|
||||||
|
tags[ARTIST_NAMESPACE]?.let {
|
||||||
|
if(it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name)
|
||||||
|
}
|
||||||
|
//Set author (if we can find one)
|
||||||
|
tags[AUTHOR_NAMESPACE]?.let {
|
||||||
|
if(it.isNotEmpty()) manga.author = it.joinToString(transform = Tag::name)
|
||||||
|
}
|
||||||
|
//Set genre
|
||||||
|
genre?.let { manga.genre = it }
|
||||||
|
|
||||||
|
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
|
||||||
|
//We default to completed
|
||||||
|
manga.status = Manga.COMPLETED
|
||||||
|
title?.let { t ->
|
||||||
|
ONGOING_SUFFIX.find {
|
||||||
|
t.endsWith(it, ignoreCase = true)
|
||||||
|
}?.let {
|
||||||
|
manga.status = Manga.ONGOING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Build a nice looking description out of what we know
|
||||||
|
val titleDesc = StringBuilder()
|
||||||
|
title?.let { titleDesc += "Title: $it\n" }
|
||||||
|
altTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
||||||
|
|
||||||
|
val detailsDesc = StringBuilder()
|
||||||
|
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
||||||
|
datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
||||||
|
visible?.let { detailsDesc += "Visible: $it\n" }
|
||||||
|
language?.let {
|
||||||
|
detailsDesc += "Language: $it"
|
||||||
|
if(translated == true) detailsDesc += " TR"
|
||||||
|
detailsDesc += "\n"
|
||||||
|
}
|
||||||
|
size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" }
|
||||||
|
length?.let { detailsDesc += "Length: $it pages\n" }
|
||||||
|
favorites?.let { detailsDesc += "Favorited: $it times\n" }
|
||||||
|
averageRating?.let {
|
||||||
|
detailsDesc += "Rating: $it"
|
||||||
|
ratingCount?.let { detailsDesc += " ($it)" }
|
||||||
|
detailsDesc += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsDesc = StringBuilder("Tags:\n")
|
||||||
|
//BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
||||||
|
tags.entries.forEach { namespace, tags ->
|
||||||
|
if(tags.isNotEmpty()) {
|
||||||
|
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
||||||
|
tagsDesc += "▪ $namespace: $joinedTags\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manga.description = listOf(titleDesc, detailsDesc, tagsDesc)
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
}
|
52
app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
Normal file
52
app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gallery metadata storage model
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ExGalleryMetadata {
|
||||||
|
var url: String? = null
|
||||||
|
|
||||||
|
var exh: Boolean? = null
|
||||||
|
|
||||||
|
var title: String? = null
|
||||||
|
var altTitle: String? = null
|
||||||
|
|
||||||
|
var thumbnailUrl: String? = null
|
||||||
|
|
||||||
|
var genre: String? = null
|
||||||
|
|
||||||
|
var uploader: String? = null
|
||||||
|
var datePosted: Long? = null
|
||||||
|
var parent: String? = null
|
||||||
|
var visible: String? = null
|
||||||
|
var language: String? = null
|
||||||
|
var translated: Boolean? = null
|
||||||
|
var size: Long? = null
|
||||||
|
var length: Int? = null
|
||||||
|
var favorites: Int? = null
|
||||||
|
var ratingCount: Int? = null
|
||||||
|
var averageRating: Double? = null
|
||||||
|
|
||||||
|
//Being specific about which classes are used in generics to make deserialization easier
|
||||||
|
var tags: HashMap<String, ArrayList<Tag>> = HashMap()
|
||||||
|
|
||||||
|
private fun splitGalleryUrl()
|
||||||
|
= url?.let {
|
||||||
|
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun galleryId() = splitGalleryUrl()?.let { it[it.size - 2] }
|
||||||
|
|
||||||
|
fun galleryToken() =
|
||||||
|
splitGalleryUrl()?.last()
|
||||||
|
|
||||||
|
fun galleryUniqueIdentifier() = exh?.let { exh ->
|
||||||
|
url?.let {
|
||||||
|
"${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
app/src/main/java/exh/metadata/models/Tag.kt
Normal file
7
app/src/main/java/exh/metadata/models/Tag.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package exh.metadata.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple tag model
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class Tag(var name: String, var light: Boolean)
|
182
app/src/main/java/exh/ui/login/LoginActivity.kt
Normal file
182
app/src/main/java/exh/ui/login/LoginActivity.kt
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package exh.ui.login
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||||
|
import kotlinx.android.synthetic.main.eh_activity_login.*
|
||||||
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
|
import timber.log.Timber
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.HttpCookie
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginActivity
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
|
val preferenceManager: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setAppTheme()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.eh_activity_login)
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
setupToolbar(toolbar, backNavigation = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setup() {
|
||||||
|
btn_cancel.setOnClickListener { onBackPressed() }
|
||||||
|
btn_recheck.setOnClickListener { webview.loadUrl("http://exhentai.org/") }
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
CookieManager.getInstance().removeAllCookies {
|
||||||
|
runOnUiThread {
|
||||||
|
startWebview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CookieManager.getInstance().removeAllCookie()
|
||||||
|
startWebview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startWebview() {
|
||||||
|
webview.settings.javaScriptEnabled = true
|
||||||
|
webview.settings.domStorageEnabled = true
|
||||||
|
|
||||||
|
webview.loadUrl("https://forums.e-hentai.org/index.php?act=Login")
|
||||||
|
|
||||||
|
webview.setWebViewClient(object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
Timber.d(url)
|
||||||
|
val parsedUrl = Uri.parse(url)
|
||||||
|
if(parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
|
||||||
|
//Hide distracting content
|
||||||
|
view.loadUrl(HIDE_JS)
|
||||||
|
|
||||||
|
//Check login result
|
||||||
|
if(parsedUrl.getQueryParameter("code")?.toInt() != 0) {
|
||||||
|
if(checkLoginCookies(url)) view.loadUrl("http://exhentai.org/")
|
||||||
|
}
|
||||||
|
} else if(parsedUrl.host.equals("exhentai.org", ignoreCase = true)) {
|
||||||
|
//At ExHentai, check that everything worked out...
|
||||||
|
if(applyExHentaiCookies(url)) {
|
||||||
|
preferenceManager.enableExhentai().set(true)
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we are logged in
|
||||||
|
*/
|
||||||
|
fun checkLoginCookies(url: String): Boolean {
|
||||||
|
getCookies(url)?.let { parsed ->
|
||||||
|
return parsed.filter {
|
||||||
|
(it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true)
|
||||||
|
|| it.name.equals(PASS_HASH_COOKIE, ignoreCase = true))
|
||||||
|
&& it.value.isNotBlank()
|
||||||
|
}.count() >= 2
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse cookies at ExHentai
|
||||||
|
*/
|
||||||
|
fun applyExHentaiCookies(url: String): Boolean {
|
||||||
|
getCookies(url)?.let { parsed ->
|
||||||
|
|
||||||
|
var memberId: String? = null
|
||||||
|
var passHash: String? = null
|
||||||
|
var igneous: String? = null
|
||||||
|
|
||||||
|
parsed.forEach {
|
||||||
|
when (it.name.toLowerCase()) {
|
||||||
|
MEMBER_ID_COOKIE -> memberId = it.value
|
||||||
|
PASS_HASH_COOKIE -> passHash = it.value
|
||||||
|
IGNEOUS_COOKIE -> igneous = it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Missing a cookie
|
||||||
|
if (memberId == null || passHash == null || igneous == null) return false
|
||||||
|
|
||||||
|
//Update prefs
|
||||||
|
preferenceManager.memberIdVal().set(memberId)
|
||||||
|
preferenceManager.passHashVal().set(passHash)
|
||||||
|
preferenceManager.igneousVal().set(igneous)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCookies(url: String): List<HttpCookie>?
|
||||||
|
= CookieManager.getInstance().getCookie(url)?.let {
|
||||||
|
it.split("; ").flatMap {
|
||||||
|
HttpCookie.parse(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
android.R.id.home -> onBackPressed()
|
||||||
|
else -> return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MEMBER_ID_COOKIE = "ipb_member_id"
|
||||||
|
const val PASS_HASH_COOKIE = "ipb_pass_hash"
|
||||||
|
const val IGNEOUS_COOKIE = "igneous"
|
||||||
|
|
||||||
|
const val HIDE_JS = """
|
||||||
|
javascript:(function () {
|
||||||
|
document.getElementsByTagName('body')[0].style.visibility = 'hidden';
|
||||||
|
document.getElementsByName('submit')[0].style.visibility = 'visible';
|
||||||
|
document.querySelector('td[width="60%"][valign="top"]').style.visibility = 'visible';
|
||||||
|
|
||||||
|
function hide(e) {if(e !== null && e !== undefined) e.style.display = 'none';}
|
||||||
|
|
||||||
|
hide(document.querySelector(".errorwrap"));
|
||||||
|
hide(document.querySelector('td[width="40%"][valign="top"]'));
|
||||||
|
var child = document.querySelector(".page").querySelector('div');
|
||||||
|
child.style.padding = null;
|
||||||
|
var ft = child.querySelectorAll('table');
|
||||||
|
var fd = child.parentNode.querySelectorAll('div > div');
|
||||||
|
var fh = document.querySelector('#border').querySelectorAll('td > table');
|
||||||
|
hide(ft[0]);
|
||||||
|
hide(ft[1]);
|
||||||
|
hide(fd[1]);
|
||||||
|
hide(fd[2]);
|
||||||
|
hide(child.querySelector('br'));
|
||||||
|
var error = document.querySelector(".page > div > .borderwrap");
|
||||||
|
if(error !== null) error.style.visibility = 'visible';
|
||||||
|
hide(fh[0]);
|
||||||
|
hide(fh[1]);
|
||||||
|
hide(document.querySelector("#gfooter"));
|
||||||
|
hide(document.querySelector(".copyright"));
|
||||||
|
document.querySelectorAll("td").forEach(function(e) {
|
||||||
|
e.style.color = "white";
|
||||||
|
});
|
||||||
|
var pc = document.querySelector(".postcolor");
|
||||||
|
if(pc !== null) pc.style.color = "#26353F";
|
||||||
|
})()
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
42
app/src/main/res/drawable/eh_ic_ehlogo_red_24dp.xml
Normal file
42
app/src/main/res/drawable/eh_ic_ehlogo_red_24dp.xml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="8"
|
||||||
|
android:viewportHeight="7">
|
||||||
|
|
||||||
|
<!-- Crafted by hand, command by command -->
|
||||||
|
<group
|
||||||
|
android:translateX="0.8"
|
||||||
|
android:translateY="0.7"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8">
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 0 0 h 3 v 1 h -3 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 0 1 v 2 h 1 v -2 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 0 3 h 2.25 v 1 h -2.25 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 0 4 v 2 h 1 v -2 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 0 6 h 3 v 1 h -3 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 3 3 h 1 v 1 h -1" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 4.75 0 h 1 v 7 h -1 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 5.75 3 h 1.25 v 1 h -1.25 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#660611"
|
||||||
|
android:pathData="M 7 0 h 1 v 7 h -1 Z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
67
app/src/main/res/layout/eh_activity_login.xml
Normal file
67
app/src/main/res/layout/eh_activity_login.xml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.design.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<include layout="@layout/toolbar"/>
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:id="@+id/linearLayout">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/btn_cancel"
|
||||||
|
android:layout_weight="1"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:text="Recheck"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/btn_recheck"
|
||||||
|
android:layout_weight="1"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless" />
|
||||||
|
</LinearLayout>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout>
|
@ -1,6 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Tachiyomi</string>
|
|
||||||
|
|
||||||
<string name="name">Nombre</string>
|
<string name="name">Nombre</string>
|
||||||
|
|
||||||
<!-- Activities and fragments labels (toolbar title) -->
|
<!-- Activities and fragments labels (toolbar title) -->
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Tachiyomi</string>
|
|
||||||
|
|
||||||
<string name="name">Nome</string>
|
<string name="name">Nome</string>
|
||||||
|
|
||||||
<!-- Activities and fragments labels (toolbar title) -->
|
<!-- Activities and fragments labels (toolbar title) -->
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Tachiyomi</string>
|
|
||||||
|
|
||||||
<string name="name">Nome</string>
|
<string name="name">Nome</string>
|
||||||
|
|
||||||
<!-- Activities and fragments labels (toolbar title) -->
|
<!-- Activities and fragments labels (toolbar title) -->
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Tachiyomi</string>
|
<string name="app_name">TachiyomiEH</string>
|
||||||
|
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
|
|
||||||
@ -375,4 +375,7 @@
|
|||||||
<string name="download_notifier_text_only_wifi">No wifi connection available</string>
|
<string name="download_notifier_text_only_wifi">No wifi connection available</string>
|
||||||
<string name="download_notifier_no_network">No network connection available</string>
|
<string name="download_notifier_no_network">No network connection available</string>
|
||||||
|
|
||||||
|
<!-- EH -->
|
||||||
|
<string name="label_login">Login</string>
|
||||||
|
<string name="pref_category_eh">E-Hentai</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
29
app/src/main/res/xml/eh_pref_eh.xml
Normal file
29
app/src/main/res/xml/eh_pref_eh.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
android:icon="@drawable/eh_ic_ehlogo_red_24dp"
|
||||||
|
android:key="eh_screen"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/pref_category_eh"
|
||||||
|
app:asp_tintEnabled="true">
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="Enable ExHentai"
|
||||||
|
android:summaryOff="Requires login"
|
||||||
|
android:key="enable_exhentai"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:dependency="enable_exhentai"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:key="secure_exh"
|
||||||
|
android:title="Secure ExHentai"
|
||||||
|
android:summary="Use the HTTPS version of ExHentai. Uncheck if ExHentai is not working." />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
@ -10,6 +10,9 @@ buildscript {
|
|||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
|
||||||
// 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
|
||||||
|
|
||||||
|
//Firebase (EH)
|
||||||
|
classpath 'com.google.gms:google-services:3.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,4 +15,6 @@
|
|||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
android.enableBuildCache=true
|
||||||
|
kotlin.incremental=true
|
Reference in New Issue
Block a user