Add Bakkin multisrc extension (#8115)

This commit is contained in:
ObserverOfTime 2021-07-14 23:23:51 +03:00 committed by GitHub
parent 039b63792d
commit 1ae23385d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 241 additions and 0 deletions

View File

@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.extension.en.bakkinselfhosted
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.bakkin.BakkinReaderX
class BakkinSelfHosted : BakkinReaderX("Bakkin Self-hosted", "", "en") {
override val baseUrl by lazy {
preferences.getString("baseUrl", "http://127.0.0.1/")!!
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen)
screen.addPreference(
EditTextPreference(screen.context).apply {
key = "baseUrl"
title = "Custom URL"
summary = "Connect to a self-hosted Bakkin Reader X server"
setDefaultValue("http://127.0.0.1/")
setOnPreferenceChangeListener { _, newValue ->
// Make sure the URL ends with one slash
val url = (newValue as String).trimEnd('/') + '/'
preferences.edit().putString("baseUrl", url).commit()
}
}
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,21 @@
package eu.kanade.tachiyomi.multisrc.bakkin
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
class BakkinGenerator : ThemeSourceGenerator {
override val themePkg = "bakkin"
override val themeClass = "BakkinReaderX"
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang("Bakkin", "https://bakkin.moe/reader/", "en"),
SingleLang("Bakkin Self-hosted", "", "en", className = "BakkinSelfHosted")
)
companion object {
@JvmStatic fun main(args: Array<String>) = BakkinGenerator().createAll()
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.multisrc.bakkin
@kotlinx.serialization.Serializable
internal data class Series(
val dir: String,
val name: String,
val author: String?,
val status: String?,
val thumb: String?,
val volumes: List<Volume>
) : Iterable<Chapter> {
override fun iterator() = volumes.flatMap {
// Prepend the volume name to the chapter name
it.map { ch -> ch.copy(name = "$it - $ch") }
}.iterator()
val cover get() = thumb ?: "static/nocover.png"
override fun toString() = name.ifEmpty { dir }
}
@kotlinx.serialization.Serializable
internal data class Volume(
val dir: String,
val name: String,
val chapters: List<Chapter>
) : Iterable<Chapter> by chapters {
override fun toString() = name.ifEmpty { dir }
}
@kotlinx.serialization.Serializable
internal data class Chapter(
val dir: String,
val name: String,
val pages: List<String>
) : Iterable<String> by pages {
override fun toString() = name.ifEmpty { dir }
}

View File

@ -0,0 +1,153 @@
package eu.kanade.tachiyomi.multisrc.bakkin
import android.app.Application
import android.os.Build
import androidx.preference.CheckBoxPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
abstract class BakkinReaderX(
override val name: String,
override val baseUrl: String,
override val lang: String
) : ConfigurableSource, HttpSource() {
override val supportsLatest = false
private val userAgent = "Mozilla/5.0 (" +
"Android ${Build.VERSION.RELEASE}; Mobile) " +
"Tachiyomi/${BuildConfig.VERSION_NAME}"
protected val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)!!
}
private val json by lazy { Injekt.get<Json>() }
private val mainUrl
get() = baseUrl + "/main.php" +
if (preferences.getBoolean("fullsize", false)) "?fullsize" else ""
private var seriesCache = emptyList<Series>()
private fun <R> observableSeries(block: (List<Series>) -> R) =
if (seriesCache.isNotEmpty()) Observable.just(block(seriesCache))
else client.newCall(GET(mainUrl, headers)).asObservableSuccess().map {
seriesCache = json.parseToJsonElement(it.body!!.string())
.jsonObject.values.map(json::decodeFromJsonElement)
block(seriesCache)
}
override fun headersBuilder() = Headers.Builder().add("User-Agent", userAgent)
// Request the actual manga URL for the webview
override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl#m=${manga.url}", headers)
override fun fetchPopularManga(page: Int): Observable<MangasPage> =
observableSeries { series ->
series.map {
SManga.create().apply {
url = it.dir
title = it.toString()
thumbnail_url = baseUrl + it.cover
}
}.let { MangasPage(it, false) }
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
observableSeries { series ->
series.first { it.dir == manga.url }.let {
SManga.create().apply {
url = it.dir
title = it.toString()
thumbnail_url = baseUrl + it.cover
initialized = true
author = it.author
status = when (it.status) {
"Ongoing" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
}
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
observableSeries { series ->
series.first { it.dir == manga.url }.mapIndexed { idx, chapter ->
SChapter.create().apply {
url = chapter.dir
name = chapter.toString()
chapter_number = idx.toFloat()
date_upload = -1L
}
}
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
observableSeries { series ->
series.flatten().first { it.dir == chapter.url }
.mapIndexed { idx, page -> Page(idx, "", "$baseUrl$page") }
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
screen.addPreference(
CheckBoxPreference(screen.context).apply {
key = "fullsize"
summary = "View fullsize images"
setDefaultValue(false)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}
)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
throw UnsupportedOperationException("Search is not supported by this source.")
override fun popularMangaRequest(page: Int): Request =
throw UnsupportedOperationException("Not used!")
override fun latestUpdatesRequest(page: Int): Request =
throw UnsupportedOperationException("Not used!")
override fun searchMangaParse(response: Response): MangasPage =
throw UnsupportedOperationException("Not used!")
override fun popularMangaParse(response: Response): MangasPage =
throw UnsupportedOperationException("Not used!")
override fun latestUpdatesParse(response: Response): MangasPage =
throw UnsupportedOperationException("Not used!")
override fun mangaDetailsParse(response: Response): SManga =
throw UnsupportedOperationException("Not used!")
override fun chapterListParse(response: Response): List<SChapter> =
throw UnsupportedOperationException("Not used!")
override fun pageListParse(response: Response): List<Page> =
throw UnsupportedOperationException("Not used!")
override fun imageUrlParse(response: Response): String =
throw UnsupportedOperationException("Not used!")
}