diff --git a/src/pt/reaperscans/AndroidManifest.xml b/src/pt/reaperscans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/reaperscans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/pt/reaperscans/build.gradle b/src/pt/reaperscans/build.gradle deleted file mode 100644 index 549ba0ab6..000000000 --- a/src/pt/reaperscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Reaper Scans' - pkgNameSuffix = 'pt.reaperscans' - extClass = '.ReaperScans' - extVersionCode = 33 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/reaperscans/res/mipmap-hdpi/ic_launcher.png b/src/pt/reaperscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f6c5fc5a7..000000000 Binary files a/src/pt/reaperscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/reaperscans/res/mipmap-mdpi/ic_launcher.png b/src/pt/reaperscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e7ffd65f3..000000000 Binary files a/src/pt/reaperscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/reaperscans/res/mipmap-xhdpi/ic_launcher.png b/src/pt/reaperscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d79fd54e9..000000000 Binary files a/src/pt/reaperscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/reaperscans/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/reaperscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 779d9aea3..000000000 Binary files a/src/pt/reaperscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/reaperscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/reaperscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 08a0864d9..000000000 Binary files a/src/pt/reaperscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/reaperscans/res/web_hi_res_512.png b/src/pt/reaperscans/res/web_hi_res_512.png deleted file mode 100644 index 1c6f8c99c..000000000 Binary files a/src/pt/reaperscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScans.kt b/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScans.kt deleted file mode 100644 index cd91ca0a9..000000000 --- a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScans.kt +++ /dev/null @@ -1,258 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.reaperscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -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.encodeToString -import kotlinx.serialization.json.Json -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy - -class ReaperScans : HttpSource() { - - override val name = "Reaper Scans" - - override val baseUrl = "https://reaperscans.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = true - - // Migrated from Madara to a custom CMS. - override val versionId = 2 - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimitHost(API_URL.toHttpUrl(), 1, 2) - .build() - - private val json: Json by injectLazy() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int): Request { - val payloadObj = ReaperSearchDto( - order = "desc", - orderBy = "total_views", - status = "Ongoing", - type = "Comic" - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$API_URL/series/querysearch", apiHeaders, payload) - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangaList = response.parseAs>() - .map(ReaperSeriesDto::toSManga) - - return MangasPage(mangaList, hasNextPage = false) - } - - override fun latestUpdatesRequest(page: Int): Request { - val payloadObj = ReaperSearchDto( - order = "desc", - orderBy = "latest", - status = "Ongoing", - type = "Comic" - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$API_URL/series/querysearch", apiHeaders, payload) - } - - override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val sortByFilter = filters.firstInstanceOrNull() - - val payloadObj = ReaperSearchDto( - order = if (sortByFilter?.state?.ascending == true) "asc" else "desc", - orderBy = sortByFilter?.selected ?: "total_views", - status = filters.firstInstanceOrNull()?.selected?.value ?: "Ongoing", - type = "Comic", - tagIds = filters.firstInstanceOrNull()?.state - ?.filter(Genre::state) - ?.map(Genre::id) - .orEmpty() - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - val apiUrl = "$API_URL/series/querysearch".toHttpUrl().newBuilder() - .addQueryParameter("q", query) - .toString() - - return POST(apiUrl, apiHeaders, payload) - } - - override fun searchMangaParse(response: Response): MangasPage { - val query = response.request.url.queryParameter("q").orEmpty() - - var mangaList = response.parseAs>() - .map(ReaperSeriesDto::toSManga) - - if (query.isNotBlank()) { - mangaList = mangaList.filter { it.title.contains(query, ignoreCase = true) } - } - - return MangasPage(mangaList, hasNextPage = false) - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(seriesDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun seriesDetailsRequest(manga: SManga): Request { - val seriesSlug = manga.url.substringAfterLast("/") - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .build() - - return GET("$API_URL/series/$seriesSlug#${manga.status}", apiHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - return response.parseAs().toSManga().apply { - status = response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN - } - } - - override fun chapterListRequest(manga: SManga): Request = seriesDetailsRequest(manga) - - override fun chapterListParse(response: Response): List { - val result = response.parseAs() - val seriesSlug = response.request.url.pathSegments.last() - - return result.chapters.orEmpty() - .map { it.toSChapter(seriesSlug) } - .reversed() - } - - override fun pageListRequest(chapter: SChapter): Request { - val chapterId = chapter.url.substringAfterLast("#") - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .build() - - return GET("$API_URL/series/chapter/$chapterId", apiHeaders) - } - - override fun pageListParse(response: Response): List { - return response.parseAs().content?.images.orEmpty() - .mapIndexed { i, url -> Page(i, "", "$API_URL/$url") } - } - - override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val imageHeaders = headersBuilder() - .add("Accept", ACCEPT_IMAGE) - .build() - - return GET(page.imageUrl!!, imageHeaders) - } - - private fun getStatusList(): List = listOf( - Status("Em andamento", "Ongoing"), - Status("Em hiato", "Hiatus"), - Status("Cancelado", "Dropped"), - ) - - private fun getSortProperties(): List = listOf( - SortProperty("Título", "title"), - SortProperty("Visualizações", "total_views"), - SortProperty("Data de criação", "latest") - ) - - private fun getGenreList(): List = listOf( - Genre("Artes Marciais", 2), - Genre("Aventura", 10), - Genre("Ação", 9), - Genre("Comédia", 14), - Genre("Drama", 15), - Genre("Escolar", 7), - Genre("Fantasia", 11), - Genre("Ficção científica", 16), - Genre("Guerra", 17), - Genre("Isekai", 18), - Genre("Jogo", 12), - Genre("Mangá", 24), - Genre("Manhua", 23), - Genre("Manhwa", 22), - Genre("Mecha", 19), - Genre("Mistério", 20), - Genre("Nacional", 8), - Genre("Realidade Virtual", 21), - Genre("Retorno", 3), - Genre("Romance", 5), - Genre("Segunda vida", 4), - Genre("Seinen", 1), - Genre("Shounen", 13), - Genre("Terror", 6) - ) - - override fun getFilterList(): FilterList = FilterList( - StatusFilter(getStatusList()), - SortByFilter(getSortProperties()), - GenreFilter(getGenreList()) - ) - - private inline fun Response.parseAs(): T = use { - json.decodeFromString(it.body?.string().orEmpty()) - } - - private inline fun List<*>.firstInstanceOrNull(): R? = - filterIsInstance().firstOrNull() - - companion object { - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_JSON = "application/json, text/plain, */*" - - const val API_URL = "https://api.reaperscans.com.br" - - private val JSON_MEDIA_TYPE = "application/json".toMediaType() - } -} diff --git a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansDto.kt b/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansDto.kt deleted file mode 100644 index 2b50a30a2..000000000 --- a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansDto.kt +++ /dev/null @@ -1,92 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.reaperscans - -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 ReaperSeriesDto( - val id: Int, - @SerialName("series_slug") val slug: String, - val author: String? = null, - val description: String? = null, - val studio: String? = null, - val status: String? = null, - val thumbnail: String, - val title: String, - val tags: List? = emptyList(), - val chapters: List? = emptyList() -) { - - fun toSManga(): SManga = SManga.create().apply { - val descriptionBody = this@ReaperSeriesDto.description?.let(Jsoup::parseBodyFragment) - - title = this@ReaperSeriesDto.title - author = this@ReaperSeriesDto.author?.trim() - artist = this@ReaperSeriesDto.studio?.trim() - description = descriptionBody?.select("p") - ?.joinToString("\n\n") { it.text() } - ?.ifEmpty { descriptionBody.text().replace("\n", "\n\n") } - genre = tags.orEmpty() - .sortedBy(ReaperTagDto::name) - .joinToString { it.name } - thumbnail_url = "${ReaperScans.API_URL}/cover/$thumbnail" - status = when (this@ReaperSeriesDto.status) { - "Ongoing" -> SManga.ONGOING - "Hiatus" -> SManga.ON_HIATUS - "Dropped" -> SManga.CANCELLED - "Completed", "Finished" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - url = "/series/$slug" - } -} - -@Serializable -data class ReaperTagDto(val name: String) - -@Serializable -data class ReaperChapterDto( - val id: Int, - @SerialName("chapter_name") val name: String, - @SerialName("chapter_slug") val slug: String, - val index: String, - @SerialName("created_at") val createdAt: String, -) { - - fun toSChapter(seriesSlug: String): SChapter = SChapter.create().apply { - name = this@ReaperChapterDto.name.trim() - date_upload = runCatching { DATE_FORMAT.parse(createdAt.substringBefore("."))?.time } - .getOrNull() ?: 0L - url = "/series/$seriesSlug/$slug#$id" - } - - companion object { - private val DATE_FORMAT by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale("pt", "BR")) - } - } -} - -@Serializable -data class ReaperReaderDto( - val content: ReaperReaderContentDto? = null -) - -@Serializable -data class ReaperReaderContentDto( - val images: List? = emptyList() -) - -@Serializable -data class ReaperSearchDto( - val order: String, - @SerialName("order_by") val orderBy: String, - @SerialName("series_status") val status: String, - @SerialName("series_type") val type: String, - @SerialName("tags_ids") val tagIds: List = emptyList() -) diff --git a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansFilters.kt b/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansFilters.kt deleted file mode 100644 index 5a4c451c6..000000000 --- a/src/pt/reaperscans/src/eu/kanade/tachiyomi/extension/pt/reaperscans/ReaperScansFilters.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.reaperscans - -import eu.kanade.tachiyomi.source.model.Filter - -class Genre(title: String, val id: Int) : Filter.CheckBox(title) - -class GenreFilter(genres: List) : Filter.Group("Gêneros", genres) - -open class EnhancedSelect(name: String, values: Array) : Filter.Select(name, values) { - val selected: T - get() = values[state] -} - -data class Status(val name: String, val value: String) { - override fun toString(): String = name -} - -class StatusFilter(statuses: List) : EnhancedSelect( - "Status", - statuses.toTypedArray() -) - -data class SortProperty(val name: String, val value: String) { - override fun toString(): String = name -} - -class SortByFilter(private val sortProperties: List) : Filter.Sort( - "Ordenar por", - sortProperties.map { it.name }.toTypedArray(), - Selection(1, ascending = false) -) { - val selected: String - get() = sortProperties[state!!.index].value -}