-
Notifications
You must be signed in to change notification settings - Fork 553
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Migrate * Use HttpUrl * Sort chapters * Change popularRequest Co-authored-by: Vetle Ledaal <[email protected]> * Update src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyScan.kt Change searchMangaRequest Co-authored-by: Vetle Ledaal <[email protected]> * change latestUpdatesRequest Co-authored-by: Vetle Ledaal <[email protected]> * Add comment * Change chapters url * changes * Fix pages header * Use setUrlWithoutDomain * Use HttpUrl in SChapter::id and remove variable shadowing --------- Co-authored-by: Vetle Ledaal <[email protected]>
- Loading branch information
1 parent
d847abf
commit 230a689
Showing
8 changed files
with
298 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
ext { | ||
extName = 'Sussy Scan' | ||
extClass = '.SussyScan' | ||
themePkg = 'madara' | ||
baseUrl = 'https://oldi.sussytoons.com' | ||
overrideVersionCode = 4 | ||
extVersionCode = 42 | ||
isNsfw = true | ||
} | ||
|
||
apply from: "$rootDir/common.gradle" |
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
238 changes: 216 additions & 22 deletions
238
src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyScan.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,228 @@ | ||
package eu.kanade.tachiyomi.extension.pt.sussyscan | ||
|
||
import eu.kanade.tachiyomi.multisrc.madara.Madara | ||
import android.annotation.SuppressLint | ||
import eu.kanade.tachiyomi.network.GET | ||
import eu.kanade.tachiyomi.network.interceptor.rateLimit | ||
import org.jsoup.nodes.Document | ||
import org.jsoup.nodes.Element | ||
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.json.Json | ||
import kotlinx.serialization.json.decodeFromStream | ||
import okhttp3.HttpUrl.Companion.toHttpUrl | ||
import okhttp3.Interceptor | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
import org.jsoup.Jsoup | ||
import rx.Observable | ||
import uy.kohesive.injekt.injectLazy | ||
import java.text.SimpleDateFormat | ||
import java.util.Locale | ||
|
||
class SussyScan : Madara( | ||
"Sussy Scan", | ||
"https://oldi.sussytoons.com", | ||
"pt-BR", | ||
SimpleDateFormat("MMMM dd, yyyy", Locale("pt", "BR")), | ||
) { | ||
override val client = super.client.newBuilder() | ||
.rateLimit(2) | ||
import java.util.concurrent.TimeUnit | ||
|
||
class SussyScan : HttpSource() { | ||
|
||
override val name = "Sussy Scan" | ||
|
||
override val baseUrl = "https://new.sussytoons.site" | ||
|
||
private val apiUrl = "https://api-dev.sussytoons.site" | ||
|
||
override val lang = "pt-BR" | ||
|
||
override val supportsLatest = true | ||
|
||
// Moved from Madara | ||
override val versionId = 2 | ||
|
||
private val json: Json by injectLazy() | ||
|
||
override val client = network.cloudflareClient.newBuilder() | ||
.rateLimit(1, 2, TimeUnit.SECONDS) | ||
.addInterceptor(::imageLocation) | ||
.build() | ||
|
||
override val useLoadMoreRequest = LoadMoreStrategy.Never | ||
override val useNewChapterEndpoint = true | ||
override fun headersBuilder() = super.headersBuilder() | ||
.set("scan-id", "1") // Required header for requests | ||
|
||
// ============================= Popular ================================== | ||
|
||
override fun popularMangaRequest(page: Int): Request { | ||
return GET("$apiUrl/obras/top5", headers) | ||
} | ||
|
||
override fun popularMangaParse(response: Response): MangasPage { | ||
val dto = response.parseAs<WrapperDto<List<MangaDto>>>() | ||
val mangas = dto.results.map { it.toSManga() } | ||
return MangasPage(mangas, false) // There's a pagination bug | ||
} | ||
|
||
// ============================= Latest =================================== | ||
|
||
override fun latestUpdatesRequest(page: Int): Request { | ||
val url = "$apiUrl/obras/novos-capitulos".toHttpUrl().newBuilder() | ||
.addQueryParameter("pagina", page.toString()) | ||
.addQueryParameter("limite", "24") | ||
.build() | ||
return GET(url, headers) | ||
} | ||
|
||
override fun latestUpdatesParse(response: Response): MangasPage { | ||
val dto = response.parseAs<WrapperDto<List<MangaDto>>>() | ||
val mangas = dto.results.map { it.toSManga() } | ||
return MangasPage(mangas, dto.hasNextPage()) | ||
} | ||
|
||
// ============================= Search =================================== | ||
|
||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||
val url = "$apiUrl/obras".toHttpUrl().newBuilder() | ||
.addQueryParameter("pagina", page.toString()) | ||
.addQueryParameter("limite", "8") | ||
.addQueryParameter("obr_nome", query) | ||
.build() | ||
return GET(url, headers) | ||
} | ||
|
||
override fun searchMangaParse(response: Response) = latestUpdatesParse(response) | ||
|
||
// ============================= Details ================================== | ||
|
||
override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}" | ||
|
||
override fun mangaDetailsRequest(manga: SManga): Request { | ||
val url = "$apiUrl/obras".toHttpUrl().newBuilder() | ||
.addPathSegment(manga.id) | ||
.build() | ||
return GET(url, headers) | ||
} | ||
|
||
override fun mangaDetailsParse(response: Response) = | ||
response.parseAs<WrapperDto<MangaDto>>().results.toSManga() | ||
|
||
private val SManga.id: String get() { | ||
val mangaUrl = apiUrl.toHttpUrl().newBuilder() | ||
.addPathSegments(url) | ||
.build() | ||
return mangaUrl.pathSegments[2] | ||
} | ||
|
||
// ============================= Chapters ================================= | ||
|
||
override fun getChapterUrl(chapter: SChapter): String { | ||
return "$baseUrl/capitulo".toHttpUrl().newBuilder() | ||
.addPathSegment(chapter.id) | ||
.build() | ||
.toString() | ||
} | ||
|
||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) | ||
|
||
override fun chapterListParse(response: Response): List<SChapter> { | ||
return response.parseAs<WrapperDto<WrapperChapterDto>>().results.chapters.map { | ||
SChapter.create().apply { | ||
name = it.name | ||
it.chapterNumber?.let { | ||
chapter_number = it | ||
} | ||
val chapterApiUrl = "$apiUrl/capitulos".toHttpUrl().newBuilder() | ||
.addPathSegment(it.id.toString()) | ||
.build() | ||
setUrlWithoutDomain(chapterApiUrl.toString()) | ||
date_upload = it.updateAt.toDate() | ||
} | ||
} | ||
} | ||
|
||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { | ||
return super.fetchChapterList(manga) | ||
.map { it.sortedBy(SChapter::chapter_number).reversed() } | ||
} | ||
|
||
private val SChapter.id: String get() { | ||
val chapterApiUrl = apiUrl.toHttpUrl().newBuilder() | ||
.addPathSegments(url) | ||
.build() | ||
return chapterApiUrl.pathSegments.last() | ||
} | ||
|
||
// ============================= Pages ==================================== | ||
|
||
override fun pageListRequest(chapter: SChapter) = GET("$apiUrl${chapter.url}", headers) | ||
|
||
override fun pageListParse(response: Response): List<Page> { | ||
val dto = response.parseAs<WrapperDto<ChapterPageDto>>().results | ||
return dto.pages.mapIndexed { index, image -> | ||
val imageUrl = CDN_URL.toHttpUrl().newBuilder() | ||
.addPathSegments("wp-content/uploads/WP-manga/data") | ||
.addPathSegments(image.src) | ||
.build().toString() | ||
Page(index, imageUrl = imageUrl) | ||
} | ||
} | ||
|
||
override val mangaDetailsSelectorTitle = "${super.mangaDetailsSelectorTitle}, span.rate-title, title" | ||
override val mangaDetailsSelectorThumbnail = "head meta[property='og:image']" | ||
override fun imageUrlParse(response: Response): String = "" | ||
|
||
override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply { | ||
title = title.substringBeforeLast("–") | ||
override fun imageUrlRequest(page: Page): Request { | ||
val imageHeaders = headers.newBuilder() | ||
.add("Referer", "$baseUrl/") | ||
.build() | ||
return GET(page.url, imageHeaders) | ||
} | ||
|
||
override fun imageFromElement(element: Element): String? { | ||
return super.imageFromElement(element)?.takeIf { it.isNotEmpty() } | ||
?: element.attr("content") // Thumbnail from <head> | ||
// ============================= Utilities ==================================== | ||
|
||
private fun MangaDto.toSManga(): SManga { | ||
val sManga = SManga.create().apply { | ||
title = name | ||
thumbnail_url = thumbnail | ||
initialized = true | ||
val mangaUrl = "$baseUrl/obra".toHttpUrl().newBuilder() | ||
.addPathSegment(this@toSManga.id.toString()) | ||
.addPathSegment(this@toSManga.slug) | ||
.build() | ||
setUrlWithoutDomain(mangaUrl.toString()) | ||
} | ||
|
||
Jsoup.parseBodyFragment(description).let { sManga.description = it.text() } | ||
sManga.status = status.toStatus() | ||
|
||
return sManga | ||
} | ||
|
||
private fun imageLocation(chain: Interceptor.Chain): Response { | ||
val request = chain.request() | ||
val response = chain.proceed(request) | ||
if (response.isSuccessful) { | ||
return response | ||
} | ||
|
||
val url = request.url.toString() | ||
if (url.contains(CDN_URL, ignoreCase = true)) { | ||
response.close() | ||
|
||
val newRequest = request.newBuilder() | ||
.url(url.replace(CDN_URL, OLDI_URL, ignoreCase = true)) | ||
.build() | ||
|
||
return chain.proceed(newRequest) | ||
} | ||
return response | ||
} | ||
|
||
private inline fun <reified T> Response.parseAs(): T { | ||
return json.decodeFromStream(body.byteStream()) | ||
} | ||
|
||
private fun String.toDate() = | ||
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L } | ||
|
||
companion object { | ||
const val CDN_URL = "https://usc1.contabostorage.com/23b45111d96c42c18a678c1d8cba7123:cdn" | ||
const val OLDI_URL = "https://oldi.sussytoons.site" | ||
|
||
@SuppressLint("SimpleDateFormat") | ||
val dateFormat = SimpleDateFormat("yyyy-MM-dd") | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
src/pt/sussyscan/src/eu/kanade/tachiyomi/extension/pt/sussyscan/SussyScanDto.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package eu.kanade.tachiyomi.extension.pt.sussyscan | ||
|
||
import eu.kanade.tachiyomi.source.model.SManga | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.json.JsonNames | ||
|
||
@Serializable | ||
data class WrapperDto<T>( | ||
@SerialName("pagina") | ||
val currentPage: Int = 0, | ||
@SerialName("totalPaginas") | ||
val lastPage: Int = 0, | ||
@JsonNames("resultado") | ||
private val resultados: T, | ||
) { | ||
val results: T get() = resultados | ||
|
||
fun hasNextPage() = currentPage < lastPage | ||
} | ||
|
||
@Serializable | ||
class MangaDto( | ||
@SerialName("obr_id") | ||
val id: Int, | ||
@SerialName("obr_descricao") | ||
val description: String, | ||
@SerialName("obr_imagem") | ||
val thumbnail: String, | ||
@SerialName("obr_nome") | ||
val name: String, | ||
@SerialName("obr_slug") | ||
val slug: String, | ||
@SerialName("status") | ||
val status: MangaStatus, | ||
) { | ||
@Serializable | ||
class MangaStatus( | ||
@SerialName("stt_nome") | ||
val value: String, | ||
) { | ||
fun toStatus(): Int { | ||
return when (value.lowercase()) { | ||
"em andamento" -> SManga.ONGOING | ||
"completo" -> SManga.COMPLETED | ||
"hiato" -> SManga.ON_HIATUS | ||
else -> SManga.UNKNOWN | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Serializable | ||
class ChapterDto( | ||
@SerialName("cap_id") | ||
val id: Int, | ||
@SerialName("cap_nome") | ||
val name: String, | ||
@SerialName("cap_numero") | ||
val chapterNumber: Float?, | ||
@SerialName("cap_lancado_em") | ||
val updateAt: String, | ||
) | ||
|
||
@Serializable | ||
class WrapperChapterDto( | ||
@SerialName("capitulos") | ||
val chapters: List<ChapterDto>, | ||
) | ||
|
||
@Serializable | ||
class ChapterPageDto( | ||
@SerialName("cap_paginas") | ||
val pages: List<PageDto>, | ||
) | ||
|
||
@Serializable | ||
class PageDto( | ||
val src: String, | ||
) |