From 41f0122adbe8acc5ecc079db8bab36b4d6bbd86d Mon Sep 17 00:00:00 2001 From: hexated Date: Tue, 3 Jan 2023 05:52:08 +0700 Subject: [PATCH] added YugenAnime --- YugenAnime/build.gradle.kts | 27 +++ YugenAnime/src/main/AndroidManifest.xml | 2 + .../src/main/kotlin/com/hexated/YugenAnime.kt | 182 ++++++++++++++++++ .../kotlin/com/hexated/YugenAnimePlugin.kt | 14 ++ 4 files changed, 225 insertions(+) create mode 100644 YugenAnime/build.gradle.kts create mode 100644 YugenAnime/src/main/AndroidManifest.xml create mode 100644 YugenAnime/src/main/kotlin/com/hexated/YugenAnime.kt create mode 100644 YugenAnime/src/main/kotlin/com/hexated/YugenAnimePlugin.kt diff --git a/YugenAnime/build.gradle.kts b/YugenAnime/build.gradle.kts new file mode 100644 index 00000000..17c649ad --- /dev/null +++ b/YugenAnime/build.gradle.kts @@ -0,0 +1,27 @@ +// use an integer for version numbers +version = 1 + + +cloudstream { + language = "en" + // All of these properties are optional, you can safely remove them + + // description = "Lorem Ipsum" + authors = listOf("Hexated") + + /** + * Status int as the following: + * 0: Down + * 1: Ok + * 2: Slow + * 3: Beta only + * */ + status = 1 // will be 3 if unspecified + tvTypes = listOf( + "AnimeMovie", + "Anime", + "OVA", + ) + + iconUrl = "https://www.google.com/s2/favicons?domain=yugen.to&sz=%size%" +} \ No newline at end of file diff --git a/YugenAnime/src/main/AndroidManifest.xml b/YugenAnime/src/main/AndroidManifest.xml new file mode 100644 index 00000000..874740e3 --- /dev/null +++ b/YugenAnime/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/YugenAnime/src/main/kotlin/com/hexated/YugenAnime.kt b/YugenAnime/src/main/kotlin/com/hexated/YugenAnime.kt new file mode 100644 index 00000000..81f09016 --- /dev/null +++ b/YugenAnime/src/main/kotlin/com/hexated/YugenAnime.kt @@ -0,0 +1,182 @@ +package com.hexated + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId +import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId +import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.net.URI + +class YugenAnime : MainAPI() { + override var mainUrl = "https://yugen.to" + override var name = "YugenAnime" + override val hasMainPage = true + override var lang = "en" + override val hasDownloadSupport = true + + override val supportedTypes = setOf( + TvType.Anime, + TvType.AnimeMovie, + TvType.OVA + ) + + companion object { + fun getType(t: String): TvType { + return if (t.contains("OVA", true) || t.contains("Special", true)) TvType.OVA + else if (t.contains("Movie", true)) TvType.AnimeMovie + else TvType.Anime + } + + fun getStatus(t: String): ShowStatus { + return when (t) { + "Finished Airing" -> ShowStatus.Completed + "Currently Airing" -> ShowStatus.Ongoing + else -> ShowStatus.Completed + } + } + } + + override val mainPage = mainPageOf( + "$mainUrl/trending/?page=" to "Trending", + "$mainUrl/latest/?page=" to "Recently Released", + "$mainUrl/best/?page=" to "Most Popular Series", + "$mainUrl/new/?page=" to "New to YugenAnime", + ) + + override suspend fun getMainPage( + page: Int, + request: MainPageRequest + ): HomePageResponse { + val items = mutableListOf() + val document = app.get(request.data + page).document + val home = document.select("div.cards-grid a, ul.ep-grid li.ep-card").mapNotNull { + it.toSearchResult() + } + items.add(HomePageList(request.name, home, request.name == "Recently Released")) + return newHomePageResponse(items) + } + + private fun Element.toSearchResult(): AnimeSearchResponse? { + val title = this.attr("title").ifBlank { this.select("div.ep-origin-name").text() } + .ifBlank { this.select("span.anime-name").text() } ?: return null + val href = fixUrl(this.attr("href").ifBlank { this.select("a.ep-details").attr("href") }) + val posterUrl = fixUrlNull(this.selectFirst("img.lozad")?.attr("data-src")) + val epNum = + this.select("a.ep-thumbnail").attr("title").substringBefore(":").filter { it.isDigit() } + .toIntOrNull() + return newAnimeSearchResponse(title, href, TvType.Anime) { + this.posterUrl = posterUrl + addDubStatus(dubExist = true, subExist = true, dubEpisodes = epNum, subEpisodes = epNum) + } + } + + override suspend fun search(query: String): List { + val document = app.get("$mainUrl/search/?q=$query").document + return document.select("div.cards-grid a.anime-meta").mapNotNull { + it.toSearchResult() + } + } + + override suspend fun load(url: String): LoadResponse? { + val document = app.get(url).document + + val title = document.selectFirst("div.content h1")?.text() ?: return null + val poster = document.selectFirst("img.cover")?.attr("src") + val tags = document.getPageContent("Genres").split(",").map { it.trim() } + val type = getType(document.getPageContent("Format")) + val year = document.getPageContent("Premiered").filter { it.isDigit() }.toIntOrNull() + val status = getStatus(document.getPageContent("Status")) + val description = document.select("p.description").text() + + val malId = document.getExternalId("MyAnimeList") + val anilistId = document.getExternalId("AniList") + + val trailer = document.selectFirst("iframe.lozad.video")?.attr("src") + + val episodes = app.get("${url}watch").document.select("ul.ep-grid li.ep-card").map { eps -> + val epsTitle = eps.select("a.ep-title").text() + val link = fixUrl(eps.select("a.ep-title").attr("href")) + val episode = epsTitle.substringBefore(":").filter { it.isDigit() }.toIntOrNull() + Episode(link, name = epsTitle.substringAfter(":").trim(), episode = episode) + } + + return newAnimeLoadResponse(title, url, type) { + engName = title + posterUrl = poster + this.year = year + addEpisodes(DubStatus.Subbed, episodes) + showStatus = status + plot = description + this.tags = tags + addMalId(malId) + addAniListId(anilistId) + addTrailer(trailer) + } + } + + override suspend fun loadLinks( + data: String, + isCasting: Boolean, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ): Boolean { + + val episode = data.removeSuffix("/").split("/").last() + val dubData = data.substringBeforeLast("/$episode").let { "$it-dub/$episode" } + + listOf(data, dubData).apmap { url -> + val doc = app.get(url).document + val iframe = doc.select("iframe#main-embed").attr("src") ?: return@apmap null + val id = iframe.removeSuffix("/").split("/").lastOrNull() ?: return@apmap null + val source = app.post( + "https://yugen.to/api/embed/", data = mapOf( + "id" to id, + "ac" to "0" + ), referer = iframe, + headers = mapOf("X-Requested-With" to "XMLHttpRequest") + ).parsedSafe()?.hls?.distinct()?.firstOrNull() ?: return@apmap null + + val isDub = if (url.contains("-dub")) "dub" else "sub" + + M3u8Helper.generateM3u8( + "${getSourceType(getBaseUrl(source))} [$isDub]", + source, + "" + ).forEach(callback) + } + + return true + } + + private fun Document.getExternalId(str: String): Int? { + return this.select("div.anime-metadetails > div:contains(External Links) a:contains($str)") + .attr("href").removeSuffix("/").split("/").lastOrNull()?.toIntOrNull() + } + + private fun Document.getPageContent(str: String): String { + return this.select("div.anime-metadetails > div:contains($str) span.description").text() + } + + private fun getBaseUrl(url: String): String { + return URI(url).let { + "${it.scheme}://${it.host}" + } + } + + private fun getSourceType(url: String): String { + return when { + url.contains("vrv", true) -> "Vrv" + url.contains("gofcdn", true) -> "Gofcdn" + else -> this.name + } + } + + data class Sources( + @JsonProperty("hls") val hls: List? = null, + ) + +} \ No newline at end of file diff --git a/YugenAnime/src/main/kotlin/com/hexated/YugenAnimePlugin.kt b/YugenAnime/src/main/kotlin/com/hexated/YugenAnimePlugin.kt new file mode 100644 index 00000000..c3cd6973 --- /dev/null +++ b/YugenAnime/src/main/kotlin/com/hexated/YugenAnimePlugin.kt @@ -0,0 +1,14 @@ + +package com.hexated + +import com.lagradost.cloudstream3.plugins.CloudstreamPlugin +import com.lagradost.cloudstream3.plugins.Plugin +import android.content.Context + +@CloudstreamPlugin +class YugenAnimePlugin: Plugin() { + override fun load(context: Context) { + // All providers should be added in this manner. Please don't edit the providers list directly. + registerMainAPI(YugenAnime()) + } +} \ No newline at end of file