@@ -3,13 +3,20 @@ package com.hexated
33import com.lagradost.cloudstream3.*
44import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
55import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
6+ import com.lagradost.cloudstream3.mvvm.safeApiCall
67import com.lagradost.cloudstream3.utils.*
78import com.lagradost.nicehttp.NiceResponse
9+ import kotlinx.coroutines.CoroutineScope
10+ import kotlinx.coroutines.Dispatchers
11+ import kotlinx.coroutines.launch
12+ import kotlinx.coroutines.coroutineScope
813import kotlinx.coroutines.async
914import kotlinx.coroutines.awaitAll
10- import kotlinx.coroutines.coroutineScope
1115import org.jsoup.Jsoup
1216import org.jsoup.nodes.Element
17+ import com.lagradost.cloudstream3.SubtitleFile
18+ import com.lagradost.cloudstream3.app
19+ import kotlin.text.Regex
1320
1421class AnimeSailProvider : MainAPI () {
1522 override var mainUrl = " https://154.26.137.28"
@@ -18,19 +25,12 @@ class AnimeSailProvider : MainAPI() {
1825 override var lang = " id"
1926 override val hasDownloadSupport = true
2027
21- override val supportedTypes = setOf (
22- TvType .Anime ,
23- TvType .AnimeMovie ,
24- TvType .OVA
25- )
28+ override val supportedTypes = setOf (TvType .Anime , TvType .AnimeMovie , TvType .OVA )
2629
2730 companion object {
2831 fun getType (t : String ): TvType {
29- return when {
30- t.contains(" OVA" , true ) || t.contains(" Special" ) -> TvType .OVA
31- t.contains(" Movie" , true ) -> TvType .AnimeMovie
32- else -> TvType .Anime
33- }
32+ return if (t.contains(" OVA" , true ) || t.contains(" Special" )) TvType .OVA
33+ else if (t.contains(" Movie" , true )) TvType .AnimeMovie else TvType .Anime
3434 }
3535
3636 fun getStatus (t : String ): ShowStatus {
@@ -45,19 +45,22 @@ class AnimeSailProvider : MainAPI() {
4545 private suspend fun request (url : String , ref : String? = null): NiceResponse {
4646 return app.get(
4747 url,
48- headers = mapOf (
49- " Accept" to " text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
50- ),
48+ headers =
49+ mapOf (
50+ " Accept" to
51+ " text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
52+ ),
5153 cookies = mapOf (" _as_ipin_ct" to " ID" ),
5254 referer = ref
5355 )
5456 }
5557
56- override val mainPage = mainPageOf(
57- " $mainUrl /rilisan-anime-terbaru/page/" to " Episode Anime Terbaru" ,
58- " $mainUrl /rilisan-donghua-terbaru/page/" to " Episode Donghua Terbaru" ,
59- " $mainUrl /movie-terbaru/page/" to " Movie Terbaru" ,
60- )
58+ override val mainPage =
59+ mainPageOf(
60+ " $mainUrl /rilisan-anime-terbaru/page/" to " Episode Anime Terbaru" ,
61+ " $mainUrl /rilisan-donghua-terbaru/page/" to " Episode Donghua Terbaru" ,
62+ " $mainUrl /movie-terbaru/page/" to " Movie Terbaru" ,
63+ )
6164
6265 override suspend fun getMainPage (page : Int , request : MainPageRequest ): HomePageResponse {
6366 val document = request(request.data + page).document
@@ -66,25 +69,31 @@ class AnimeSailProvider : MainAPI() {
6669 }
6770
6871 private fun getProperAnimeLink (uri : String ): String {
69- return if (uri.contains(" /anime/" )) uri else {
72+ return if (uri.contains(" /anime/" )) {
73+ uri
74+ } else {
7075 var title = uri.substringAfter(" $mainUrl /" )
71- title = when {
72- title.contains(" -episode" ) && ! title.contains(" -movie" ) -> title.substringBefore(" -episode" )
73- title.contains(" -movie" ) -> title.substringBefore(" -movie" )
74- else -> title
75- }
76+ title =
77+ when {
78+ (title.contains(" -episode" )) && ! (title.contains(" -movie" )) ->
79+ title.substringBefore(" -episode" )
80+
81+ (title.contains(" -movie" )) -> title.substringBefore(" -movie" )
82+ else -> title
83+ }
84+
7685 " $mainUrl /anime/$title "
7786 }
7887 }
7988
8089 private fun Element.toSearchResult (): AnimeSearchResponse {
81- val href = getProperAnimeLink(fixUrlNull(selectFirst(" a" )?.attr(" href" )).toString())
82- val title = select(" .tt > h2" ).text().trim()
83- val posterUrl = fixUrlNull( selectFirst(" div.limit img" )?.attr(" src" ))
84- val epNum = selectFirst( " .tt > h2 " )?.text()?. let {
85- Regex ( " Episode \\ s?( \\ d+) " ).find(it)?.groupValues?.getOrNull( 1 )?.toIntOrNull()
86- }
87-
90+ val href = getProperAnimeLink(fixUrlNull(this . selectFirst(" a" )?.attr(" href" )).toString())
91+ val title = this . select(" .tt > h2" ).text().trim()
92+ val posterUrl = fixUrl( this . selectFirst(" div.limit img" )?.attr(" src" ) ? : " " )
93+ val epNum =
94+ this .selectFirst( " .tt > h2 " )?.text( )?.let {
95+ Regex ( " Episode \\ s?( \\ d+) " ).find(it)?.groupValues?.getOrNull( 1 )?.toIntOrNull()
96+ }
8897 return newAnimeSearchResponse(title, href, TvType .Anime ) {
8998 this .posterUrl = posterUrl
9099 addSub(epNum)
@@ -94,24 +103,46 @@ class AnimeSailProvider : MainAPI() {
94103 override suspend fun search (query : String ): List <SearchResponse > {
95104 val link = " $mainUrl /?s=$query "
96105 val document = request(link).document
106+
97107 return document.select(" div.listupd article" ).map { it.toSearchResult() }
98108 }
99109
100110 override suspend fun load (url : String ): LoadResponse {
101111 val document = request(url).document
102112
103- val title = document.selectFirst(" h1.entry-title" )?.text()
104- ?.replace(" Subtitle Indonesia" , " " )?.trim().orEmpty()
113+ val title =
114+ document.selectFirst(" h1.entry-title" )
115+ ?.text()
116+ .toString()
117+ .replace(" Subtitle Indonesia" , " " )
118+ .trim()
105119 val poster = document.selectFirst(" div.entry-content > img" )?.attr(" src" )
106120 val type = getType(document.select(" tbody th:contains(Tipe)" ).next().text().lowercase())
107121 val year = document.select(" tbody th:contains(Dirilis)" ).next().text().trim().toIntOrNull()
108122
109- val episodes = document.select(" ul.daftar > li" ).map {
110- val link = fixUrl(it.select(" a" ).attr(" href" ))
111- val name = it.select(" a" ).text()
112- val episode = Regex (" Episode\\ s?(\\ d+)" ).find(name)?.groupValues?.getOrNull(0 )?.toIntOrNull()
113- newEpisode(link) { this .episode = episode }
114- }.reversed()
123+ // --- Corrected Episode Mapping ---
124+ val episodes =
125+ document.select(" ul.daftar > li" ) // Assuming this is your episode list selector
126+ .mapNotNull { episodeElement -> // Use mapNotNull to safely skip if data is missing
127+ val anchor = episodeElement.selectFirst(" a" ) ? : return @mapNotNull null
128+ val episodeLink = fixUrl(anchor.attr(" href" ))
129+ val episodeName = anchor.text()
130+
131+ val episodeNumber =
132+ // Renamed from 'episode' to avoid confusion with property name
133+ Regex (" Episode\\ s?(\\ d+)" )
134+ .find(episodeName)
135+ ?.groupValues
136+ ?.getOrNull(1 ) // IMPORTANT: Group 1 for the number
137+ ?.toIntOrNull()
138+
139+ newEpisode(episodeLink) { // 'episodeLink' is the 'data' argument
140+ this .name = episodeName // Set the 'name' property
141+ this .episode = episodeNumber // Set the 'episode' property (the number)
142+ }
143+ }
144+ .reversed()
145+ // --- End Corrected Episode Mapping ---
115146
116147 val tracker = APIHolder .getTracker(listOf (title), TrackerType .getTypes(type), year, true )
117148
@@ -120,9 +151,11 @@ class AnimeSailProvider : MainAPI() {
120151 backgroundPosterUrl = tracker?.cover
121152 this .year = year
122153 addEpisodes(DubStatus .Subbed , episodes)
123- showStatus = getStatus(document.select(" tbody th:contains(Status)" ).next().text().trim())
154+ showStatus =
155+ getStatus(document.select(" tbody th:contains(Status)" ).next().text().trim())
124156 plot = document.selectFirst(" div.entry-content > p" )?.text()
125- this .tags = document.select(" tbody th:contains(Genre)" ).next().select(" a" ).map { it.text() }
157+ this .tags =
158+ document.select(" tbody th:contains(Genre)" ).next().select(" a" ).map { it.text() }
126159 addMalId(tracker?.malId)
127160 addAniListId(tracker?.aniId?.toIntOrNull())
128161 }
@@ -133,99 +166,111 @@ class AnimeSailProvider : MainAPI() {
133166 isCasting : Boolean ,
134167 subtitleCallback : (SubtitleFile ) -> Unit ,
135168 callback : (ExtractorLink ) -> Unit
136- ): Boolean = coroutineScope {
169+ ): Boolean {
170+
137171 val document = request(data).document
138- val options = document.select(" .mobius > .mirror > option" )
139-
140- options.map { option ->
141- async {
142- try {
143- val iframe = fixUrl(
144- Jsoup .parse(base64Decode(option.attr(" data-em" ))).select(" iframe" ).attr(" src" )
145- ? : throw ErrorLoadingException (" No iframe found" )
146- )
147- val quality = getIndexQuality(option.text())
148-
149- when {
150- iframe.startsWith(" $mainUrl /utils/player/kodir2" ) -> {
151- val text = request(iframe, ref = data).text
152- val html = text.substringAfter(" = `" ).substringBefore(" `;" )
153- val link = Jsoup .parse(html).select(" source" ).last()?.attr(" src" )
154- if (link != null ) callback(newExtractorLink(name, name, link, INFER_TYPE ) {
155- referer = mainUrl
156- this .quality = quality
157- })
158- }
159172
160- iframe.contains(" /arch/" ) || iframe.contains(" /race/" ) ||
161- iframe.contains(" /hexupload/" ) || iframe.contains(" /pomf/" ) -> {
162- val link = request(iframe, ref = data).document.select(" source" ).attr(" src" )
163- val source = when {
164- iframe.contains(" /arch/" ) -> " Arch"
165- iframe.contains(" /race/" ) -> " Race"
166- iframe.contains(" /hexupload/" ) -> " Hexupload"
167- iframe.contains(" /pomf/" ) -> " Pomf"
168- else -> name
173+ // Replace blocking apmap with coroutine-friendly concurrent processing
174+ coroutineScope {
175+ val jobs = document.select(" .mobius > .mirror > option" ).map { element ->
176+ async {
177+ safeApiCall {
178+ val iframe =
179+ fixUrl(
180+ Jsoup .parse(base64Decode(element.attr(" data-em" )))
181+ .select(" iframe" )
182+ .attr(" src" )
183+ )
184+ val quality = getIndexQuality(element.text())
185+ when {
186+ iframe.startsWith(" $mainUrl /utils/player/arch/" ) ||
187+ iframe.startsWith(" $mainUrl /utils/player/race/" ) ->
188+ request(iframe, ref = data).document.select(" source" ).attr(" src" )
189+ .let { link ->
190+ val source =
191+ when {
192+ iframe.contains(" /arch/" ) -> " Arch"
193+ iframe.contains(" /race/" ) -> " Race"
194+ else -> this @AnimeSail.name
195+ }
196+ callback.invoke(
197+ ExtractorLink (
198+ source = source,
199+ name = source,
200+ url = link,
201+ referer = mainUrl,
202+ quality = getIndexQuality(element.text()),
203+ type = ExtractorLinkType .VIDEO
204+ )
205+ )
206+ }
207+ iframe.startsWith(" https://aghanim.xyz/tools/redirect/" ) -> {
208+ val link =
209+ " https://rasa-cintaku-semakin-berantai.xyz/v/${
210+ iframe.substringAfter(" id=" ).substringBefore(" &token" )
211+ } "
212+ loadFixedExtractor(link, quality, mainUrl, subtitleCallback, callback)
169213 }
170- callback(newExtractorLink(source, source, link, INFER_TYPE ) {
171- referer = mainUrl
172- this .quality = quality
173- })
174- }
175214
176- iframe.startsWith(" https://aghanim.xyz/tools/redirect/" ) -> {
177- val link = " https://rasa-cintaku-semakin-berantai.xyz/v/" +
178- iframe.substringAfter(" id=" ).substringBefore(" &token" )
179- loadFixedExtractor(link, quality, mainUrl, subtitleCallback, callback)
180- }
181-
182- iframe.startsWith(" $mainUrl /utils/player/framezilla/" ) ||
183- iframe.startsWith(" https://uservideo.xyz" ) -> {
184- val link = request(iframe, ref = data).document.select(" iframe" ).attr(" src" )
185- loadFixedExtractor(fixUrl(link), quality, mainUrl, subtitleCallback, callback)
186- }
215+ iframe.startsWith(" $mainUrl /utils/player/framezilla/" ) ||
216+ iframe.startsWith(" https://uservideo.xyz" ) -> {
217+ request(iframe, ref = data).document.select(" iframe" ).attr(" src" ).let { link
218+ ->
219+ loadFixedExtractor(
220+ fixUrl(link),
221+ quality,
222+ mainUrl,
223+ subtitleCallback,
224+ callback
225+ )
226+ }
227+ }
187228
188- else -> {
189- loadFixedExtractor(iframe, quality, mainUrl, subtitleCallback, callback)
229+ else -> {
230+ loadFixedExtractor(iframe, quality, mainUrl, subtitleCallback, callback)
231+ }
190232 }
191233 }
192- } catch (e: Exception ) {
193- logError(e)
194234 }
195235 }
196- }.awaitAll()
236+ jobs.awaitAll()
237+ }
197238
198- true
239+ return true
240+ }
241+
242+ private fun getIndexQuality (str : String? ): Int {
243+ return Regex (" (\\ d{3,4})[pP]" ).find(str ? : " " )?.groupValues?.getOrNull(1 )?.toIntOrNull()
244+ ? : Qualities .Unknown .value
199245 }
200246
201247 private suspend fun loadFixedExtractor (
202248 url : String ,
203- quality : Int? ,
249+ quality : Int ,
204250 referer : String? = null,
205251 subtitleCallback : (SubtitleFile ) -> Unit ,
206252 callback : (ExtractorLink ) -> Unit
207253 ) {
208254 loadExtractor(url, referer, subtitleCallback) { link ->
209- callback(
210- newExtractorLink(
211- source = link.name,
212- name = link.name,
213- url = link.url,
214- INFER_TYPE
215- ) {
216- this .referer = mainUrl
217- this .quality = if (link.type == ExtractorLinkType .M3U8 ) link.quality
218- else quality ? : Qualities .Unknown .value
219- this .type = link.type
220- this .headers = link.headers
221- this .extractorData = link.extractorData
222- }
223- )
255+ CoroutineScope (Dispatchers .IO ).launch {
256+ callback.invoke(
257+ ExtractorLink (
258+ source = name,
259+ name = name,
260+ url = link.url,
261+ referer = link.referer,
262+ quality = link.quality,
263+ type = link.type,
264+ extractorData = link.extractorData,
265+ headers = link.headers
266+ )
267+ )
268+ }
224269 }
225- }
226270
227- private fun getIndexQuality (str : String ): Int {
228- return Regex (" (\\ d{3,4})[pP]" ).find(str)?.groupValues?.getOrNull(1 )?.toIntOrNull()
229- ? : Qualities .Unknown .value
271+ fun getIndexQuality (str : String ): Int {
272+ return Regex (" (\\ d{3,4})[pP]" ).find(str)?.groupValues?.getOrNull(1 )?.toIntOrNull()
273+ ? : Qualities .Unknown .value
274+ }
230275 }
231276}
0 commit comments