diff --git a/lib-multisrc/zerotheme/build.gradle.kts b/lib-multisrc/zerotheme/build.gradle.kts new file mode 100644 index 000000000..dc076cc37 --- /dev/null +++ b/lib-multisrc/zerotheme/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("lib-multisrc") +} + +baseVersionCode = 1 diff --git a/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroTheme.kt b/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroTheme.kt new file mode 100644 index 000000000..12c160572 --- /dev/null +++ b/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroTheme.kt @@ -0,0 +1,83 @@ +package eu.kanade.tachiyomi.multisrc.zerotheme + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +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.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import keiyoushi.utils.parseAs +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response + +abstract class ZeroTheme( + override val name: String, + override val baseUrl: String, + override val lang: String, +) : HttpSource() { + + override val supportsLatest: Boolean = true + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(2) + .build() + + open val cdnUrl: String = "https://cdn.${baseUrl.substringAfterLast("/")}" + + open val imageLocation: String = "images" + + private val sourceLocation: String get() = "$cdnUrl/$imageLocation" + + // =========================== Popular ================================ + + override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList()) + + override fun popularMangaParse(response: Response) = searchMangaParse(response) + + // =========================== Latest =================================== + + override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) + + override fun latestUpdatesParse(response: Response): MangasPage = + MangasPage(response.toDto().toSMangaList(sourceLocation), hasNextPage = false) + + // =========================== Search ================================= + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/api/search".toHttpUrl().newBuilder() + .addQueryParameter("q", query) + .addQueryParameter("page", page.toString()) + .build() + return GET(url, headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val dto = response.parseAs() + val mangas = dto.mangas.map { it.toSManga(sourceLocation) } + return MangasPage(mangas, hasNextPage = dto.hasNextPage()) + } + + // =========================== Details ================================= + + override fun mangaDetailsParse(response: Response) = response.toDto().toSManga(sourceLocation) + + // =========================== Chapter ================================= + + override fun chapterListParse(response: Response) = response.toDto().toSChapterList() + + // =========================== Pages =================================== + + override fun pageListParse(response: Response): List = + response.toDto().toPageList(sourceLocation) + + override fun imageUrlParse(response: Response) = "" + + // =========================== Utilities =============================== + + inline fun Response.toDto(): T { + val jsonString = asJsoup().selectFirst("[data-page]")!!.attr("data-page") + return jsonString.parseAs() + } +} diff --git a/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroThemeDto.kt b/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroThemeDto.kt new file mode 100644 index 000000000..f3eaa893a --- /dev/null +++ b/lib-multisrc/zerotheme/src/eu/kanade/tachiyomi/multisrc/zerotheme/ZeroThemeDto.kt @@ -0,0 +1,139 @@ +package eu.kanade.tachiyomi.multisrc.zerotheme + +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import keiyoushi.utils.tryParse +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames +import org.jsoup.Jsoup +import java.text.SimpleDateFormat +import java.util.Locale + +@Serializable +class Props( + @JsonNames("comic_infos", "chapter", "new_chapters") + val content: T, +) + +@Serializable +class LatestDto( + private val props: Props>, +) { + fun toSMangaList(srcPath: String) = props.content.map { it.comic.toSManga(srcPath) } + + @Serializable + class Comic( + val comic: MangaDto, + ) +} + +@Serializable +class MangaDetailsDto( + private val props: Props, +) { + fun toSManga(srcPath: String) = props.content.toSManga(srcPath) + fun toSChapterList() = props.content.chapters!!.map { it.toSChapter() } +} + +@Serializable +class PageDto( + val props: Props, +) { + fun toPageList(srcPath: String): List { + return props.content.chapter.pages + .filter { it.pathSegment.contains("xml").not() } + .mapIndexed { index, path -> + Page(index, imageUrl = "$srcPath/${path.pathSegment}") + } + } + + @Serializable + class ChapterWrapper( + val chapter: Chapter, + ) + + @Serializable + class Chapter( + val pages: List, + ) + + @Serializable + class Image( + @SerialName("page_path") + val pathSegment: String, + ) +} + +@Serializable +class SearchDto( + @SerialName("comics") + private val page: PageDto, +) { + + val mangas: List get() = page.data + + fun hasNextPage() = page.currentPage < page.lastPage + + @Serializable + class PageDto( + val `data`: List, + @SerialName("last_page") + val lastPage: Int = 0, + @SerialName("current_page") + val currentPage: Int = 0, + ) +} + +@Serializable +class MangaDto( + val title: String, + val description: String?, + @SerialName("cover") + val thumbnailUrl: String?, + val slug: String, + val status: List? = emptyList(), + val genres: List? = emptyList(), + val chapters: List? = emptyList(), +) { + + fun toSManga(srcPath: String) = SManga.create().apply { + title = this@MangaDto.title + description = this@MangaDto.description?.let { Jsoup.parseBodyFragment(it).text() } + this.thumbnail_url = thumbnailUrl?.let { "$srcPath/$it" } + + status = when (this@MangaDto.status?.firstOrNull()?.name?.lowercase()) { + "em andamento" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + genre = genres?.joinToString { it.name } + url = "/comic/$slug" + } + + @Serializable + class ValueDto( + val name: String, + ) +} + +@Serializable +class ChapterDto( + @SerialName("chapter_number") + val number: Float, + @SerialName("chapter_path") + val path: String, + @SerialName("created_at") + val createdAt: String, +) { + fun toSChapter() = SChapter.create().apply { + name = number.toString() + chapter_number = number + date_upload = dateFormat.tryParse(createdAt) + url = "/chapter/$path" + } + + companion object { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT) + } +} diff --git a/src/pt/egotoons/build.gradle b/src/pt/egotoons/build.gradle index 0e78513da..c28e8e1fd 100644 --- a/src/pt/egotoons/build.gradle +++ b/src/pt/egotoons/build.gradle @@ -1,9 +1,9 @@ ext { extName = 'Ego Toons' extClass = '.EgoToons' - themePkg = 'yuyu' + themePkg = 'zerotheme' baseUrl = 'https://egotoons.com' - overrideVersionCode = 1 + overrideVersionCode = 2 isNsfw = true } diff --git a/src/pt/egotoons/src/eu/kanade/tachiyomi/extension/pt/egotoons/EgoToons.kt b/src/pt/egotoons/src/eu/kanade/tachiyomi/extension/pt/egotoons/EgoToons.kt index 859425ebf..ce38c2398 100644 --- a/src/pt/egotoons/src/eu/kanade/tachiyomi/extension/pt/egotoons/EgoToons.kt +++ b/src/pt/egotoons/src/eu/kanade/tachiyomi/extension/pt/egotoons/EgoToons.kt @@ -1,18 +1,18 @@ package eu.kanade.tachiyomi.extension.pt.egotoons -import eu.kanade.tachiyomi.multisrc.yuyu.YuYu +import eu.kanade.tachiyomi.multisrc.zerotheme.ZeroTheme import eu.kanade.tachiyomi.network.interceptor.rateLimit -class EgoToons : YuYu( +class EgoToons : ZeroTheme( "Ego Toons", "https://egotoons.com", "pt-BR", ) { - - override fun headersBuilder() = super.headersBuilder() - .set("Accept-Encoding", "") + override val versionId = 2 override val client = super.client.newBuilder() .rateLimit(2) .build() + + override val imageLocation = "image-db" } diff --git a/src/pt/lertoons/build.gradle b/src/pt/lertoons/build.gradle new file mode 100644 index 000000000..17a9d9f8d --- /dev/null +++ b/src/pt/lertoons/build.gradle @@ -0,0 +1,10 @@ +ext { + extName = 'Ler Toons' + extClass = '.LerToons' + themePkg = 'zerotheme' + baseUrl = 'https://lertoons.com' + overrideVersionCode = 0 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/lertoons/res/mipmap-hdpi/ic_launcher.png b/src/pt/lertoons/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..6bd0ddd91 Binary files /dev/null and b/src/pt/lertoons/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/lertoons/res/mipmap-mdpi/ic_launcher.png b/src/pt/lertoons/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..40b980d18 Binary files /dev/null and b/src/pt/lertoons/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/lertoons/res/mipmap-xhdpi/ic_launcher.png b/src/pt/lertoons/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..2391dab56 Binary files /dev/null and b/src/pt/lertoons/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/lertoons/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/lertoons/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..0e3934f99 Binary files /dev/null and b/src/pt/lertoons/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/lertoons/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/lertoons/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..e874f9f19 Binary files /dev/null and b/src/pt/lertoons/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/lertoons/src/eu/kanade/tachiyomi/extension/pt/lertoons/LerToons.kt b/src/pt/lertoons/src/eu/kanade/tachiyomi/extension/pt/lertoons/LerToons.kt new file mode 100644 index 000000000..da6c72424 --- /dev/null +++ b/src/pt/lertoons/src/eu/kanade/tachiyomi/extension/pt/lertoons/LerToons.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.pt.lertoons + +import eu.kanade.tachiyomi.multisrc.zerotheme.ZeroTheme +import eu.kanade.tachiyomi.network.interceptor.rateLimit + +class LerToons : ZeroTheme( + "Ler Toons", + "https://lertoons.com", + "pt-BR", +) { + override val versionId = 3 + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(2) + .build() +}