Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
hamedhani1998 Jul 9, 2026
baf9ebc
Update build.gradle.kts
hamedhani1998 Jul 10, 2026
79fc155
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
18598ef
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
84ffac9
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
e462fba
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
9026812
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
3e05ec5
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
a3e4d26
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
7c4a06e
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
3d8c446
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
088c2a3
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
5ef3b3e
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
0509323
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
b2401da
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
11758aa
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
0576974
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
e74a312
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
cfaa2bd
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
5f24b0c
Update MangaCloud.kt
hamedhani1998 Jul 10, 2026
6dd530d
Merge branch 'main' into feat-add-mangacloud
hamedhani1998 Jul 11, 2026
b0c40ab
fix(ar/mangacloud): add missing ContentWarning import
Jul 11, 2026
6410fc8
fix(ar/mangacloud): address reviewer feedback - use kotlinx.serializa…
Jul 15, 2026
b719b6d
Merge branch 'main' into feat-add-mangacloud
hamedhani1998 Jul 15, 2026
5bea772
fix(ar/mangacloud): implement KeiSource, set libVersion to 1.6
Jul 16, 2026
e43d8d1
Merge branch 'main' into feat-add-mangacloud
hamedhani1998 Jul 16, 2026
edb7942
fix(ar/mangacloud): use parseAs, remove defaults, add async parallel …
Jul 17, 2026
4bc2367
Merge branch 'main' into feat-add-mangacloud
hamedhani1998 Jul 17, 2026
5a03251
Merge branch 'main' into feat-add-mangacloud
hamedhani1998 Jul 18, 2026
72f0125
Merge branch 'main' into feat-add-mangacloud
AwkwardPeak7 Jul 19, 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.6"

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,126 @@
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.parseAs
import kotlinx.serialization.Serializable
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

@Serializable
data class FirestoreListResponse(
val documents: List<FirestoreDocument>? = null,
val nextPageToken: String? = null,
)

@Serializable
data class FirestoreDocument(
val name: String? = null,
val fields: JsonObject? = null,
)

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 result = response.parseAs<FirestoreListResponse>()
val mangas = result.documents?.mapNotNull { doc ->
val fields = doc.fields ?: return@mapNotNull null
val mangaId = doc.name?.substringAfterLast("/") ?: return@mapNotNull null
MangaData(
smanga = SManga.create().apply {
url = 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
"معلق" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
},
altTitles = fields.stringArray("alternativeTitles"),
genres = fields.stringArray("genres"),
statusString = when (fields.string("status")) {
"مستمر" -> "ongoing"
"مكتمل" -> "completed"
else -> ""
},
)
} ?: emptyList()
return MangaListResult(mangas, result.nextPageToken)
}

fun parseDetails(response: Response): SManga {
val doc = response.parseAs<FirestoreDocument>()
val fields = doc.fields!!
val mangaId = doc.name!!.substringAfterLast("/")
check(mangaId.isNotEmpty()) { "mangaId must not be empty" }
return SManga.create().apply {
url = 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 result = response.parseAs<FirestoreListResponse>()
return result.documents?.mapNotNull { doc ->
val docName = doc.name ?: 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 } ?: emptyList()
}

fun parsePages(response: Response): List<Page> {
val doc = response.parseAs<FirestoreDocument>()
val fields = doc.fields ?: return emptyList()
val pagesArray = fields["pages"]?.jsonObject?.get("arrayValue")?.jsonObject

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val pagesArray = fields["pages"]?.jsonObject?.get("arrayValue")?.jsonObject
val pagesArray = fields["pages"]["arrayValue"].obj

use helpers from keiyoushi.utils

?.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()
Loading