diff --git a/src/en/rizzcomic/build.gradle b/src/en/rizzcomic/build.gradle index 275a59e32..e96954b72 100644 --- a/src/en/rizzcomic/build.gradle +++ b/src/en/rizzcomic/build.gradle @@ -1,9 +1,9 @@ ext { - extName = 'Rizz Comic' - extClass = '.RizzComic' + extName = 'Realm Oasis' + extClass = '.RealmOasis' themePkg = 'mangathemesia' - baseUrl = 'https://rizzfables.com' - overrideVersionCode = 5 + baseUrl = 'https://realmoasis.com' + overrideVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/en/rizzcomic/res/mipmap-hdpi/ic_launcher.png b/src/en/rizzcomic/res/mipmap-hdpi/ic_launcher.png index 3240c622c..4a8bfb1bc 100644 Binary files a/src/en/rizzcomic/res/mipmap-hdpi/ic_launcher.png and b/src/en/rizzcomic/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/rizzcomic/res/mipmap-mdpi/ic_launcher.png b/src/en/rizzcomic/res/mipmap-mdpi/ic_launcher.png index 8be812241..d10ae0ba1 100644 Binary files a/src/en/rizzcomic/res/mipmap-mdpi/ic_launcher.png and b/src/en/rizzcomic/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/rizzcomic/res/mipmap-xhdpi/ic_launcher.png b/src/en/rizzcomic/res/mipmap-xhdpi/ic_launcher.png index eb6609980..43ac8febb 100644 Binary files a/src/en/rizzcomic/res/mipmap-xhdpi/ic_launcher.png and b/src/en/rizzcomic/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/rizzcomic/res/mipmap-xxhdpi/ic_launcher.png b/src/en/rizzcomic/res/mipmap-xxhdpi/ic_launcher.png index 6a2e222eb..dafeecdf3 100644 Binary files a/src/en/rizzcomic/res/mipmap-xxhdpi/ic_launcher.png and b/src/en/rizzcomic/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/rizzcomic/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/rizzcomic/res/mipmap-xxxhdpi/ic_launcher.png index c99d7c610..280ee60d9 100644 Binary files a/src/en/rizzcomic/res/mipmap-xxxhdpi/ic_launcher.png and b/src/en/rizzcomic/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RealmOasis.kt similarity index 59% rename from src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt rename to src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RealmOasis.kt index 7feb5c9fb..8fe8ab0e2 100644 --- a/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt +++ b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RealmOasis.kt @@ -1,36 +1,42 @@ package eu.kanade.tachiyomi.extension.en.rizzcomic -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt +import android.app.Application +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import eu.kanade.tachiyomi.source.model.Filter 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.util.asJsoup import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import okhttp3.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response +import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale -class RizzComic : MangaThemesiaAlt( - "Rizz Comic", - "https://rizzfables.com", +class RealmOasis : MangaThemesia( + "Realm Oasis", + "https://realmoasis.com", "en", - mangaUrlDirectory = "/series", + mangaUrlDirectory = "/comics", dateFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH), ) { override val client = super.client.newBuilder() - .rateLimit(1, 3) + .rateLimitHost(baseUrl.toHttpUrl(), 1, 3) .addInterceptor { chain -> val request = chain.request() val isApiRequest = request.header("X-API-Request") != null @@ -52,15 +58,22 @@ class RizzComic : MangaThemesiaAlt( .build() } - override val versionId = 2 + override val versionId = 3 - override val slugRegex = Regex("""^(r\d+-)""") + private val preferences = + Injekt.get().getSharedPreferences("source_$id", 0x0000) - // don't allow disabling random part setting - override fun setupPreferenceScreen(screen: PreferenceScreen) = Unit - - override val listUrl = mangaUrlDirectory - override val listSelector = "div.bsx a" + private val mangaPath by lazy { + client.newCall(GET(baseUrl, headers)) + .execute().asJsoup() + .selectFirst(".listupd a")!! + .absUrl("href") + .toHttpUrl() + .pathSegments[0] + .also { + mangaPathCache = it + } + } override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR) override fun popularMangaParse(response: Response) = searchMangaParse(response) @@ -98,6 +111,7 @@ class RizzComic : MangaThemesiaAlt( @Serializable class Comic( + val id: String, val title: String, @SerialName("image_url") val cover: String? = null, @SerialName("long_description") val synopsis: String? = null, @@ -108,16 +122,7 @@ class RizzComic : MangaThemesiaAlt( val serialization: String? = null, @SerialName("genre_id") val genres: String? = null, ) { - val slug get() = title.trim().lowercase() - .replace(slugRegex, "-") - .replace("-s-", "s-") - .replace("-ll-", "ll-") - val genreIds get() = genres?.split(",")?.map(String::trim) - - companion object { - private val slugRegex = Regex("""[^a-z0-9]+""") - } } override fun searchMangaParse(response: Response): MangasPage { @@ -125,13 +130,13 @@ class RizzComic : MangaThemesiaAlt( val entries = result.map { comic -> SManga.create().apply { - url = "$mangaUrlDirectory/${comic.slug}/" + url = comic.id title = comic.title description = comic.synopsis author = listOfNotNull(comic.author, comic.serialization).joinToString() artist = comic.artist status = comic.status.parseStatus() - thumbnail_url = comic.cover?.let { "$baseUrl/assets/images/$it" } + thumbnail_url = comic.cover?.let { "https://x.0ms.dev/q70/$baseUrl/assets/images/$it" } genre = buildList { add(comic.type?.capitalize()) comic.genreIds?.onEach { gId -> @@ -145,19 +150,82 @@ class RizzComic : MangaThemesiaAlt( return MangasPage(entries, false) } + override fun mangaDetailsRequest(manga: SManga): Request { + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegment(mangaPath) + .addPathSegment( + UrlUtils.generateSeriesLink(manga.url.toInt()), + ).build() + + return GET(url, headers) + } + + override fun getMangaUrl(manga: SManga): String { + return buildString { + append(baseUrl) + append("/") + append(mangaPathCache) + append("/") + append( + UrlUtils.generateSeriesLink(manga.url.toInt()), + ) + } + } + override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) .asObservableSuccess() .map { mangaDetailsParse(it).apply { description = manga.description } } } + override fun chapterListRequest(manga: SManga): Request { + return mangaDetailsRequest(manga) + } + + override fun chapterFromElement(element: Element): SChapter { + return super.chapterFromElement(element).apply { + val chapUrl = element.selectFirst("a")!!.absUrl("href") + + val (seriesId, chapterId) = UrlUtils.extractChapterIds(chapUrl) + ?: throw Exception("unable find chapter id from url") + + url = "$seriesId/$chapterId" + } + } + + override fun pageListRequest(chapter: SChapter): Request { + val (seriesId, chapterId) = chapter.url.split("/").take(2).map(String::toInt) + + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegment(mangaPath) + .addPathSegment( + UrlUtils.generateChapterLink(seriesId, chapterId), + ).build() + + return GET(url, headers) + } + + override fun getChapterUrl(chapter: SChapter): String { + val (seriesId, chapterId) = chapter.url.split("/").take(2).map(String::toInt) + + return buildString { + append(baseUrl) + append("/") + append(mangaPathCache) + append("/") + append( + UrlUtils.generateChapterLink(seriesId, chapterId), + ) + } + } + override fun imageRequest(page: Page): Request { val newHeaders = headersBuilder() .set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*") .set("Referer", "$baseUrl/") .build() - return GET(page.imageUrl!!, newHeaders) + return GET("https://x.0ms.dev/q70/" + page.imageUrl!!, newHeaders) } private inline fun Response.parseAs(): T = @@ -175,4 +243,19 @@ class RizzComic : MangaThemesiaAlt( val charPool = ('a'..'z') + ('A'..'Z') return List(length) { charPool.random() }.joinToString("") } + + private var mangaPathCache: String = "" + get() { + if (field.isBlank()) { + field = preferences.getString(mangaPathPrefCache, "comics")!! + } + + return field + } + set(newVal) { + preferences.edit().putString(mangaPathPrefCache, newVal).apply() + field = newVal + } } + +private const val mangaPathPrefCache = "manga_path" diff --git a/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/UrlUtils.kt b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/UrlUtils.kt new file mode 100644 index 000000000..9a7288e61 --- /dev/null +++ b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/UrlUtils.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.extension.en.rizzcomic + +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +object UrlUtils { + private fun randomString(length: Int): String { + val charPool = ('a'..'z') + ('0'..'9') + return List(length) { charPool.random() }.joinToString("") + } + + fun generateSeriesLink(seriesId: Int): String { + return buildString { + append(randomString(4)) + append("s") + append(randomString(4)) + append(seriesId.toString().padStart(5, '0')) + append(randomString(12)) + } + } + + fun generateChapterLink(seriesId: Int, chapterId: Int): String { + return buildString { + append(randomString(4)) + append("c") + append(randomString(4)) + append(seriesId.toString().padStart(5, '0')) + append(randomString(4)) + append(chapterId.toString().padStart(6, '0')) + append(randomString(4)) + } + } + + // private val seriesRegex = """^[a-z0-9]{4}s[a-z0-9]{4}([0-9]{5})[a-z0-9]+$""".toRegex() + // + // fun extractSeriesId(url: String): Int? { + // val path = url.toHttpUrlOrNull()?.pathSegments?.lastOrNull() + // ?: return null + // return seriesRegex.find(path)?.groupValues?.get(1)?.toIntOrNull() + // } + + private val ChaptersRegex = """^[a-z0-9]{4}c[a-z0-9]{4}([0-9]{5})[a-z0-9]{4}([0-9]{6})[a-z0-9]+$""".toRegex() + + fun extractChapterIds(url: String): Pair? { + val path = url.toHttpUrlOrNull()?.pathSegments?.lastOrNull() + ?: return null + return ChaptersRegex.find(path)?.let { matchResult -> + val seriesId = matchResult.groupValues[1].toIntOrNull() + val chapterId = matchResult.groupValues[2].toIntOrNull() + if (seriesId != null && chapterId != null) { + seriesId to chapterId + } else { + null + } + } + } +}