diff --git a/src/all/galaxy/build.gradle b/src/all/galaxy/build.gradle deleted file mode 100644 index 4be47e5a8..000000000 --- a/src/all/galaxy/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - extName = 'Galaxy' - extClass = '.GalaxyFactory' - extVersionCode = 5 - isNsfw = false -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/galaxy/res/mipmap-hdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 034c4da56..000000000 Binary files a/src/all/galaxy/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0f3b79ab..000000000 Binary files a/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5e884b309..000000000 Binary files a/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ab0e59453..000000000 Binary files a/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c42daac55..000000000 Binary files a/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Galaxy.kt b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Galaxy.kt deleted file mode 100644 index 9435dbf48..000000000 --- a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Galaxy.kt +++ /dev/null @@ -1,327 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.galaxy - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -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.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import java.util.Calendar - -abstract class Galaxy( - override val name: String, - override val baseUrl: String, - override val lang: String, -) : HttpSource() { - - override val supportsLatest = true - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(2) - .build() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int): Request { - return if (page == 1) { - GET("$baseUrl/webtoons/romance/home", headers) - } else { - GET("$baseUrl/webtoons/action/home", headers) - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val entries = document.select( - """div.tabs div[wire:snapshot*=App\\Models\\Serie], main div:has(h2:matches(Today\'s Hot|الرائج اليوم)) a[wire:snapshot*=App\\Models\\Serie]""", - ).map { element -> - SManga.create().apply { - setUrlWithoutDomain( - if (element.tagName().equals("a")) { - element.absUrl("href") - } else { - element.selectFirst("a")!!.absUrl("href") - }, - ) - thumbnail_url = element.selectFirst("img")?.absUrl("src") - title = element.selectFirst("div.text-sm")!!.text() - } - }.distinctBy { it.url } - - return MangasPage(entries, response.request.url.pathSegments.getOrNull(1) == "romance") - } - - override fun latestUpdatesRequest(page: Int): Request { - val url = "$baseUrl/latest?serie_type=webtoon&main_genres=romance" + - if (page > 1) { - "&page=$page" - } else { - "" - } - - return GET(url, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - val entries = document.select("div[wire:snapshot*=App\\\\Models\\\\Serie]").map { element -> - SManga.create().apply { - setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) - thumbnail_url = element.selectFirst("img")?.absUrl("src") - title = element.select("div.flex a[href*=/series/]").last()!!.text() - } - } - val hasNextPage = document.selectFirst("[role=navigation] button[wire:click*=nextPage]") != null - - return MangasPage(entries, hasNextPage) - } - - private var filters: List = emptyList() - private val scope = CoroutineScope(Dispatchers.IO) - protected fun launchIO(block: () -> Unit) = scope.launch { - try { - block() - } catch (_: Exception) { } - } - - override fun getFilterList(): FilterList { - launchIO { - if (filters.isEmpty()) { - val document = client.newCall(GET("$baseUrl/search", headers)).execute().asJsoup() - - val mainGenre = FilterData( - displayName = document.select("label[for$=main_genres]").text(), - options = document.select("select[wire:model.live=main_genres] option").map { - it.text() to it.attr("value") - }, - queryParameter = "main_genres", - ) - val typeFilter = FilterData( - displayName = document.select("label[for$=type]").text(), - options = document.select("select[wire:model.live=type] option").map { - it.text() to it.attr("value") - }, - queryParameter = "type", - ) - val statusFilter = FilterData( - displayName = document.select("label[for$=status]").text(), - options = document.select("select[wire:model.live=status] option").map { - it.text() to it.attr("value") - }, - queryParameter = "status", - ) - val genreFilter = FilterData( - displayName = if (lang == "ar") { - "التصنيفات" - } else { - "Genre" - }, - options = document.select("div[x-data*=genre] > div").map { - it.text() to it.attr("wire:key") - }, - queryParameter = "genre", - ) - - filters = listOf(mainGenre, typeFilter, statusFilter, genreFilter) - } - } - - val filters: List> = filters.map { - SelectFilter( - it.displayName, - it.options, - it.queryParameter, - ) - }.ifEmpty { - listOf( - Filter.Header("Press 'reset' to load filters"), - ) - } - - return FilterList(filters) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/search".toHttpUrl().newBuilder().apply { - addQueryParameter("serie_type", "webtoon") - addQueryParameter("title", query.trim()) - filters.filterIsInstance().forEach { - it.addFilterParameter(this) - } - if (page > 1) { - addQueryParameter("page", page.toString()) - } - }.build() - - return GET(url, headers) - } - - override fun searchMangaParse(response: Response) = latestUpdatesParse(response) - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - return SManga.create().apply { - title = document.select("#full_model h3").text() - thumbnail_url = document.selectFirst("main img[src*=series/webtoon]")?.absUrl("src") - status = when (document.getQueryParam("status")) { - "ongoing", "soon" -> SManga.ONGOING - "completed", "droped" -> SManga.COMPLETED - "onhold" -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - genre = buildList { - document.getQueryParam("type") - ?.capitalize()?.let(::add) - document.select("#full_model a[href*=search?genre]") - .eachText().let(::addAll) - }.joinToString() - author = document.select("#full_model [wire:key^=a-]").eachText().joinToString() - artist = document.select("#full_model [wire:key^=r-]").eachText().joinToString() - description = buildString { - append(document.select("#full_model p").text().trim()) - append("\n\nAlternative Names:\n") - document.select("#full_model [wire:key^=n-]") - .joinToString("\n") { "• ${it.text().trim().removeMdEscaped()}" } - .let(::append) - }.trim() - } - } - - private fun Document.getQueryParam(queryParam: String): String? { - return selectFirst("#full_model a[href*=search?$queryParam]") - ?.absUrl("href")?.toHttpUrlOrNull()?.queryParameter(queryParam) - } - - private fun String.capitalize(): String { - val result = StringBuilder(length) - var capitalize = true - for (char in this) { - result.append( - if (capitalize) { - char.uppercase() - } else { - char.lowercase() - }, - ) - capitalize = char.isWhitespace() - } - return result.toString() - } - - private val mdRegex = Regex("""&#(\d+);""") - - private fun String.removeMdEscaped(): String { - val char = mdRegex.find(this)?.groupValues?.get(1)?.toIntOrNull() - ?: return this - - return replaceFirst(mdRegex, Char(char).toString()) - } - - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - - return document.select("a[href*=/read/]:not([type=button])").map { element -> - SChapter.create().apply { - setUrlWithoutDomain(element.absUrl("href")) - name = element.select("span.font-normal").text() - date_upload = element.selectFirst("div:not(:has(> svg)) > span.text-xs") - ?.text().parseRelativeDate() - } - } - } - - protected open fun String?.parseRelativeDate(): Long { - this ?: return 0L - - val number = Regex("""(\d+)""").find(this)?.value?.toIntOrNull() ?: 0 - val cal = Calendar.getInstance() - - return when { - listOf("second", "ثانية").any { contains(it, true) } -> { - cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - } - - contains("دقيقتين", true) -> { - cal.apply { add(Calendar.MINUTE, -2) }.timeInMillis - } - listOf("minute", "دقائق").any { contains(it, true) } -> { - cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis - } - - contains("ساعتان", true) -> { - cal.apply { add(Calendar.HOUR, -2) }.timeInMillis - } - listOf("hour", "ساعات").any { contains(it, true) } -> { - cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - } - - contains("يوم", true) -> { - cal.apply { add(Calendar.DAY_OF_YEAR, -1) }.timeInMillis - } - contains("يومين", true) -> { - cal.apply { add(Calendar.DAY_OF_YEAR, -2) }.timeInMillis - } - listOf("day", "أيام").any { contains(it, true) } -> { - cal.apply { add(Calendar.DAY_OF_YEAR, -number) }.timeInMillis - } - - contains("أسبوع", true) -> { - cal.apply { add(Calendar.WEEK_OF_YEAR, -1) }.timeInMillis - } - contains("أسبوعين", true) -> { - cal.apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis - } - listOf("week", "أسابيع").any { contains(it, true) } -> { - cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis - } - - contains("شهر", true) -> { - cal.apply { add(Calendar.MONTH, -1) }.timeInMillis - } - contains("شهرين", true) -> { - cal.apply { add(Calendar.MONTH, -2) }.timeInMillis - } - listOf("month", "أشهر").any { contains(it, true) } -> { - cal.apply { add(Calendar.MONTH, -number) }.timeInMillis - } - - contains("سنة", true) -> { - cal.apply { add(Calendar.YEAR, -1) }.timeInMillis - } - contains("سنتان", true) -> { - cal.apply { add(Calendar.YEAR, -2) }.timeInMillis - } - listOf("year", "سنوات").any { contains(it, true) } -> { - cal.apply { add(Calendar.YEAR, -number) }.timeInMillis - } - - else -> 0L - } - } - - override fun pageListParse(response: Response): List { - val document = response.asJsoup() - - return document.select("[wire:key^=image] img").mapIndexed { idx, img -> - Page(idx, imageUrl = img.absUrl("src")) - } - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() -} diff --git a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/GalaxyFactory.kt b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/GalaxyFactory.kt deleted file mode 100644 index fae762902..000000000 --- a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/GalaxyFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.galaxy - -import android.content.SharedPreferences -import android.widget.Toast -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.SourceFactory -import keiyoushi.utils.getPreferencesLazy - -class GalaxyFactory : SourceFactory { - - class GalaxyWebtoon : Galaxy("Galaxy Webtoon", "https://galaxyaction.net", "en") { - override val id = 2602904659965278831 - } - - class GalaxyManga : - Galaxy("Galaxy Manga", "https://galaxymanga.net", "ar"), - ConfigurableSource { - override val id = 2729515745226258240 - - override val baseUrl by lazy { getPrefBaseUrl() } - - private val preferences: SharedPreferences by getPreferencesLazy() - - companion object { - private const val RESTART_APP = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق" - private const val BASE_URL_PREF_TITLE = "تعديل الرابط" - private const val BASE_URL_PREF = "overrideBaseUrl" - private const val BASE_URL_PREF_SUMMARY = ".للاستخدام المؤقت. تحديث التطبيق سيؤدي الى حذف الإعدادات" - private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl" - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { - key = BASE_URL_PREF - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(super.baseUrl) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: ${super.baseUrl}" - - setOnPreferenceChangeListener { _, _ -> - Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show() - true - } - } - screen.addPreference(baseUrlPref) - } - private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!! - - init { - preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl -> - if (prefDefaultBaseUrl != super.baseUrl) { - preferences.edit() - .putString(BASE_URL_PREF, super.baseUrl) - .putString(DEFAULT_BASE_URL_PREF, super.baseUrl) - .apply() - } - } - } - } - - override fun createSources() = listOf( - GalaxyWebtoon(), - GalaxyManga(), - ) -} diff --git a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Genre.kt b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Genre.kt deleted file mode 100644 index d21cd4395..000000000 --- a/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Genre.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.galaxy - -import eu.kanade.tachiyomi.source.model.Filter -import okhttp3.HttpUrl - -class SelectFilter( - name: String, - private val options: List>, - private val queryParam: String, -) : Filter.Select( - name, - buildList { - add("") - addAll(options.map { it.first }) - }.toTypedArray(), -) { - fun addFilterParameter(url: HttpUrl.Builder) { - if (state == 0) return - - url.addQueryParameter(queryParam, options[state - 1].second) - } -} - -class FilterData( - val displayName: String, - val options: List>, - val queryParameter: String, -) diff --git a/src/en/galaxymanga/build.gradle b/src/en/galaxymanga/build.gradle new file mode 100644 index 000000000..f1fefb17e --- /dev/null +++ b/src/en/galaxymanga/build.gradle @@ -0,0 +1,10 @@ +ext { + extName = 'Galaxy Manga' + extClass = '.GalaxyManga' + themePkg = 'mangathemesia' + baseUrl = 'https://galaxymanga.io' + overrideVersionCode = 0 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/galaxymanga/res/mipmap-hdpi/ic_launcher.png b/src/en/galaxymanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..30471157c Binary files /dev/null and b/src/en/galaxymanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/galaxymanga/res/mipmap-mdpi/ic_launcher.png b/src/en/galaxymanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..67f56c041 Binary files /dev/null and b/src/en/galaxymanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/galaxymanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/galaxymanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..6d896d273 Binary files /dev/null and b/src/en/galaxymanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..b7c965ae1 Binary files /dev/null and b/src/en/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..3bb248225 Binary files /dev/null and b/src/en/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/galaxymanga/src/eu/kanade/tachiyomi/extension/en/galaxymanga/GalaxyManga.kt b/src/en/galaxymanga/src/eu/kanade/tachiyomi/extension/en/galaxymanga/GalaxyManga.kt new file mode 100644 index 000000000..dab58c283 --- /dev/null +++ b/src/en/galaxymanga/src/eu/kanade/tachiyomi/extension/en/galaxymanga/GalaxyManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.galaxymanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia + +class GalaxyManga : MangaThemesia( + "Galaxy Manga", + "https://galaxymanga.io", + "en", +)