Skip to content

Commit 8dd0553

Browse files
Last Sync: 2025-12-24 03:22 (Mobile)
1 parent 4f54092 commit 8dd0553

28 files changed

Lines changed: 3876 additions & 46 deletions

File tree

AnichinProvider/build.gradle.kts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version = 2
2+
3+
cloudstream {
4+
description = "Anichin"
5+
language = "id"
6+
authors = listOf("Duro92")
7+
8+
/**
9+
* Status int as the following:
10+
* 0: Down
11+
* 1: Ok
12+
* 2: Slow
13+
* 3: Beta only
14+
* */
15+
status = 1 // will be 3 if unspecified
16+
tvTypes = listOf(
17+
"AsianDrama",
18+
"TvSeries",
19+
"Movie",
20+
)
21+
22+
iconUrl = "https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://anichin.watch&size=%size%"
23+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest />
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package com.anichin
2+
3+
import com.lagradost.cloudstream3.*
4+
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
5+
import com.lagradost.cloudstream3.LoadResponse.Companion.addScore
6+
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
7+
import com.lagradost.cloudstream3.MainAPI
8+
import com.lagradost.cloudstream3.SearchResponse
9+
import com.lagradost.cloudstream3.base64Decode
10+
import com.lagradost.cloudstream3.TvType
11+
import com.lagradost.cloudstream3.mainPageOf
12+
import com.lagradost.cloudstream3.newMovieSearchResponse
13+
import com.lagradost.cloudstream3.newTvSeriesLoadResponse
14+
import com.lagradost.cloudstream3.newMovieLoadResponse
15+
import com.lagradost.cloudstream3.newEpisode
16+
import com.lagradost.cloudstream3.utils.*
17+
import org.jsoup.nodes.Element
18+
import org.jsoup.Jsoup
19+
20+
class Anichin : MainAPI() {
21+
override var mainUrl = "https://anichin.watch"
22+
override var name = "Anichin"
23+
override val hasMainPage = true
24+
override var lang = "id"
25+
override val supportedTypes = setOf(TvType.Movie, TvType.TvSeries)
26+
27+
companion object {
28+
fun getStatus(t: String): ShowStatus {
29+
return when (t) {
30+
"Completed" -> ShowStatus.Completed
31+
"Ongoing" -> ShowStatus.Ongoing
32+
else -> ShowStatus.Completed
33+
}
34+
}
35+
}
36+
37+
override val mainPage = mainPageOf(
38+
"donghua/?status=&type=&order=latest" to "Baru ditambahkan",
39+
"donghua/?status=&type=&order=update" to "Update Terbaru",
40+
"donghua/?status=&type=movie&order=update" to "Movie Terbaru",
41+
"donghua/?status=&type=&order=popular" to "Terpopuler",
42+
"donghua/?sub=&order=rating" to "Rating Terbaik",
43+
)
44+
45+
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
46+
val url = "$mainUrl/${request.data}".plus("&page=$page")
47+
val document = app.get(url).document
48+
val items = document.select("div.listupd article.bs")
49+
.mapNotNull { it.toSearchResult() }
50+
return newHomePageResponse(HomePageList(request.name, items), hasNext = items.isNotEmpty())
51+
}
52+
53+
private fun Element.toSearchResult(): SearchResponse? {
54+
val linkElement = this.selectFirst("a") ?: return null
55+
val href = fixUrl(linkElement.attr("href"))
56+
val title = linkElement.attr("title").ifBlank {
57+
this.selectFirst("div.tt")?.text()
58+
} ?: return null
59+
val poster = this.selectFirst("img")?.getImageAttr()?.let { fixUrlNull(it) }
60+
61+
val isSeries = href.contains("/series/", true) || href.contains("drama", true)
62+
63+
return if (isSeries) {
64+
newTvSeriesSearchResponse(title, href, TvType.TvSeries) {
65+
this.posterUrl = poster
66+
}
67+
} else {
68+
newMovieSearchResponse(title, href, TvType.Movie) {
69+
this.posterUrl = poster
70+
}
71+
}
72+
}
73+
74+
override suspend fun search(query: String): List<SearchResponse> {
75+
val document = app.get("$mainUrl/?s=$query", timeout = 50L).document
76+
val results = document.select("div.listupd article.bs")
77+
.mapNotNull { it.toSearchResult() }
78+
return results
79+
}
80+
81+
private fun Element.toRecommendResult(): SearchResponse? {
82+
val title = this.selectFirst("div.tt")?.text()?.trim() ?: return null
83+
val href = this.selectFirst("a")?.attr("href") ?: return null
84+
val posterUrl = this.selectFirst("img")?.getImageAttr()?.let { fixUrlNull(it) }
85+
return newMovieSearchResponse(title, href, TvType.Movie) {
86+
this.posterUrl = posterUrl
87+
}
88+
}
89+
override suspend fun load(url: String): LoadResponse {
90+
val document = app.get(url).document
91+
92+
93+
val title = document.selectFirst("h1.entry-title")?.text()?.trim().orEmpty()
94+
95+
96+
val poster = document.selectFirst("div.bigcontent img")?.getImageAttr()?.let { fixUrlNull(it) }
97+
98+
99+
val description = document.select("div.entry-content p")
100+
.joinToString("\n") { it.text() }
101+
.trim()
102+
103+
104+
val year = document.selectFirst("span:matchesOwn(Dirilis:)")?.ownText()
105+
?.filter { it.isDigit() }?.take(4)?.toIntOrNull()
106+
107+
108+
109+
val duration = document.selectFirst("div.spe span:contains(Durasi:)")?.ownText()?.let {
110+
val h = Regex("(\\d+)\\s*hr").find(it)?.groupValues?.get(1)?.toIntOrNull() ?: 0
111+
val m = Regex("(\\d+)\\s*min").find(it)?.groupValues?.get(1)?.toIntOrNull() ?: 0
112+
h * 60 + m
113+
}
114+
val country = document.selectFirst("span:matchesOwn(Negara:)")?.ownText()?.trim()
115+
val type = document.selectFirst("span:matchesOwn(Tipe:)")?.ownText()?.trim()
116+
117+
// Genre / tags
118+
val tags = document.select("div.genxed a").map { it.text() }
119+
120+
// Aktor
121+
val actors = document.select("span:has(b:matchesOwn(Artis:)) a")
122+
.map { it.text().trim() }
123+
124+
val rating = document.selectFirst("div.rating strong")
125+
?.text()
126+
?.replace("Rating", "")
127+
?.trim()
128+
?.toDoubleOrNull()
129+
130+
val trailer = document.selectFirst("div.bixbox.trailer iframe")?.attr("src")
131+
132+
val status = getStatus(
133+
document.selectFirst("div.info-content div.spe span")
134+
?.ownText()
135+
?.replace(":", "")
136+
?.trim()
137+
?: ""
138+
)
139+
140+
141+
val recommendations = document.select("div.listupd article.bs")
142+
.mapNotNull { it.toRecommendResult() }
143+
144+
145+
val episodeElements = document.select("div.eplister ul li a")
146+
147+
val episodes = episodeElements
148+
.reversed() // karena biasanya terbaru di atas
149+
.mapIndexed { index, aTag ->
150+
val href = fixUrl(aTag.attr("href"))
151+
152+
newEpisode(href) {
153+
this.name = "Episode ${index + 1}"
154+
this.episode = index + 1
155+
}
156+
}
157+
158+
return if (episodes.size > 1) {
159+
// TV Series
160+
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
161+
this.posterUrl = poster
162+
this.year = year
163+
this.plot = description
164+
this.tags = tags
165+
showStatus = status
166+
this.recommendations = recommendations
167+
this.duration = duration ?: 0
168+
if (rating != null) addScore(rating.toString(), 10)
169+
addActors(actors)
170+
addTrailer(trailer)
171+
}
172+
} else {
173+
// Movie
174+
newMovieLoadResponse(title, url, TvType.Movie, episodes.firstOrNull()?.data ?: url) {
175+
this.posterUrl = poster
176+
this.year = year
177+
this.plot = description
178+
this.tags = tags
179+
this.recommendations = recommendations
180+
this.duration = duration ?: 0
181+
if (rating != null) addScore(rating.toString(), 10)
182+
addActors(actors)
183+
addTrailer(trailer)
184+
}
185+
}
186+
187+
}
188+
189+
override suspend fun loadLinks(
190+
data: String,
191+
isCasting: Boolean,
192+
subtitleCallback: (SubtitleFile) -> Unit,
193+
callback: (ExtractorLink) -> Unit
194+
): Boolean {
195+
196+
val document = app.get(data).document
197+
198+
199+
document.selectFirst("div.player-embed iframe")
200+
?.getIframeAttr()
201+
?.let { iframe ->
202+
loadExtractor(httpsify(iframe), data, subtitleCallback, callback)
203+
}
204+
205+
206+
val mirrorOptions = document.select("select.mirror option[value]:not([disabled])")
207+
208+
for (opt in mirrorOptions) {
209+
val base64 = opt.attr("value")
210+
if (base64.isBlank()) continue
211+
212+
try {
213+
// Fix untuk base64 yang diselipkan whitespace
214+
val cleanedBase64 = base64.replace("\\s".toRegex(), "")
215+
val decodedHtml = base64Decode(cleanedBase64)
216+
217+
val iframeTag = Jsoup.parse(decodedHtml).selectFirst("iframe")
218+
219+
val mirrorUrl = when {
220+
iframeTag?.attr("src")?.isNotBlank() == true ->
221+
iframeTag.attr("src")
222+
iframeTag?.attr("data-src")?.isNotBlank() == true ->
223+
iframeTag.attr("data-src")
224+
else -> null
225+
}
226+
227+
if (!mirrorUrl.isNullOrBlank()) {
228+
loadExtractor(httpsify(mirrorUrl), data, subtitleCallback, callback)
229+
}
230+
231+
} catch (e: Exception) {
232+
println("Mirror decode error: ${e.localizedMessage}")
233+
}
234+
}
235+
236+
return true
237+
}
238+
239+
240+
private fun Element.getImageAttr(): String {
241+
return when {
242+
this.hasAttr("data-src") -> this.attr("abs:data-src")
243+
this.hasAttr("data-lazy-src") -> this.attr("abs:data-lazy-src")
244+
this.hasAttr("srcset") -> this.attr("abs:srcset").substringBefore(" ")
245+
else -> this.attr("abs:src")
246+
}
247+
}
248+
249+
private fun Element?.getIframeAttr(): String? {
250+
return this?.attr("data-litespeed-src").takeIf { it?.isNotEmpty() == true }
251+
?: this?.attr("src")
252+
}
253+
254+
private fun String?.fixImageQuality(): String? {
255+
if (this == null) return null
256+
val regex = Regex("(-\\d*x\\d*)").find(this)?.groupValues?.get(0) ?: return this
257+
return this.replace(regex, "")
258+
}
259+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.anichin
2+
3+
import android.content.Context
4+
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
5+
import com.lagradost.cloudstream3.plugins.Plugin
6+
7+
8+
@CloudstreamPlugin
9+
class AnichinPlugin : Plugin() {
10+
override fun load(context: Context) {
11+
registerMainAPI(Anichin())
12+
}
13+
}

AnimeSailProvider/src/main/kotlin/com/hexated/AnimeSailProvider.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ class AnimeSailProvider : MainAPI() {
5555
}
5656

5757
override val mainPage = mainPageOf(
58-
"$mainUrl/page/" to "Episode Terbaru",
59-
"$mainUrl/movie-terbaru/page/" to "Movie Terbaru",
60-
"$mainUrl/genres/donghua/page/" to "Donghua"
58+
"$mainUrl/rilisan-anime-terbaru/page/" to "Episode Anime Terbaru",
59+
"$mainUrl/rilisan-donghua-terbaru/page/" to "Episode Donghua Terbaru",
60+
"$mainUrl/movie-terbaru/page/" to "Movie Terbaru"
6161
)
6262

6363
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {

KuronimeProvider/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// use an integer for version numbers
2+
version = 21
3+
4+
5+
cloudstream {
6+
language = "id"
7+
// All of these properties are optional, you can safely remove them
8+
9+
// description = "Lorem Ipsum"
10+
authors = listOf("Hexated")
11+
12+
/**
13+
* Status int as the following:
14+
* 0: Down
15+
* 1: Ok
16+
* 2: Slow
17+
* 3: Beta only
18+
* */
19+
status = 1 // will be 3 if unspecified
20+
tvTypes = listOf(
21+
"AnimeMovie",
22+
"Anime",
23+
"OVA",
24+
)
25+
26+
iconUrl = "https://www.google.com/s2/favicons?domain=45.12.2.2&sz=%size%"
27+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest package="com.hexated"/>

0 commit comments

Comments
 (0)