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("org.jsoup:jsoup:1.13.1") // html parser
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
|
||||
implementation("io.karn:khttp-android:0.1.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue