Add Remanga source
This commit is contained in:
@@ -27,8 +27,9 @@ enum class MangaSource(
|
||||
MANGATOWN("MangaTown", "en", MangaTownRepository::class.java),
|
||||
MANGALIB("MangaLib", "ru", MangaLibRepository::class.java),
|
||||
NUDEMOON("Nude-Moon", "ru", NudeMoonRepository::class.java),
|
||||
MANGAREAD("MangaRead", "en", MangareadRepository::class.java);
|
||||
// HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java)
|
||||
MANGAREAD("MangaRead", "en", MangareadRepository::class.java),
|
||||
REMANGA("Remanga", "ru", RemangaRepository::class.java),
|
||||
HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java);
|
||||
|
||||
@get:Throws(NoBeanDefFoundException::class)
|
||||
@Deprecated("")
|
||||
|
||||
@@ -19,7 +19,9 @@ val parserModule
|
||||
factory<MangaRepository>(named(MangaSource.HENCHAN)) { HenChanRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) }
|
||||
single<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.REMANGA)) { RemangaRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.HENTAILIB)) { HentaiLibRepository(get()) }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.net.Uri
|
||||
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.core.model.MangaPage
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
@@ -23,4 +24,17 @@ abstract class RemoteMangaRepository(
|
||||
override suspend fun getTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
abstract fun onCreatePreferences(): Set<String>
|
||||
|
||||
protected fun generateUid(url: String): Long {
|
||||
val uri = Uri.parse(url)
|
||||
val path = uri.path ?: error("Cannot generate uid: bad uri \"$url\"")
|
||||
val x = source.name.hashCode()
|
||||
val y = path.hashCode()
|
||||
return (x.toLong() shl 32) or (y.toLong() and 0xffffffffL)
|
||||
}
|
||||
|
||||
protected fun generateUid(id: Int): Long {
|
||||
val x = source.name.hashCode()
|
||||
return (x.toLong() shl 32) or (id.toLong() and 0xffffffffL)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.koitharu.kotatsu.core.parser.site
|
||||
|
||||
/*
|
||||
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
|
||||
class HentaiLibRepository(loaderContext: MangaLoaderContext) : MangaLibRepository(loaderContext) {
|
||||
|
||||
protected override val defaultDomain = "hentailib.me"
|
||||
override val defaultDomain = "hentailib.me"
|
||||
|
||||
override val source = MangaSource.HENTAILIB
|
||||
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
.parseJsonArray()
|
||||
return json.map { jo ->
|
||||
val url = "https://$domain/${jo.getString("slug")}"
|
||||
val covers = jo.getJSONObject("covers")
|
||||
Manga(
|
||||
id = url.longHashCode(),
|
||||
url = url,
|
||||
@@ -227,7 +228,8 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
.toFloatOrNull()?.div(5f) ?: Manga.NO_RATING,
|
||||
state = null,
|
||||
source = source,
|
||||
coverUrl = "https://$domain${jo.getJSONObject("covers").getString("thumbnail")}"
|
||||
coverUrl = "https://$domain${covers.getString("thumbnail")}",
|
||||
largeCoverUrl = "https://$domain${covers.getString("default")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.koitharu.kotatsu.core.parser.site
|
||||
|
||||
import androidx.collection.arraySetOf
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.core.exceptions.ParseException
|
||||
import org.koitharu.kotatsu.core.model.*
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.SourceSettings
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) {
|
||||
|
||||
override val source = MangaSource.REMANGA
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.RATING,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.NEWEST
|
||||
)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
sortOrder: SortOrder?,
|
||||
tag: MangaTag?
|
||||
): List<Manga> {
|
||||
val domain = conf.getDomain(DEFAULT_DOMAIN)
|
||||
val urlBuilder = StringBuilder()
|
||||
.append("https://api.")
|
||||
.append(domain)
|
||||
if (query != null) {
|
||||
urlBuilder.append("/api/search/?query=")
|
||||
.append(query.urlEncoded())
|
||||
} else {
|
||||
urlBuilder.append("/api/search/catalog/?page=")
|
||||
.append("&ordering=")
|
||||
.append(getSortKey(sortOrder))
|
||||
if (tag != null) {
|
||||
urlBuilder.append("&genres=" + tag.key)
|
||||
}
|
||||
}
|
||||
urlBuilder.append((offset / PAGE_SIZE) + 1)
|
||||
.append("&count=")
|
||||
.append(PAGE_SIZE)
|
||||
val content = loaderContext.httpGet(urlBuilder.toString()).parseJson()
|
||||
.getJSONArray("content")
|
||||
return content.map { jo ->
|
||||
val url = "https://$domain/manga/${jo.getString("dir")}"
|
||||
val img = jo.getJSONObject("img")
|
||||
Manga(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
title = jo.getString("rus_name"),
|
||||
altTitle = jo.getString("en_name"),
|
||||
rating = jo.getString("avg_rating").toFloatOrNull()?.div(10f) ?: Manga.NO_RATING,
|
||||
coverUrl = "https://api.$domain${img.getString("mid")}",
|
||||
largeCoverUrl = "https://api.$domain${img.getString("high")}",
|
||||
author = null,
|
||||
tags = jo.optJSONArray("genres")?.mapToSet { g ->
|
||||
MangaTag(
|
||||
title = g.getString("name"),
|
||||
key = g.getInt("id").toString(),
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
}.orEmpty(),
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val domain = conf.getDomain(DEFAULT_DOMAIN)
|
||||
val slug = manga.url.toHttpUrl().pathSegments.last()
|
||||
val data = loaderContext.httpGet(
|
||||
url = "https://api.$domain/api/titles/$slug/"
|
||||
).parseJson()
|
||||
val content = try {
|
||||
data.getJSONObject("content")
|
||||
} catch (e: JSONException) {
|
||||
throw ParseException(data.optString("msg"), e)
|
||||
}
|
||||
val branchId = content.getJSONArray("branches").optJSONObject(0)
|
||||
?.getLong("id") ?: throw ParseException("No branches found")
|
||||
val chapters = loaderContext.httpGet(
|
||||
url = "https://api.$domain/api/titles/chapters/?branch_id=$branchId"
|
||||
).parseJson().getJSONArray("content")
|
||||
return manga.copy(
|
||||
description = content.getString("description"),
|
||||
state = when (content.optJSONObject("status")?.getInt("id")) {
|
||||
STATUS_ONGOING -> MangaState.ONGOING
|
||||
STATUS_FINISHED -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
tags = content.getJSONArray("genres").mapToSet { g ->
|
||||
MangaTag(
|
||||
title = g.getString("name"),
|
||||
key = g.getInt("id").toString(),
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
},
|
||||
chapters = chapters.mapIndexed { i, jo ->
|
||||
val id = jo.getLong("id")
|
||||
val name = jo.getString("name")
|
||||
MangaChapter(
|
||||
id = generateUid(id.toInt()),
|
||||
url = "https://api.$domain/api/titles/chapters/$id/",
|
||||
number = chapters.length() - i,
|
||||
name = buildString {
|
||||
append("Глава ")
|
||||
append(jo.getString("chapter"))
|
||||
if (name.isNotEmpty()) {
|
||||
append(" - ")
|
||||
append(name)
|
||||
}
|
||||
},
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
}.asReversed()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val referer = "https://${conf.getDomain(DEFAULT_DOMAIN)}/"
|
||||
val content = loaderContext.httpGet(chapter.url).parseJson()
|
||||
.getJSONObject("content").getJSONArray("pages")
|
||||
val pages = ArrayList<MangaPage>(content.length())
|
||||
for (i in 0 until content.length()) {
|
||||
when (val item = content.get(i)) {
|
||||
is JSONObject -> pages += parsePage(item, referer)
|
||||
is JSONArray -> item.mapTo(pages) { parsePage(it, referer) }
|
||||
else -> throw ParseException("Unknown json item $item")
|
||||
}
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override suspend fun getTags(): Set<MangaTag> {
|
||||
val domain = conf.getDomain(DEFAULT_DOMAIN)
|
||||
val content = loaderContext.httpGet("https://api.$domain/api/forms/titles/?get=genres")
|
||||
.parseJson().getJSONObject("content").getJSONArray("genres")
|
||||
return content.mapToSet { jo ->
|
||||
MangaTag(
|
||||
title = jo.getString("name"),
|
||||
key = jo.getInt("id").toString(),
|
||||
source = source
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences() = arraySetOf(SourceSettings.KEY_DOMAIN)
|
||||
|
||||
private fun getSortKey(order: SortOrder?) = when (order) {
|
||||
SortOrder.UPDATED -> "-chapter_date"
|
||||
SortOrder.POPULARITY -> "-rating"
|
||||
SortOrder.RATING -> "-votes"
|
||||
SortOrder.NEWEST -> "-id"
|
||||
else -> "-rating"
|
||||
}
|
||||
|
||||
private fun parsePage(jo: JSONObject, referer: String) = MangaPage(
|
||||
id = generateUid(jo.getLong("id").toInt()),
|
||||
url = jo.getString("link"),
|
||||
referer = referer,
|
||||
source = source
|
||||
)
|
||||
|
||||
private companion object {
|
||||
|
||||
const val PAGE_SIZE = 30
|
||||
const val DEFAULT_DOMAIN = "remanga.org"
|
||||
|
||||
const val STATUS_ONGOING = 1
|
||||
const val STATUS_FINISHED = 0
|
||||
}
|
||||
}
|
||||
@@ -111,4 +111,17 @@ fun String.md5(): String {
|
||||
return BigInteger(1, md.digest(toByteArray()))
|
||||
.toString(16)
|
||||
.padStart(32, '0')
|
||||
}
|
||||
|
||||
fun String.substringBetween(from: String, to: String, fallbackValue: String): String {
|
||||
val fromIndex = indexOf(from)
|
||||
if (fromIndex == -1) {
|
||||
return fallbackValue
|
||||
}
|
||||
val toIndex = lastIndexOf(to)
|
||||
return if (toIndex == -1) {
|
||||
fallbackValue
|
||||
} else {
|
||||
substring(fromIndex + from.length, toIndex)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user