--> Add new features to Gmanga (#13494)

* --> Add new features
* now you can switch in the extension setting between :
   -show new Mangas
    or
   -show new chapter
* link manga status to tachiyomi native manga status
* add additional information to description
  - manga start date
  - manga end Date
  - manga Alternative information

* --> moved formattedDatePattern in a constant in the class

* --> change formattedDatePattern with SimpleDateFormat in a constant in the class so the extension can work with  API level < 26

* --> change formattedDatePattern with SimpleDateFormat in a constant in the class so the extension can work with  API level < 26
--> add some information "that the story it not add yet" when there is no description provided by the Scanlator team

* -->  increment extVersionCode by 1 instead of 3
This commit is contained in:
KENIS Mohamed Ali 2022-09-19 14:38:51 +01:00 committed by GitHub
parent dd7266ca17
commit 2d31ba573a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 163 additions and 31 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'GMANGA'
pkgNameSuffix = 'ar.gmanga'
extClass = '.Gmanga'
extVersionCode = 11
extVersionCode = 12
}
apply from: "$rootDir/common.gradle"

View File

@ -4,6 +4,9 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_CHAPTER_LISTING
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_CHAPTER_LISTING_SHOW_ALL
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_CHAPTER_LISTING_SHOW_POPULAR
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_LASTETS_LISTING
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_LASTETS_LISTING_SHOW_LASTETS_CHAPTER
import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_LASTETS_LISTING_SHOW_LASTETS_MANGA
import eu.kanade.tachiyomi.extension.ar.gmanga.dto.TableDto
import eu.kanade.tachiyomi.extension.ar.gmanga.dto.asChapterList
import eu.kanade.tachiyomi.network.GET
@ -20,6 +23,7 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonArray
@ -32,6 +36,8 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Gmanga : ConfigurableSource, HttpSource() {
@ -53,11 +59,17 @@ class Gmanga : ConfigurableSource, HttpSource() {
.rateLimit(4)
.build()
private val parsedDatePattern: SimpleDateFormat = SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss ZZZ zzz",
Locale.ENGLISH
)
private val formattedDatePattern: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", USER_AGENT)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) = preferences.setupPreferenceScreen(screen)
override fun setupPreferenceScreen(screen: PreferenceScreen) =
preferences.setupPreferenceScreen(screen)
override fun chapterListRequest(manga: SManga): Request {
val mangaId = manga.url.substringAfterLast("/")
@ -95,54 +107,154 @@ class Gmanga : ConfigurableSource, HttpSource() {
}.sortedWith(compareBy({ -it.chapter_number }, { -it.date_upload }))
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used")
override fun imageUrlParse(response: Response): String =
throw UnsupportedOperationException("Not used")
override fun latestUpdatesParse(response: Response): MangasPage {
val data = json.decodeFromString<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html())
val isLatest = when (preferences.getString(PREF_LASTETS_LISTING)) {
PREF_LASTETS_LISTING_SHOW_LASTETS_MANGA -> true
PREF_LASTETS_LISTING_SHOW_LASTETS_CHAPTER -> false
else -> true
}
val mangas = if (!isLatest) {
val decMga = decryptResponse(response)
val selectedManga = decMga["rows"]!!.jsonArray[0].jsonObject["rows"]!!.jsonArray
buildJsonArray {
for (i in 0 until selectedManga.size) {
add(selectedManga[i].jsonArray[17])
}
}
} else {
val data = json.decodeFromString<JsonObject>(
response.asJsoup().select(".js-react-on-rails-component").html()
)
data["mangaDataAction"]!!.jsonObject["newMangas"]!!.jsonArray
}
return MangasPage(
data["mangaDataAction"]!!.jsonObject["newMangas"]!!.jsonArray.map {
mangas.jsonArray.map {
SManga.create().apply {
url = "/mangas/${it.jsonObject["id"]!!.jsonPrimitive.content}"
title = it.jsonObject["title"]!!.jsonPrimitive.content
thumbnail_url = it.jsonObject["cover"]!!.jsonPrimitive.contentOrNull?.let { coverFileName ->
val thumbnail = "medium_${coverFileName.substringBeforeLast(".")}.webp"
val thumbnail = "medium_${
it.jsonObject["cover"]!!.jsonPrimitive.content.substringBeforeLast(".")
}.webp"
thumbnail_url =
"https://media.gmanga.me/uploads/manga/cover/${it.jsonObject["id"]!!.jsonPrimitive.content}/$thumbnail"
}
}
},
false
(mangas.size >= 30) && !isLatest
)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/mangas/latest", headers)
val latestUrl = when (preferences.getString(PREF_LASTETS_LISTING)) {
PREF_LASTETS_LISTING_SHOW_LASTETS_MANGA -> "$baseUrl/mangas/latest"
PREF_LASTETS_LISTING_SHOW_LASTETS_CHAPTER -> "https://api.gmanga.me/api/releases?page=$page"
else -> "$baseUrl/mangas/latest"
}
return GET(latestUrl, headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val data = json.decodeFromString<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html())
val altNamePrefix = "مسميّات أخرى"
val translationStatusPrefix = "حالة الترجمة"
val startedDayPrefix = "تاريخ النشر"
val endedDayPrefix = "تاريخ الانتهاء"
val data = json.decodeFromString<JsonObject>(
response.asJsoup().select(".js-react-on-rails-component").html()
)
val mangaData = data["mangaDataAction"]!!.jsonObject["mangaData"]!!.jsonObject
return SManga.create().apply {
description = mangaData["summary"]!!.jsonPrimitive.contentOrNull ?: ""
artist = mangaData["artists"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
author = mangaData["authors"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
genre = mangaData["categories"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
description =
mangaData["summary"]!!.jsonPrimitive.contentOrNull?.ifEmpty { "لم يتم اضافة قصة بعد" }
artist =
mangaData["artists"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
author =
mangaData["authors"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
status = parseStatus(mangaData["story_status"].toString())
genre = listOfNotNull(
mangaData["type"]!!.jsonObject["title"]!!.jsonPrimitive.content,
mangaData["type"]!!.jsonObject["name"]!!.jsonPrimitive.content,
mangaData["categories"]!!.jsonArray.joinToString(", ") { it.jsonObject["name"]!!.jsonPrimitive.content }
).joinToString(", ")
parseTranslationStatus(mangaData["translation_status"].toString()).let {
description = "$description\n\n:$translationStatusPrefix\n$it"
}
var startedDate =
mangaData["s_date"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() }
startedDate = if (startedDate.isNullOrBlank().not()) {
parsedDatePattern.parse(startedDate!!)?.let { formattedDatePattern.format(it) }
} else {
null
}
var endedDay = mangaData["e_date"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() }
endedDay = if (endedDay.isNullOrBlank().not()) {
parsedDatePattern.parse(endedDay!!)?.let { formattedDatePattern.format(it) }
} else {
null
}
val alternativeName = listOfNotNull(
mangaData["synonyms"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() },
mangaData["arabic_title"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() },
mangaData["japanese"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() },
mangaData["english"]!!.jsonPrimitive.content.takeIf { it.isBlank().not() }
).joinToString("\n").trim()
val additionalInformation = listOfNotNull(
startedDate,
endedDay,
alternativeName
)
additionalInformation.forEach { info ->
when (info) {
startedDate ->
description =
"$description\n\n:$startedDayPrefix\n$startedDate"
endedDay -> description = "$description\n\n:$endedDayPrefix\n$endedDay"
alternativeName ->
description =
"$description\n\n:$altNamePrefix\n$alternativeName"
else -> description
}
}
}
}
private fun parseStatus(status: String?) = when {
status == null -> SManga.UNKNOWN
status.contains("2") -> SManga.ONGOING
status.contains("3") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
private fun parseTranslationStatus(status: String?) = when {
status == null -> "مجهول"
status.contains("0") -> "منتهية"
status.contains("1") -> "مستمرة"
status.contains("2") -> "متوقفة"
else -> "مجهول"
}
override fun pageListParse(response: Response): List<Page> {
val url = response.request.url.toString()
val data = json.decodeFromString<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html())
val releaseData = data["readerDataAction"]!!.jsonObject["readerData"]!!.jsonObject["release"]!!.jsonObject
val data = json.decodeFromString<JsonObject>(
response.asJsoup().select(".js-react-on-rails-component").html()
)
val releaseData =
data["readerDataAction"]!!.jsonObject["readerData"]!!.jsonObject["release"]!!.jsonObject
val hasWebP = releaseData["webp_pages"]!!.jsonArray.size > 0
return releaseData[if (hasWebP) "webp_pages" else "pages"]!!.jsonArray.map { it.jsonPrimitive.content }.mapIndexed { index, pageUri ->
Page(
index,
"$url#page_$index",
"https://media.gmanga.me/uploads/releases/${releaseData["storage_key"]!!.jsonPrimitive.content}/hq${if (hasWebP) "_webp" else ""}/$pageUri"
)
}
return releaseData[if (hasWebP) "webp_pages" else "pages"]!!.jsonArray.map { it.jsonPrimitive.content }
.mapIndexed { index, pageUri ->
Page(
index,
"$url#page_$index",
"https://media.gmanga.me/uploads/releases/${releaseData["storage_key"]!!.jsonPrimitive.content}/hq${if (hasWebP) "_webp" else ""}/$pageUri"
)
}
}
override fun popularMangaParse(response: Response) = searchMangaParse(response)
@ -157,8 +269,11 @@ class Gmanga : ConfigurableSource, HttpSource() {
SManga.create().apply {
url = "/mangas/${it.jsonObject["id"]!!.jsonPrimitive.content}"
title = it.jsonObject["title"]!!.jsonPrimitive.content
val thumbnail = "medium_${it.jsonObject["cover"]!!.jsonPrimitive.content.substringBeforeLast(".")}.webp"
thumbnail_url = "https://media.gmanga.me/uploads/manga/cover/${it.jsonObject["id"]!!.jsonPrimitive.content}/$thumbnail"
val thumbnail = "medium_${
it.jsonObject["cover"]!!.jsonPrimitive.content.substringBeforeLast(".")
}.webp"
thumbnail_url =
"https://media.gmanga.me/uploads/manga/cover/${it.jsonObject["id"]!!.jsonPrimitive.content}/$thumbnail"
}
},
mangas.size == 50
@ -166,13 +281,18 @@ class Gmanga : ConfigurableSource, HttpSource() {
}
private fun decryptResponse(response: Response): JsonObject {
val encryptedData = json.decodeFromString<JsonObject>(response.body!!.string())["data"]!!.jsonPrimitive.content
val encryptedData =
json.decodeFromString<JsonObject>(response.body!!.string())["data"]!!.jsonPrimitive.content
val decryptedData = decrypt(encryptedData)
return json.decodeFromString(decryptedData)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GmangaFilters.buildSearchPayload(page, query, if (filters.isEmpty()) getFilterList() else filters).let {
return GmangaFilters.buildSearchPayload(
page,
query,
if (filters.isEmpty()) getFilterList() else filters
).let {
val body = it.toString().toRequestBody(MEDIA_TYPE)
POST("$baseUrl/api/mangas/search", headers, body)
}
@ -181,7 +301,8 @@ class Gmanga : ConfigurableSource, HttpSource() {
override fun getFilterList() = GmangaFilters.getFilterList()
companion object {
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"
private const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"
private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
}
}

View File

@ -52,6 +52,8 @@ class GmangaPreferences(id: Long) {
// preferences
const val PREF_CHAPTER_LISTING_SHOW_ALL = "gmanga_gmanga_chapter_listing_show_all"
const val PREF_CHAPTER_LISTING_SHOW_POPULAR = "gmanga_chapter_listing_most_viewed"
const val PREF_LASTETS_LISTING_SHOW_LASTETS_CHAPTER = "gmanga_Last_listing_last_chapter_added"
const val PREF_LASTETS_LISTING_SHOW_LASTETS_MANGA = "gmanga_chapter_listing_last_manga_added"
val PREF_CHAPTER_LISTING = StringPreference(
"gmanga_chapter_listing",
@ -61,9 +63,18 @@ class GmangaPreferences(id: Long) {
StringPreferenceOption(PREF_CHAPTER_LISTING_SHOW_ALL, "عرض جميع النسخ")
)
)
val PREF_LASTETS_LISTING = StringPreference(
"gmanga_last_listing",
"كيفية عرض بقائمة الأعمال الجديدة ",
listOf(
StringPreferenceOption(PREF_LASTETS_LISTING_SHOW_LASTETS_CHAPTER, "اختيار آخر الإضافات"),
StringPreferenceOption(PREF_LASTETS_LISTING_SHOW_LASTETS_MANGA, "اختيار لمانجات الجديدة")
)
)
private val STRING_PREFERENCES = listOf(
PREF_CHAPTER_LISTING
PREF_CHAPTER_LISTING,
PREF_LASTETS_LISTING
)
}
}