Add Remanga source

This commit is contained in:
Koitharu
2021-01-23 19:44:11 +02:00
parent 0d0e3acd04
commit 85c424580a
7 changed files with 224 additions and 7 deletions

View File

@@ -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("")

View File

@@ -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()) }
}

View File

@@ -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)
}
}

View File

@@ -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
}*/
}

View File

@@ -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")}"
)
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}