Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
73cab62
feat(ar): add MangaCloud source
Jul 9, 2026
fe6835b
Merge branch 'main' into feat-add-mangacloud
hanihamed1998 Jul 9, 2026
baf9ebc
Update build.gradle.kts
hanihamed1998 Jul 10, 2026
79fc155
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
18598ef
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
84ffac9
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
e462fba
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
9026812
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
3e05ec5
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
a3e4d26
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
7c4a06e
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
3d8c446
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
088c2a3
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
5ef3b3e
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
0509323
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
b2401da
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
11758aa
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
0576974
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
e74a312
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
cfaa2bd
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
5f24b0c
Update MangaCloud.kt
hanihamed1998 Jul 10, 2026
6dd530d
Merge branch 'main' into feat-add-mangacloud
hanihamed1998 Jul 11, 2026
b0c40ab
fix(ar/mangacloud): add missing ContentWarning import
Jul 11, 2026
7150d76
Merge branch 'main' into feat-add-mangacloud
hanihamed1998 Jul 12, 2026
c9260b1
Merge branch 'main' into feat-add-mangacloud
hanihamed1998 Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/ar/mangacloud/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import io.github.keiyoushi.gradle.api.ContentWarning

plugins {
alias(kei.plugins.extension)
}

keiyoushi {
name = "MangaCloud"
versionCode = 1
contentWarning = ContentWarning.SAFE
libVersion = "1.4"

source {
lang = "ar"
baseUrl = "https://mangacloud.online"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/ar/mangacloud/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package eu.kanade.tachiyomi.extension.ar.mangacloud

import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import keiyoushi.utils.jsonInstance
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Response

data class MangaData(
val smanga: SManga,
val altTitles: List<String> = emptyList(),
val genres: List<String> = emptyList(),
val statusString: String = "",
)

data class MangaListResult(
val mangas: List<MangaData>,
val nextPageToken: String?,
)

object FirestoreParser {

fun parseList(response: Response): MangaListResult {
val json = jsonInstance.parseToJsonElement(response.body.string()).jsonObject
val documents = json["documents"]?.jsonArray ?: return MangaListResult(emptyList(), null)
val mangas = documents.mapNotNull { doc ->
val fields = doc.jsonObject["fields"]?.jsonObject ?: return@mapNotNull null
val mangaId = doc.jsonObject["name"]?.jsonPrimitive?.contentOrNull?.substringAfterLast("/") ?: return@mapNotNull null
val altTitles = fields.stringArray("alternativeTitles")
val genres = fields.stringArray("genres")
val statusStr = fields.string("status")
MangaData(
smanga = SManga.create().apply {
url = "/manga/$mangaId"
title = fields.string("title")
thumbnail_url = fields.string("coverImage")
description = fields.string("description")
author = fields.string("author")
artist = fields.string("artist")
genre = genres.joinToString(", ")
status = when (statusStr) {
"مستمر" -> SManga.ONGOING
"مكتمل" -> SManga.COMPLETED
"متروك", "متروكة" -> SManga.ON_HIATUS
"معلق" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
},
altTitles = altTitles,
genres = genres,
statusString = when (statusStr) {
"مستمر" -> "ongoing"
"مكتمل" -> "completed"
else -> ""
},
)
}
val nextPageToken = json["nextPageToken"]?.jsonPrimitive?.contentOrNull
return MangaListResult(mangas, nextPageToken)
}

fun parseDetails(response: Response): SManga {
val json = jsonInstance.parseToJsonElement(response.body.string()).jsonObject
val fields = json["fields"]?.jsonObject ?: return SManga.create()
val mangaId = json["name"]?.jsonPrimitive?.contentOrNull?.substringAfterLast("/") ?: ""
return SManga.create().apply {
url = "/manga/$mangaId"
title = fields.string("title")
thumbnail_url = fields.string("coverImage")
description = fields.string("description")
author = fields.string("author")
artist = fields.string("artist")
genre = fields.stringArray("genres").joinToString(", ")
status = when (fields.string("status")) {
"مستمر" -> SManga.ONGOING
"مكتمل" -> SManga.COMPLETED
"متروك" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
}

fun parseChapters(response: Response): List<SChapter> {
val json = jsonInstance.parseToJsonElement(response.body.string()).jsonObject
val documents = json["documents"]?.jsonArray ?: return emptyList()
return documents.mapNotNull { doc ->
val fields = doc.jsonObject["fields"]?.jsonObject ?: return@mapNotNull null
val docName = doc.jsonObject["name"]?.jsonPrimitive?.contentOrNull ?: return@mapNotNull null
val chapterNum = docName.substringAfterLast("/")
val num = chapterNum.toIntOrNull() ?: 0
SChapter.create().apply {
url = docName.substringAfterLast("cloudmangas/")
name = "الفصل $num"
chapter_number = num.toFloat()
}
}.sortedByDescending { it.chapter_number }
}

fun parsePages(response: Response): List<Page> {
val json = jsonInstance.parseToJsonElement(response.body.string()).jsonObject
val fields = json["fields"]?.jsonObject ?: return emptyList()
val pagesArray = fields["pages"]?.jsonObject?.get("arrayValue")?.jsonObject?.get("values")?.jsonArray ?: return emptyList()
return pagesArray.mapIndexed { index, value ->
Page(index, imageUrl = value.jsonObject["stringValue"]?.jsonPrimitive?.contentOrNull ?: "")
}
}
}

private fun JsonObject.string(key: String): String = this[key]?.jsonObject?.get("stringValue")?.jsonPrimitive?.contentOrNull ?: ""

private fun JsonObject.stringArray(key: String): List<String> = this[key]?.jsonObject?.get("arrayValue")?.jsonObject?.get("values")?.jsonArray
?.map { it.jsonObject["stringValue"]?.jsonPrimitive?.contentOrNull ?: "" }
?: emptyList()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.ar.mangacloud

import eu.kanade.tachiyomi.source.model.Filter

internal class SortFilter(name: String, private val pairs: Array<Pair<String, String>>) : Filter.Select<String>(name, pairs.map { it.first }.toTypedArray()) {
val selected get() = pairs[state].second
}

internal class GenreFilter(name: String, private val pairs: Array<Pair<String, String>>) : Filter.Select<String>(name, pairs.map { it.first }.toTypedArray()) {
val selected get() = pairs[state].second
}

internal class StatusFilter(name: String, private val pairs: Array<Pair<String, String>>) : Filter.Select<String>(name, pairs.map { it.first }.toTypedArray()) {
val selected get() = pairs[state].second
}
Loading