parent
41e64ac576
commit
fe47d20f67
8
src/all/yabai/build.gradle
Normal file
8
src/all/yabai/build.gradle
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'Yabai'
|
||||||
|
extClass = '.Yabai'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,69 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.yabai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
|
fun getFilters(): FilterList {
|
||||||
|
return FilterList(
|
||||||
|
SelectFilter("Category", categories.keys.toList()),
|
||||||
|
SelectFilter("Language", languages.keys.toList()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal open class SelectFilter(name: String, val vals: List<String>, state: Int = 0) :
|
||||||
|
Filter.Select<String>(name, vals.map { it }.toTypedArray(), state)
|
||||||
|
|
||||||
|
val categories = mapOf(
|
||||||
|
"All" to "",
|
||||||
|
"Doujinshi" to 1,
|
||||||
|
"Manga" to 2,
|
||||||
|
"Artist CG" to 3,
|
||||||
|
"Game CG" to 4,
|
||||||
|
"Western" to 5,
|
||||||
|
"Non-H" to 6,
|
||||||
|
"Image Set" to 7,
|
||||||
|
"Cosplay" to 8,
|
||||||
|
"Misc" to 9,
|
||||||
|
"Asian Porn" to 10,
|
||||||
|
"Private" to 11,
|
||||||
|
)
|
||||||
|
|
||||||
|
val languages = mapOf(
|
||||||
|
"All" to "",
|
||||||
|
"Japanese" to "jp",
|
||||||
|
"English" to "gb",
|
||||||
|
"Korean" to "kr",
|
||||||
|
"Russian" to "ru",
|
||||||
|
"Chinese" to "cn",
|
||||||
|
"French" to "fr",
|
||||||
|
"Italian" to "it",
|
||||||
|
"Spanish" to "es",
|
||||||
|
"Portuguese" to "pt",
|
||||||
|
"German" to "de",
|
||||||
|
"Thai" to "th",
|
||||||
|
"Arabic" to "sa",
|
||||||
|
"Turkish" to "tr",
|
||||||
|
"Hebrew" to "il",
|
||||||
|
"Tagalog" to "ph",
|
||||||
|
"Ukrainian" to "ua",
|
||||||
|
"Bulgarian" to "bg",
|
||||||
|
"Dutch" to "nl",
|
||||||
|
"Mongolian" to "mn",
|
||||||
|
"Vietnamese" to "vn",
|
||||||
|
"Macedonian" to "mk",
|
||||||
|
"Polish" to "pl",
|
||||||
|
"Hungarian" to "hu",
|
||||||
|
"Norwegian" to "no",
|
||||||
|
"Indonesian" to "id",
|
||||||
|
"Lithuanian" to "lt",
|
||||||
|
"Serbian" to "rs",
|
||||||
|
"Persian" to "ir",
|
||||||
|
"Croatian" to "hr",
|
||||||
|
"Czech" to "cz",
|
||||||
|
"Slovak" to "sk",
|
||||||
|
"Romanian" to "ro",
|
||||||
|
"Finnish" to "fi",
|
||||||
|
"Greek" to "gr",
|
||||||
|
"Swedish" to "se",
|
||||||
|
"Latin" to "va",
|
||||||
|
)
|
@ -0,0 +1,219 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.yabai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import keiyoushi.utils.parseAs
|
||||||
|
import keiyoushi.utils.toJsonString
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class Yabai : HttpSource() {
|
||||||
|
override val name = "Yabai"
|
||||||
|
|
||||||
|
override val baseUrl = "https://yabai.si"
|
||||||
|
|
||||||
|
override val lang = "all"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
|
.addInterceptor(::tokenInterceptor)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private var popularNextHash: String? = null
|
||||||
|
|
||||||
|
private var searchNextHash: String? = null
|
||||||
|
|
||||||
|
private var storedToken: String? = null
|
||||||
|
|
||||||
|
private fun tokenInterceptor(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
|
||||||
|
val modifiedRequest = request.newBuilder()
|
||||||
|
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.addHeader("X-Inertia", "true")
|
||||||
|
.addHeader("X-Inertia-Version", "b6320c13b244af5aafcd16668b9b38e4")
|
||||||
|
|
||||||
|
if (request.method == "POST") {
|
||||||
|
modifiedRequest.addHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
|
if (request.header("X-XSRF-TOKEN") == null) {
|
||||||
|
val token = getToken()
|
||||||
|
val response = chain.proceed(
|
||||||
|
modifiedRequest
|
||||||
|
.addHeader("X-XSRF-TOKEN", token)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.isSuccessful && response.code == 419) {
|
||||||
|
response.close()
|
||||||
|
storedToken = null
|
||||||
|
val newToken = getToken()
|
||||||
|
return chain.proceed(
|
||||||
|
modifiedRequest
|
||||||
|
.addHeader("X-XSRF-TOKEN", newToken)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain.proceed(modifiedRequest.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getToken(): String {
|
||||||
|
if (storedToken.isNullOrEmpty()) {
|
||||||
|
val request = GET(baseUrl, headers)
|
||||||
|
val response = client.newCall(request).execute()
|
||||||
|
|
||||||
|
var found = false
|
||||||
|
|
||||||
|
val headers = response.headers("Set-Cookie")
|
||||||
|
headers.forEach {
|
||||||
|
if (it.startsWith("XSRF-TOKEN=")) {
|
||||||
|
storedToken = it
|
||||||
|
.split(";")
|
||||||
|
.first()
|
||||||
|
.substringAfter("=")
|
||||||
|
.replace("%3D", "=")
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
throw IOException("Unable to find CSRF token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedToken!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = getFilters()
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (page == 1) {
|
||||||
|
searchNextHash = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val queryBody = QueryDto(
|
||||||
|
qry = query,
|
||||||
|
cursor = searchNextHash,
|
||||||
|
).apply {
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is SelectFilter -> {
|
||||||
|
when (filter.name) {
|
||||||
|
"Category" -> {
|
||||||
|
categories[filter.vals[filter.state]]?.let {
|
||||||
|
cat = it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"Language" -> {
|
||||||
|
languages[filter.vals[filter.state]]?.let {
|
||||||
|
lng = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return POST(
|
||||||
|
"$baseUrl/g",
|
||||||
|
headers,
|
||||||
|
queryBody.toJsonString().toRequestBody(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val data = response.parseAs<DataResponse<IndexProps>>()
|
||||||
|
|
||||||
|
val galleries = data.props.postList.data.map {
|
||||||
|
it.toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
searchNextHash = data.props.postList.meta.nextCursor
|
||||||
|
|
||||||
|
return MangasPage(galleries, searchNextHash != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
if (page == 1) {
|
||||||
|
popularNextHash = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val queryBody = QueryDto(
|
||||||
|
cursor = popularNextHash,
|
||||||
|
)
|
||||||
|
|
||||||
|
return POST(
|
||||||
|
"$baseUrl/g",
|
||||||
|
headers,
|
||||||
|
queryBody.toJsonString().toRequestBody(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val data = response.parseAs<DataResponse<IndexProps>>()
|
||||||
|
|
||||||
|
val galleries = data.props.postList.data.map {
|
||||||
|
it.toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
popularNextHash = data.props.postList.meta.nextCursor
|
||||||
|
|
||||||
|
return MangasPage(galleries, popularNextHash != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga) = GET(
|
||||||
|
"$baseUrl${manga.url}",
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) =
|
||||||
|
response.parseAs<DataResponse<DetailProps>>().props.post.data.toSManga()
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response) =
|
||||||
|
listOf(response.parseAs<DataResponse<DetailProps>>().props.post.data.toSChapter())
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter) = GET(
|
||||||
|
"$baseUrl${chapter.url}/read",
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response) =
|
||||||
|
response.parseAs<DataResponse<ReaderProps>>().props.pages.data.list.toPages()
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val createdAtFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.yabai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.extension.all.yabai.Yabai.Companion.createdAtFormat
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import okhttp3.internal.format
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class QueryDto(
|
||||||
|
var cat: String = "",
|
||||||
|
var lng: String = "",
|
||||||
|
private val qry: String = "",
|
||||||
|
private val tag: String = "[]",
|
||||||
|
private val cursor: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DataResponse<T>(
|
||||||
|
val props: T,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class IndexProps(
|
||||||
|
@SerialName("post_list")
|
||||||
|
val postList: PostList,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PostList(
|
||||||
|
val data: List<GalleryItem>,
|
||||||
|
val meta: Meta,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class GalleryItem(
|
||||||
|
private val slug: String,
|
||||||
|
private val name: String,
|
||||||
|
private val cover: String,
|
||||||
|
) {
|
||||||
|
fun toSManga() = SManga.create().apply {
|
||||||
|
title = name
|
||||||
|
url = format("/g/$slug")
|
||||||
|
thumbnail_url = cover
|
||||||
|
status = SManga.COMPLETED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Meta(
|
||||||
|
@SerialName("next_cursor")
|
||||||
|
val nextCursor: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DetailProps(
|
||||||
|
val post: Post,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Post(
|
||||||
|
val data: Gallery,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Gallery(
|
||||||
|
private val slug: String,
|
||||||
|
private val name: String,
|
||||||
|
private val cover: String,
|
||||||
|
private val tags: Map<String, List<Tag>>?,
|
||||||
|
private val date: PostDate,
|
||||||
|
) {
|
||||||
|
fun toSManga() = SManga.create().apply {
|
||||||
|
title = name
|
||||||
|
url = format("/g/$slug")
|
||||||
|
thumbnail_url = cover
|
||||||
|
author = tags
|
||||||
|
?.filterKeys { it == "Group" }
|
||||||
|
?.flatMap { it.value }
|
||||||
|
?.joinToString { it.name }
|
||||||
|
artist = tags
|
||||||
|
?.filterKeys { it == "Artist" }
|
||||||
|
?.flatMap { it.value }
|
||||||
|
?.joinToString { it.name }
|
||||||
|
genre = tags
|
||||||
|
?.filterKeys { it != "Group" && it != "Artist" }
|
||||||
|
?.flatMap { it.value }
|
||||||
|
?.joinToString { it.fullName ?: it.name }
|
||||||
|
status = SManga.COMPLETED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toSChapter() = SChapter.create().apply {
|
||||||
|
name = "Chapter"
|
||||||
|
url = format("/g/$slug")
|
||||||
|
date_upload = try {
|
||||||
|
date.toDate()!!.time
|
||||||
|
} catch (e: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Tag(
|
||||||
|
val name: String,
|
||||||
|
@SerialName("full_name")
|
||||||
|
val fullName: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PostDate(
|
||||||
|
private val default: String,
|
||||||
|
) {
|
||||||
|
fun toDate(): Date? = createdAtFormat.parse(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ReaderProps(
|
||||||
|
val pages: Pages,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Pages(
|
||||||
|
val data: PagesData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PagesData(
|
||||||
|
val list: PagesList,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PagesList(
|
||||||
|
private val root: String,
|
||||||
|
private val code: Int,
|
||||||
|
private val head: List<String>,
|
||||||
|
private val hash: List<String>,
|
||||||
|
private val rand: List<String>,
|
||||||
|
private val type: List<String>,
|
||||||
|
) {
|
||||||
|
fun toPages() = head
|
||||||
|
.mapIndexed { index, pageNumber -> Triple(pageNumber, index, index) }
|
||||||
|
.sortedBy { it.first.toInt() }
|
||||||
|
.mapIndexed { sortedIndex, (pageNumber, originalIndex, _) ->
|
||||||
|
Page(
|
||||||
|
sortedIndex,
|
||||||
|
imageUrl = format(
|
||||||
|
"$root/$code/${
|
||||||
|
pageNumber.padStart(4, '0')
|
||||||
|
}-${hash[originalIndex]}-${rand[originalIndex]}.${type[originalIndex]}",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user