mirror of
				https://github.com/Jacekun/cs3xxx-repo.git
				synced 2024-08-14 23:57:09 +00:00 
			
		
		
		
	Add Hahomoe provider
This commit is contained in:
		
							parent
							
								
									8a1039db8e
								
							
						
					
					
						commit
						ea21bfed20
					
				
					 5 changed files with 332 additions and 0 deletions
				
			
		
							
								
								
									
										26
									
								
								Hahomoe/build.gradle.kts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Hahomoe/build.gradle.kts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | // use an integer for version numbers | ||||||
|  | version = 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | cloudstream { | ||||||
|  |     // All of these properties are optional, you can safely remove them | ||||||
|  | 
 | ||||||
|  |     description = "" | ||||||
|  |     authors = listOf("ArjixWasTaken") | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |     * Status int as the following: | ||||||
|  |     * 0: Down | ||||||
|  |     * 1: Ok | ||||||
|  |     * 2: Slow | ||||||
|  |     * 3: Beta only | ||||||
|  |     * */ | ||||||
|  |     status = 1 // will be 3 if unspecified | ||||||
|  | 
 | ||||||
|  |     // List of video source types. Users are able to filter for extensions in a given category. | ||||||
|  |     // You can find a list of avaliable types here: | ||||||
|  |     // https://recloudstream.github.io/cloudstream/html/app/com.lagradost.cloudstream3/-tv-type/index.html | ||||||
|  |     tvTypes = listOf("NSFW") | ||||||
|  | 
 | ||||||
|  |     iconUrl = "https://www.google.com/s2/favicons?domain=haho.moe&sz=%size%" | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								Hahomoe/src/main/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Hahomoe/src/main/AndroidManifest.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <manifest package="com.lagradost"/> | ||||||
							
								
								
									
										290
									
								
								Hahomoe/src/main/kotlin/com/jacekun/Hahomoe.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								Hahomoe/src/main/kotlin/com/jacekun/Hahomoe.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,290 @@ | ||||||
|  | package com.jacekun | ||||||
|  | 
 | ||||||
|  | import android.annotation.SuppressLint | ||||||
|  | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | import com.lagradost.cloudstream3.utils.getQualityFromName | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
|  | import java.text.SimpleDateFormat | ||||||
|  | import java.util.* | ||||||
|  | import khttp.structures.cookie.CookieJar | ||||||
|  | import org.jsoup.Jsoup | ||||||
|  | import org.jsoup.nodes.Document | ||||||
|  | 
 | ||||||
|  | //Credits https://raw.githubusercontent.com/ArjixWasTaken/CloudStream-3/master/app/src/main/java/com/ArjixWasTaken/cloudstream3/animeproviders/HahoMoeProvider.kt | ||||||
|  | 
 | ||||||
|  | class Hahomoe : MainAPI() { | ||||||
|  |     companion object { | ||||||
|  |         var token: String? = null | ||||||
|  |         var cookie: CookieJar? = null | ||||||
|  | 
 | ||||||
|  |         fun getType(t: String): TvType { | ||||||
|  |             return TvType.NSFW | ||||||
|  |             /* | ||||||
|  |             return if (t.contains("OVA") || t.contains("Special")) TvType.OVA | ||||||
|  |             else if (t.contains("Movie")) TvType.AnimeMovie else TvType.Anime | ||||||
|  |              */ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     private val globalTvType = TvType.NSFW | ||||||
|  |     override var mainUrl = "https://haho.moe" | ||||||
|  |     override var name = "Haho moe" | ||||||
|  |     override val hasQuickSearch: Boolean get() = false | ||||||
|  |     override val hasMainPage: Boolean get() = true | ||||||
|  |     override val supportedTypes: Set<TvType> get() = setOf(TvType.NSFW) | ||||||
|  | 
 | ||||||
|  |     private fun loadToken(): Boolean { | ||||||
|  |         return try { | ||||||
|  |             val response = khttp.get(mainUrl) | ||||||
|  |             cookie = response.cookies | ||||||
|  |             val document = Jsoup.parse(response.text) | ||||||
|  |             token = document.selectFirst("""meta[name="csrf-token"]""")?.attr("content") | ||||||
|  |             token != null | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun getMainPage( | ||||||
|  |         page: Int, | ||||||
|  |         request: MainPageRequest | ||||||
|  |     ): HomePageResponse { | ||||||
|  |         val items = ArrayList<HomePageList>() | ||||||
|  |         val soup = app.get(mainUrl).document | ||||||
|  |         for (section in soup.select("#content > section")) { | ||||||
|  |             try { | ||||||
|  |                 if (section.attr("id") == "toplist-tabs") { | ||||||
|  |                     for (top in section.select(".tab-content > [role=\"tabpanel\"]")) { | ||||||
|  |                         val title = "Top - " + top.attr("id").split("-")[1].uppercase(Locale.UK) | ||||||
|  |                         val anime = | ||||||
|  |                             top.select("li > a").mapNotNull { | ||||||
|  |                                 val epTitle = it.selectFirst(".thumb-title")?.text() ?: "" | ||||||
|  |                                 val url = fixUrlNull(it?.attr("href")) ?: return@mapNotNull null | ||||||
|  |                                 AnimeSearchResponse( | ||||||
|  |                                     name = epTitle, | ||||||
|  |                                     url = url, | ||||||
|  |                                     apiName = this.name, | ||||||
|  |                                     type = globalTvType, | ||||||
|  |                                     posterUrl = it.selectFirst("img")?.attr("src"), | ||||||
|  |                                     dubStatus = EnumSet.of(DubStatus.Subbed), | ||||||
|  |                                 ) | ||||||
|  |                             } | ||||||
|  |                         items.add(HomePageList(title, anime)) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     val title = section.selectFirst("h2")?.text() ?: "" | ||||||
|  |                     val anime = | ||||||
|  |                         section.select("li > a").mapNotNull { | ||||||
|  |                             val epTitle = it.selectFirst(".thumb-title")?.text() ?: "" | ||||||
|  |                             val url = fixUrlNull(it?.attr("href")) ?: return@mapNotNull null | ||||||
|  |                             AnimeSearchResponse( | ||||||
|  |                                 name = epTitle, | ||||||
|  |                                 url = url, | ||||||
|  |                                 apiName = this.name, | ||||||
|  |                                 type = globalTvType, | ||||||
|  |                                 posterUrl = it.selectFirst("img")?.attr("src"), | ||||||
|  |                                 dubStatus = EnumSet.of(DubStatus.Subbed), | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                     items.add(HomePageList(title, anime)) | ||||||
|  |                 } | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 e.printStackTrace() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (items.size <= 0) throw ErrorLoadingException() | ||||||
|  |         return HomePageResponse(items) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun getIsMovie(type: String, id: Boolean = false): Boolean { | ||||||
|  |         if (!id) return type == "Movie" | ||||||
|  | 
 | ||||||
|  |         val movies = listOf("rrso24fa", "e4hqvtym", "bl5jdbqn", "u4vtznut", "37t6h2r4", "cq4azcrj") | ||||||
|  |         val aniId = type.replace("$mainUrl/anime/", "") | ||||||
|  |         return movies.contains(aniId) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun parseSearchPage(soup: Document): ArrayList<SearchResponse> { | ||||||
|  |         val items = soup.select("ul.thumb > li > a") | ||||||
|  |         if (items.isEmpty()) return ArrayList() | ||||||
|  |         val returnValue = ArrayList<SearchResponse>() | ||||||
|  |         for (i in items) { | ||||||
|  |             val href = fixUrlNull(i.attr("href")) ?: "" | ||||||
|  |             val img = fixUrlNull(i.selectFirst("img")?.attr("src")) | ||||||
|  |             val title = i.attr("title") | ||||||
|  |             if (href.isNotBlank()) { | ||||||
|  |                 returnValue.add( | ||||||
|  |                     if (getIsMovie(href, true)) { | ||||||
|  |                         MovieSearchResponse( | ||||||
|  |                             name = title, | ||||||
|  |                             url = href, | ||||||
|  |                             apiName = this.name, | ||||||
|  |                             type = globalTvType, | ||||||
|  |                         ) | ||||||
|  |                     } else { | ||||||
|  |                         AnimeSearchResponse( | ||||||
|  |                             name = title, | ||||||
|  |                             url = href, | ||||||
|  |                             apiName = this.name, | ||||||
|  |                             type = globalTvType, | ||||||
|  |                             posterUrl = img, | ||||||
|  |                             dubStatus = EnumSet.of(DubStatus.Subbed), | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return returnValue | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @SuppressLint("SimpleDateFormat") | ||||||
|  |     private fun dateParser(dateString: String): String? { | ||||||
|  |         try { | ||||||
|  |             val format = SimpleDateFormat("dd 'of' MMM',' yyyy") | ||||||
|  |             val newFormat = SimpleDateFormat("dd-MM-yyyy") | ||||||
|  |             val data = | ||||||
|  |                 format.parse( | ||||||
|  |                     dateString | ||||||
|  |                         .replace("th ", " ") | ||||||
|  |                         .replace("st ", " ") | ||||||
|  |                         .replace("nd ", " ") | ||||||
|  |                         .replace("rd ", " ") | ||||||
|  |                 ) | ||||||
|  |                     ?: return null | ||||||
|  |             return newFormat.format(data) | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun search(query: String): ArrayList<SearchResponse> { | ||||||
|  |         val url = "$mainUrl/anime" | ||||||
|  |         var response = | ||||||
|  |             app.get( | ||||||
|  |                 url, | ||||||
|  |                 params = mapOf("q" to query), | ||||||
|  |                 cookies = mapOf("loop-view" to "thumb") | ||||||
|  |             ) | ||||||
|  |         var document = Jsoup.parse(response.text) | ||||||
|  |         val returnValue = parseSearchPage(document) | ||||||
|  | 
 | ||||||
|  |         while (document.select("""a.page-link[rel="next"]""").isNullOrEmpty()) { | ||||||
|  |             val link = document.select("""a.page-link[rel="next"]""") | ||||||
|  |             if (link.isNotEmpty()) { | ||||||
|  |                 response = app.get(link[0].attr("href"), cookies = mapOf("loop-view" to "thumb")) | ||||||
|  |                 document = Jsoup.parse(response.text) | ||||||
|  |                 returnValue.addAll(parseSearchPage(document)) | ||||||
|  |             } else { | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return returnValue | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun load(url: String): LoadResponse { | ||||||
|  |         val document = app.get(url, cookies = mapOf("loop-view" to "thumb")).document | ||||||
|  | 
 | ||||||
|  |         val englishTitle = | ||||||
|  |             document.selectFirst("span.value > span[title=\"English\"]") | ||||||
|  |                 ?.parent() | ||||||
|  |                 ?.text() | ||||||
|  |                 ?.trim() | ||||||
|  |         val japaneseTitle = | ||||||
|  |             document.selectFirst("span.value > span[title=\"Japanese\"]") | ||||||
|  |                 ?.parent() | ||||||
|  |                 ?.text() | ||||||
|  |                 ?.trim() | ||||||
|  |         val canonicalTitle = document.selectFirst("header.entry-header > h1.mb-3")?.text()?.trim() | ||||||
|  | 
 | ||||||
|  |         val episodeNodes = document.select("li[class*=\"episode\"] > a") | ||||||
|  | 
 | ||||||
|  |         val episodes = episodeNodes.mapNotNull { | ||||||
|  |             val dataUrl = it?.attr("href") ?: return@mapNotNull null | ||||||
|  |             val epi = Episode( | ||||||
|  |                 data = dataUrl, | ||||||
|  |                 name = it.selectFirst(".episode-title")?.text()?.trim(), | ||||||
|  |                 posterUrl = it.selectFirst("img")?.attr("src"), | ||||||
|  |                 description = it.attr("data-content").trim(), | ||||||
|  |             ) | ||||||
|  |             epi.addDate(it.selectFirst(".episode-date")?.text()?.trim()) | ||||||
|  |             epi | ||||||
|  |         } | ||||||
|  |         val status = | ||||||
|  |             when (document.selectFirst("li.status > .value")?.text()?.trim()) { | ||||||
|  |                 "Ongoing" -> ShowStatus.Ongoing | ||||||
|  |                 "Completed" -> ShowStatus.Completed | ||||||
|  |                 else -> null | ||||||
|  |             } | ||||||
|  |         val yearText = document.selectFirst("li.release-date .value")?.text() ?: "" | ||||||
|  |         val pattern = "(\\d{4})".toRegex() | ||||||
|  |         val (year) = pattern.find(yearText)!!.destructured | ||||||
|  | 
 | ||||||
|  |         val poster = document.selectFirst("img.cover-image")?.attr("src") | ||||||
|  |         val type = document.selectFirst("a[href*=\"$mainUrl/type/\"]")?.text()?.trim() | ||||||
|  | 
 | ||||||
|  |         val synopsis = document.selectFirst(".entry-description > .card-body")?.text()?.trim() | ||||||
|  |         val genre = | ||||||
|  |             document.select("li.genre.meta-data > span.value").map { | ||||||
|  |                 it?.text()?.trim().toString() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         val synonyms = | ||||||
|  |             document.select("li.synonym.meta-data > div.info-box > span.value").map { | ||||||
|  |                 it?.text()?.trim().toString() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         return AnimeLoadResponse( | ||||||
|  |             englishTitle, | ||||||
|  |             japaneseTitle, | ||||||
|  |             canonicalTitle ?: "", | ||||||
|  |             url, | ||||||
|  |             this.name, | ||||||
|  |             getType(type ?: ""), | ||||||
|  |             poster, | ||||||
|  |             year.toIntOrNull(), | ||||||
|  |             hashMapOf(DubStatus.Subbed to episodes), | ||||||
|  |             status, | ||||||
|  |             synopsis, | ||||||
|  |             ArrayList(genre), | ||||||
|  |             ArrayList(synonyms), | ||||||
|  |             null, | ||||||
|  |             null, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun loadLinks( | ||||||
|  |         data: String, | ||||||
|  |         isCasting: Boolean, | ||||||
|  |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|  |         callback: (ExtractorLink) -> Unit | ||||||
|  |     ): Boolean { | ||||||
|  |         val soup = app.get(data).document | ||||||
|  | 
 | ||||||
|  |         val sources = ArrayList<ExtractorLink>() | ||||||
|  |         for (source in soup.select("""[aria-labelledby="mirror-dropdown"] > li > a.dropdown-item""")) { | ||||||
|  |             val release = source.text().replace("/", "").trim() | ||||||
|  |             val sourceSoup = app.get( | ||||||
|  |                 "$mainUrl/embed?v=${source.attr("href").split("v=")[1].split("&")[0]}", | ||||||
|  |                 headers=mapOf("Referer" to data) | ||||||
|  |             ).document | ||||||
|  | 
 | ||||||
|  |             for (quality in sourceSoup.select("video#player > source")) { | ||||||
|  |                 sources.add( | ||||||
|  |                     ExtractorLink( | ||||||
|  |                         this.name, | ||||||
|  |                         "${this.name} $release - " + quality.attr("title"), | ||||||
|  |                         fixUrl(quality.attr("src")), | ||||||
|  |                         this.mainUrl, | ||||||
|  |                         getQualityFromName(quality.attr("title")) | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for (source in sources) { | ||||||
|  |             callback.invoke(source) | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								Hahomoe/src/main/kotlin/com/jacekun/HahomoePlugin.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Hahomoe/src/main/kotlin/com/jacekun/HahomoePlugin.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | package com.jacekun | ||||||
|  | 
 | ||||||
|  | import com.lagradost.cloudstream3.plugins.CloudstreamPlugin | ||||||
|  | import com.lagradost.cloudstream3.plugins.Plugin | ||||||
|  | import android.content.Context | ||||||
|  | 
 | ||||||
|  | @CloudstreamPlugin | ||||||
|  | class HahomoePlugin: Plugin() { | ||||||
|  |     override fun load(context: Context) { | ||||||
|  |         // All providers should be added in this manner. Please don't edit the providers list directly. | ||||||
|  |         registerMainAPI(Hahomoe()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -81,6 +81,7 @@ subprojects { | ||||||
|         implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library |         implementation("com.github.Blatzar:NiceHttp:0.3.2") // http library | ||||||
|         implementation("org.jsoup:jsoup:1.13.1") // html parser |         implementation("org.jsoup:jsoup:1.13.1") // html parser | ||||||
|         implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") |         implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") | ||||||
|  |         implementation("io.karn:khttp-android:0.1.2") | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue