diff --git a/lib-multisrc/flixscans/build.gradle.kts b/lib-multisrc/flixscans/build.gradle.kts deleted file mode 100644 index ede652be5..000000000 --- a/lib-multisrc/flixscans/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id("lib-multisrc") -} - -baseVersionCode = 6 diff --git a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt b/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt deleted file mode 100644 index a7a3a4001..000000000 --- a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt +++ /dev/null @@ -1,251 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.flixscans - -import android.util.Log -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 kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Call -import okhttp3.Callback -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import okhttp3.Response -import uy.kohesive.injekt.injectLazy -import java.io.IOException - -abstract class FlixScans( - override val name: String, - override val baseUrl: String, - override val lang: String, - protected val apiUrl: String = "$baseUrl/api/v1", - protected val cdnUrl: String = baseUrl.replace("://", "://media.").plus("/"), -) : HttpSource() { - - override val supportsLatest = true - - protected open val json: Json by injectLazy() - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(2) - .build() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiUrl/webtoon/pages/home/romance", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = response.parseAs() - - val entries = (result.hot + result.topAll + result.topMonth + result.topWeek) - .distinctBy { it.id } - .map { it.toSManga(cdnUrl) } - - return MangasPage(entries, false) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiUrl/search/advance?page=$page&serie_type=webtoon", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs>() - - val entries = result.data.map { it.toSManga(cdnUrl) } - val hasNextPage = result.lastPage > result.currentPage - - return MangasPage(entries, hasNextPage) - } - - private var fetchGenreList: List = emptyList() - private var fetchGenreCallOngoing = false - private var fetchGenreFailed = false - private var fetchGenreAttempt = 0 - - private fun fetchGenre() { - if (fetchGenreAttempt < 3 && (fetchGenreList.isEmpty() || fetchGenreFailed) && !fetchGenreCallOngoing) { - fetchGenreCallOngoing = true - - // fetch genre asynchronously as it sometimes hangs - client.newCall(fetchGenreRequest()).enqueue(fetchGenreCallback) - } - } - - private val fetchGenreCallback = object : Callback { - override fun onFailure(call: Call, e: IOException) { - fetchGenreAttempt++ - fetchGenreFailed = true - fetchGenreCallOngoing = false - - e.message?.let { Log.e("$name Filters", it) } - } - - override fun onResponse(call: Call, response: Response) { - fetchGenreCallOngoing = false - fetchGenreAttempt++ - - if (!response.isSuccessful) { - fetchGenreFailed = true - response.close() - - return - } - - val parsed = runCatching { - response.use(::fetchGenreParse) - } - - fetchGenreFailed = parsed.isFailure - fetchGenreList = parsed.getOrElse { - Log.e("$name Filters", it.stackTraceToString()) - emptyList() - } - } - } - - private fun fetchGenreRequest(): Request { - return GET("$apiUrl/search/genres", headers) - } - - private fun fetchGenreParse(response: Response): List { - return response.parseAs>() - } - - override fun getFilterList(): FilterList { - fetchGenre() - - val filters: MutableList> = mutableListOf( - Filter.Header("Ignored when using Text Search"), - MainGenreFilter(), - TypeFilter(), - StatusFilter(), - ) - - filters += if (fetchGenreList.isNotEmpty()) { - listOf( - GenreFilter("Genre", fetchGenreList), - ) - } else { - listOf( - Filter.Separator(), - Filter.Header("Press 'reset' to attempt to show Genres"), - ) - } - - return FilterList(filters) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - val url = "$apiUrl/search/serie".toHttpUrl().newBuilder() - .addPathSegment(query.trim()) - .addQueryParameter("page", page.toString()) - .build() - - return GET(url, headers) - } - - val advSearchUrl = apiUrl.toHttpUrl().newBuilder().apply { - addPathSegments("search/advance") - addQueryParameter("page", page.toString()) - addQueryParameter("serie_type", "webtoon") - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - filter.checked.let { - if (it.isNotEmpty()) { - addQueryParameter("genres", it.joinToString(",")) - } - } - } - is MainGenreFilter -> { - if (filter.state > 0) { - addQueryParameter("main_genres", filter.selected) - } - } - is TypeFilter -> { - if (filter.state > 0) { - addQueryParameter("type", filter.selected) - } - } - is StatusFilter -> { - if (filter.state > 0) { - addQueryParameter("status", filter.selected) - } - } - else -> {} - } - } - }.build() - - return GET(advSearchUrl, headers) - } - - override fun searchMangaParse(response: Response) = latestUpdatesParse(response) - - override fun mangaDetailsRequest(manga: SManga): Request { - val (prefix, id) = getPrefixIdFromUrl(manga.url) - - return GET("$apiUrl/webtoon/series/$id/$prefix", headers) - } - - override fun getMangaUrl(manga: SManga) = baseUrl + manga.url - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.parseAs() - - return result.serie.toSManga(cdnUrl) - } - - override fun chapterListRequest(manga: SManga): Request { - val (prefix, id) = getPrefixIdFromUrl(manga.url) - - return GET("$apiUrl/webtoon/chapters/$id-desc#$prefix", headers) - } - - override fun chapterListParse(response: Response): List { - val chapters = response.parseAs>() - val prefix = response.request.url.fragment!! - - return chapters.map { it.toSChapter(prefix) } - } - - override fun pageListRequest(chapter: SChapter): Request { - val (prefix, id) = getPrefixIdFromUrl(chapter.url) - - return GET("$apiUrl/webtoon/chapters/chapter/$id/$prefix", headers) - } - - protected fun getPrefixIdFromUrl(url: String): Pair { - return with(url.substringAfterLast("/")) { - val split = split("-") - - split[0] to split[1] - } - } - - override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url - - override fun pageListParse(response: Response): List { - val result = response.parseAs() - - return result.chapter.chapterData.webtoon.mapIndexed { i, img -> - Page(i, "", cdnUrl + img) - } - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() - - protected inline fun Response.parseAs(): T = - use { body.string() }.let(json::decodeFromString) -} diff --git a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt b/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt deleted file mode 100644 index 47d3e5a5c..000000000 --- a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt +++ /dev/null @@ -1,142 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.flixscans - -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import org.jsoup.Jsoup -import java.text.SimpleDateFormat -import java.util.Locale - -@Serializable -data class ApiResponse( - val data: List, - @SerialName("current_page") val currentPage: Int, - @SerialName("last_page") val lastPage: Int, -) - -@Serializable -data class HomeDto( - val hot: List, - val topWeek: List, - val topMonth: List, - val topAll: List, -) - -@Serializable -data class BrowseSeries( - val id: Int, - val title: String, - val slug: String, - val prefix: Int, - val thumbnail: String?, -) { - fun toSManga(cdnUrl: String) = SManga.create().apply { - title = this@BrowseSeries.title - url = "/series/$prefix-$id-$slug" - thumbnail_url = thumbnail?.let { cdnUrl + it } - } -} - -@Serializable -data class SearchInput( - val title: String, -) - -@Serializable -data class GenreHolder( - val name: String, - val id: Int, -) - -@Serializable -data class SeriesResponse( - val serie: Series, -) - -@Serializable -data class Series( - val id: Int, - val title: String, - val slug: String, - val prefix: Int, - val thumbnail: String?, - val story: String?, - val serieType: String?, - val mainGenres: String?, - val otherNames: List? = emptyList(), - val status: String?, - val type: String?, - val authors: List? = emptyList(), - val artists: List? = emptyList(), - val genres: List? = emptyList(), -) { - fun toSManga(cdnUrl: String) = SManga.create().apply { - title = this@Series.title - url = "/series/$prefix-$id-$slug" - thumbnail_url = cdnUrl + thumbnail - author = authors?.joinToString { it.name.trim() } - artist = artists?.joinToString { it.name.trim() } - genre = (otherGenres + genres?.map { it.name.trim() }.orEmpty()) - .distinct().joinToString { it.trim() } - description = story?.let { Jsoup.parse(it).text() } - if (otherNames?.isNotEmpty() == true) { - if (description.isNullOrEmpty()) { - description = "Alternative Names:\n" - } else { - description += "\n\nAlternative Names:\n" - } - description += otherNames.joinToString("\n") { "• ${it.trim()}" } - } - status = when (this@Series.status?.trim()) { - "ongoing" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - "onhold" -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - } - - private val otherGenres = listOfNotNull(serieType, mainGenres, type) - .map { word -> - word.trim().replaceFirstChar { - if (it.isLowerCase()) { - it.titlecase(Locale.getDefault()) - } else { - it.toString() - } - } - } -} - -@Serializable -data class Chapter( - val id: Int, - val name: String, - val slug: String, - val createdAt: String? = null, -) { - fun toSChapter(prefix: String) = SChapter.create().apply { - url = "/read/webtoon/$prefix-$id-$slug" - name = this@Chapter.name - date_upload = runCatching { dateFormat.parse(createdAt!!)!!.time }.getOrDefault(0L) - } - - companion object { - val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.ENGLISH) - } -} - -@Serializable -data class PageListResponse( - val chapter: ChapterPages, -) - -@Serializable -data class ChapterPages( - val chapterData: ChapterPageData, -) - -@Serializable -data class ChapterPageData( - val webtoon: List, -) diff --git a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt b/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt deleted file mode 100644 index aebbe7813..000000000 --- a/lib-multisrc/flixscans/src/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.flixscans - -import eu.kanade.tachiyomi.source.model.Filter - -abstract class SelectFilter( - name: String, - private val options: List, -) : Filter.Select( - name, - options.toTypedArray(), -) { - val selected get() = options[state] -} - -class CheckBoxFilter( - name: String, - val id: String, -) : Filter.CheckBox(name) - -class GenreFilter( - name: String, - private val genres: List, -) : Filter.Group( - name, - genres.map { CheckBoxFilter(it.name.trim(), it.id.toString()) }, -) { - val checked get() = state.filter { it.state }.map { it.id } -} - -class MainGenreFilter : SelectFilter( - "Main Genre", - listOf( - "", - "fantasy", - "romance", - "action", - "drama", - ), -) - -class TypeFilter : SelectFilter( - "Type", - listOf( - "", - "manhwa", - "manhua", - "manga", - "comic", - ), -) - -class StatusFilter : SelectFilter( - "Status", - listOf( - "", - "ongoing", - "completed", - "droped", - "onhold", - "soon", - ), -) diff --git a/src/all/galaxy/build.gradle b/src/all/galaxy/build.gradle new file mode 100644 index 000000000..5cc985e60 --- /dev/null +++ b/src/all/galaxy/build.gradle @@ -0,0 +1,7 @@ +ext { + extName = 'Galaxy' + extClass = '.GalaxyFactory' + extVersionCode = 1 +} + +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 new file mode 100644 index 000000000..034c4da56 Binary files /dev/null and b/src/all/galaxy/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..e0f3b79ab Binary files /dev/null and b/src/all/galaxy/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..5e884b309 Binary files /dev/null and b/src/all/galaxy/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..ab0e59453 Binary files /dev/null and b/src/all/galaxy/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..c42daac55 Binary files /dev/null and b/src/all/galaxy/res/mipmap-xxxhdpi/ic_launcher.png 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 new file mode 100644 index 000000000..9435dbf48 --- /dev/null +++ b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Galaxy.kt @@ -0,0 +1,327 @@ +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 new file mode 100644 index 000000000..60baee3c2 --- /dev/null +++ b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/GalaxyFactory.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.all.galaxy + +import eu.kanade.tachiyomi.source.SourceFactory + +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") { + override val id = 2729515745226258240 + } + + 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 new file mode 100644 index 000000000..d21cd4395 --- /dev/null +++ b/src/all/galaxy/src/eu/kanade/tachiyomi/extension/all/galaxy/Genre.kt @@ -0,0 +1,28 @@ +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/ar/galaxymanga/build.gradle b/src/ar/galaxymanga/build.gradle deleted file mode 100644 index 08f87fd00..000000000 --- a/src/ar/galaxymanga/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -ext { - extName = 'Galaxy Manga' - extClass = '.GalaxyManga' - themePkg = 'flixscans' - baseUrl = 'https://flixscans.net' - overrideVersionCode = 28 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/galaxymanga/res/mipmap-hdpi/ic_launcher.png b/src/ar/galaxymanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 508b1a1d1..000000000 Binary files a/src/ar/galaxymanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/galaxymanga/res/mipmap-mdpi/ic_launcher.png b/src/ar/galaxymanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8a5238888..000000000 Binary files a/src/ar/galaxymanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/galaxymanga/res/mipmap-xhdpi/ic_launcher.png b/src/ar/galaxymanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 0ced75903..000000000 Binary files a/src/ar/galaxymanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e5e17f45d..000000000 Binary files a/src/ar/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2e68f0aab..000000000 Binary files a/src/ar/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/galaxymanga/src/eu/kanade/tachiyomi/extension/ar/galaxymanga/GalaxyManga.kt b/src/ar/galaxymanga/src/eu/kanade/tachiyomi/extension/ar/galaxymanga/GalaxyManga.kt deleted file mode 100644 index cbaf83818..000000000 --- a/src/ar/galaxymanga/src/eu/kanade/tachiyomi/extension/ar/galaxymanga/GalaxyManga.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.galaxymanga - -import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request - -class GalaxyManga : FlixScans( - "جالاكسي مانجا", - "https://flixscans.net", - "ar", - "https://ar.flixscans.site/api/v1", -) { - override val versionId = 2 - - override fun mangaDetailsRequest(manga: SManga): Request { - val (prefix, id) = getPrefixIdFromUrl(manga.url) - - return GET("$apiUrl/series/$id/$prefix", headers) - } - - override fun chapterListRequest(manga: SManga): Request { - val (prefix, id) = getPrefixIdFromUrl(manga.url) - - return GET("$apiUrl/chapters/$id-desc#$prefix", headers) - } - - override fun pageListRequest(chapter: SChapter): Request { - val (prefix, id) = getPrefixIdFromUrl(chapter.url) - - return GET("$apiUrl/chapters/webtoon/$id/$prefix", headers) - } -} diff --git a/src/en/flixscans/build.gradle b/src/en/flixscans/build.gradle deleted file mode 100644 index 56d117249..000000000 --- a/src/en/flixscans/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -ext { - extName = 'Flix Scans' - extClass = '.FlixScansNet' - themePkg = 'flixscans' - baseUrl = 'https://flixscans.org' - overrideVersionCode = 0 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/flixscans/res/mipmap-hdpi/ic_launcher.png b/src/en/flixscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2e1b076a3..000000000 Binary files a/src/en/flixscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/flixscans/res/mipmap-mdpi/ic_launcher.png b/src/en/flixscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5964e5f77..000000000 Binary files a/src/en/flixscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/flixscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/flixscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 477a1a146..000000000 Binary files a/src/en/flixscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/flixscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/flixscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 937b9daa6..000000000 Binary files a/src/en/flixscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/flixscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/flixscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4175a36e0..000000000 Binary files a/src/en/flixscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/flixscans/src/eu/kanade/tachiyomi/extension/en/flixscans/FlixScansNet.kt b/src/en/flixscans/src/eu/kanade/tachiyomi/extension/en/flixscans/FlixScansNet.kt deleted file mode 100644 index 46b4613d1..000000000 --- a/src/en/flixscans/src/eu/kanade/tachiyomi/extension/en/flixscans/FlixScansNet.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.flixscans - -import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans - -class FlixScansNet : FlixScans( - "Flix Scans", - "https://flixscans.org", - "en", - "https://flixscans.site/api/v1", -)