Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,8 @@
ext {
extName = 'haho.moe'
extClass = '.HahoMoe'
extVersionCode = 10
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,216 @@
package eu.kanade.tachiyomi.animeextension.en.hahomoe
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.Locale
class HahoMoe : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "haho.moe"
override val baseUrl = "https://haho.moe"
override val lang = "en"
override val supportsLatest = true
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
init {
// Save the cookie that enables thumbnails in results (popular, latest, search...)
val httpUrl = baseUrl.toHttpUrl()
val cookie = Cookie.parse(httpUrl, "loop-view=thumb")!!
client.cookieJar.saveFromResponse(httpUrl, listOf(cookie))
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime?s=vdy-d&page=$page")
override fun popularAnimeSelector() = "ul.anime-loop.loop > li > a"
override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
setUrlWithoutDomain(element.attr("href") + "?s=srt-d")
title = element.selectFirst("div.label > span, div span.thumb-title")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
override fun popularAnimeNextPageSelector() = "ul.pagination li.page-item a[rel=next]"
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/anime?s=rel-d&page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
// =============================== Search ===============================
override fun getFilterList() = HahoMoeFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val (includedTags, excludedTags, orderBy, ordering) = HahoMoeFilters.getSearchParameters(filters)
val httpQuery = buildString {
if (query.isNotBlank()) append(query.trim())
if (includedTags.isNotEmpty()) {
append(includedTags.joinToString(" genre:", prefix = " genre:"))
}
if (excludedTags.isNotEmpty()) {
append(excludedTags.joinToString(" -genre:", prefix = " -genre:"))
}
}.let { URLEncoder.encode(it, "UTF-8") }
return GET("$baseUrl/anime?page=$page&s=$orderBy$ordering&q=$httpQuery")
}
override fun searchAnimeSelector() = popularAnimeSelector()
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
setUrlWithoutDomain(document.location())
thumbnail_url = document.selectFirst("img.cover-image.img-thumbnail")?.absUrl("src")
title = document.selectFirst("li.breadcrumb-item.active")!!.text()
genre = document.select("li.genre span.value, div.genre-tree ul > li > a").joinToString { it.text() }
description = document.selectFirst("div.card-body")?.text()
author = document.select("li.production span.value").joinToString { it.text() }
artist = document.selectFirst("li.group span.value")?.text()
status = parseStatus(document.selectFirst("li.status span.value")?.text())
}
private fun parseStatus(statusString: String?): Int {
return when (statusString) {
"Ongoing" -> SAnime.ONGOING
"Completed" -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
}
// ============================== Episodes ==============================
override fun episodeListSelector() = "ul.episode-loop > li > a"
private fun episodeNextPageSelector() = popularAnimeNextPageSelector()
override fun episodeListParse(response: Response): List<SEpisode> {
var doc = response.asJsoup()
return buildList {
do {
if (isNotEmpty()) {
val url = doc.selectFirst(episodeNextPageSelector())!!.absUrl("href")
doc = client.newCall(GET(url)).execute().asJsoup()
}
doc.select(episodeListSelector())
.map(::episodeFromElement)
.also(::addAll)
} while (doc.selectFirst(episodeNextPageSelector()) != null)
sortByDescending { it.episode_number }
}
}
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
val episodeNumberString = element.selectFirst("div.episode-number, div.episode-slug")?.text() ?: "Episode"
episode_number = episodeNumberString.removePrefix("Episode ").toFloatOrNull() ?: 1F
val title = element.selectFirst("div.episode-label, div.episode-title")?.text()
?.takeUnless { it.equals("No Title", true) }
?.let { ": $it" }
.orEmpty()
name = episodeNumberString + title
date_upload = element.selectFirst("div.date")?.text().orEmpty().toDate()
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val iframe = document.selectFirst("iframe")!!.attr("src")
val newHeaders = headersBuilder().set("referer", document.location()).build()
val iframeResponse = client.newCall(GET(iframe, newHeaders)).execute()
.asJsoup()
return iframeResponse.select(videoListSelector()).map(::videoFromElement)
}
override fun videoListSelector() = "source"
override fun videoFromElement(element: Element): Video {
return Video(element.attr("src"), element.attr("title"), element.attr("src"))
}
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_VALUES
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
private fun String.toDate(): Long {
val fixedDate = trim().replace(DATE_REGEX, "").replace("'", "")
return runCatching { DATE_FORMATTER.parse(fixedDate)?.time }
.getOrNull() ?: 0L
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith(
compareBy { it.quality.contains(quality) },
).reversed()
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("dd 'of' MMM, yyyy", Locale.ENGLISH)
}
private val DATE_REGEX by lazy { Regex("(?<=\\d)(st|nd|rd|th)") }
private const val PREF_QUALITY_KEY = "pref_quality_key"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "720p"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
private val PREF_QUALITY_VALUES = PREF_QUALITY_ENTRIES
}
}

View file

@ -0,0 +1,740 @@
package eu.kanade.tachiyomi.animeextension.en.hahomoe
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilter.TriState
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import java.util.Locale
object HahoMoeFilters {
open class TriStateFilterList(name: String, values: List<TriFilterVal>) : AnimeFilter.Group<TriState>(name, values)
class TriFilterVal(name: String) : TriState(name)
private inline fun <reified R> AnimeFilterList.getFirst(): R = first { it is R } as R
private inline fun <reified R> AnimeFilterList.parseTriFilter(): List<List<String>> {
return (getFirst<R>() as TriStateFilterList).state
.filterNot { it.isIgnored() }
.map { filter -> filter.state to "\"${filter.name.lowercase(Locale.US)}\"" }
.groupBy { it.first } // group by state
.let { dict ->
val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
listOf(included, excluded)
}
}
class TagFilter : TriStateFilterList("Tags", HahoMoeFiltersData.TAGS.map(::TriFilterVal))
class SortFilter : AnimeFilter.Sort(
"Sort",
HahoMoeFiltersData.ORDERS.map { it.first }.toTypedArray(),
Selection(0, true),
)
val FILTER_LIST get() = AnimeFilterList(
TagFilter(),
SortFilter(),
)
data class FilterSearchParams(
val includedTags: List<String> = emptyList(),
val excludedListedTags: List<String> = emptyList(),
val orderBy: String = "az-",
val ordering: String = "a", // ascending
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
val (included, excluded) = filters.parseTriFilter<TagFilter>()
val (orderBy, order) = filters.getFirst<SortFilter>().state?.let {
val order = if (it.ascending) "a" else "d"
val orderBy = HahoMoeFiltersData.ORDERS[it.index].second
Pair(orderBy, order)
} ?: Pair("az-", "d")
return FilterSearchParams(included, excluded, orderBy, order)
}
private object HahoMoeFiltersData {
val TAGS = listOf(
"3D CG animation",
"absurdist humour",
"action",
"action game",
"adapted into Japanese movie",
"adapted into JDrama",
"adapted into other media",
"adults are useless",
"adventure",
"Africa",
"age difference romance",
"ahegao",
"air force",
"Akihabara",
"alcohol",
"alien",
"alien invasion",
"all-boys school",
"all-girls school",
"alternating animation style",
"alternative past",
"alternative present",
"Americas",
"amnesia",
"anal",
"anal fingering",
"anal pissing",
"android",
"angel",
"angst",
"animal abuse",
"animal protagonist",
"Animerama",
"anthropomorphism",
"aphrodisiac",
"archery",
"Asia",
"ass-kicking girls",
"assjob",
"association football",
"attempted rape",
"aunt-nephew incest",
"autofellatio",
"autumn",
"bad cooking",
"Bakumatsu - Meiji Period",
"baseball",
"basketball",
"BDSM",
"be careful what you wish for",
"bestiality",
"betrayal",
"bishoujo",
"bishounen",
"bitter-sweet",
"black humour",
"blackmail",
"board games",
"body and host",
"body exchange",
"body takeover",
"bondage",
"borderline porn",
"boxing",
"boy meets girl",
"brainwashing",
"branching story",
"breast expansion",
"breast fondling",
"breasts",
"brother-sister incest",
"Buddhism",
"bukkake",
"bullying",
"call my name",
"calling your attacks",
"car crash",
"cast",
"castaway",
"catholic school",
"censored uncensored version",
"cervix penetration",
"CG collection",
"CGI",
"cheating",
"chikan",
"child abuse",
"China",
"Christianity",
"classical music",
"cockring",
"collateral damage",
"colour coded",
"combat",
"comedy",
"coming of age",
"competition",
"conspiracy",
"contemporary fantasy",
"content indicators",
"contraband",
"cooking",
"cops",
"corrupt church",
"corrupt nobility",
"cosplaying",
"countryside",
"cram school",
"creampie",
"crime",
"cross-dressing",
"cum play",
"cum swapping",
"cunnilingus",
"curse",
"cyberpunk",
"cybersex",
"cyborg",
"daily life",
"damsel in distress",
"dancing",
"dark",
"dark atmosphere",
"dark elf",
"dark fantasy",
"dark-skinned girl",
"death",
"defeat means friendship",
"deflowering",
"deity",
"delinquent",
"dementia",
"demon",
"demon hunt",
"demonic power",
"DESCRIPTION NEEDS IMPROVEMENT",
"desert",
"despair",
"detective",
"dildos - vibrators",
"disaster",
"discontinued",
"disturbing",
"divorce",
"doggy style",
"dominatrix",
"double fellatio",
"double penetration",
"double-sided dildo",
"doujin",
"dragon",
"drastic change of life",
"dreams",
"dreams and reality",
"drugs",
"dungeon",
"dutch wife",
"dynamic",
"dysfunctional family",
"dystopia",
"Earth",
"earthquake",
"eating of humans",
"ecchi",
"Egypt",
"elements",
"elf",
"emotions awaken superpowers",
"ending",
"enema",
"Engrish",
"enjo-kousai",
"enjoyable rape",
"entertainment industry",
"episodic",
"erotic asphyxiation",
"erotic game",
"erotic torture",
"Europe",
"European stylised",
"everybody dies",
"everybody has sex",
"evil military",
"excessive censoring",
"exhibitionism",
"exorcism",
"experimental animation",
"extrasensory perception",
"eye penetration",
"faceless background characters",
"facesitting",
"facial distortion",
"fairy",
"fake relationship",
"family life",
"family without mother",
"fantasy",
"father-daughter incest",
"felching",
"fellatio",
"female protagonist",
"female rapes female",
"female student",
"female teacher",
"femdom",
"feminism",
"fetishes",
"feudal warfare",
"FFM threesome",
"fictional location",
"fighting",
"fingering",
"fire",
"first love",
"fishing",
"fisting",
"foot fetish",
"footage reuse",
"footjob",
"forbidden love",
"foreskin sex",
"foursome",
"France",
"French kiss",
"friendship",
"full HD version available",
"funny expressions",
"futa x female",
"futa x futa",
"futa x male",
"futanari",
"future",
"Gainax bounce",
"game",
"gang bang",
"gang rape",
"gangs",
"gender bender",
"genetic modification",
"Germany",
"ghost",
"ghost hunting",
"giant insects",
"gigantic breasts",
"girl rapes girl",
"girly tears",
"glory hole",
"go",
"god is a girl",
"gokkun",
"golden shower",
"gore",
"grandiose displays of wealth",
"Greek mythology",
"groping",
"group love",
"gunfights",
"guro",
"gymnastics",
"half-length episodes",
"handjob",
"happy ending",
"harem",
"heaven",
"hell",
"henshin",
"heroic sacrifice",
"hidden agenda",
"hidden vibrator",
"high fantasy",
"high school",
"historical",
"Hong Kong",
"horny nosebleed",
"horror",
"hospital",
"hostage situation",
"housewives",
"human cannibalism",
"human enhancement",
"human experimentation",
"human sacrifice",
"human-android love",
"humanoid alien",
"hyperspace mallet",
"i got a crush on you",
"ice skating",
"idol",
"immortality",
"imperial stormtrooper marksmanship academy",
"important haircut",
"impregnation",
"impregnation with larvae",
"improbable physics",
"in medias res",
"incest",
"India",
"infidelity",
"Injuu Hentai Series",
"inter-dimensional schoolgirl",
"intercrural sex",
"internal shots",
"isekai",
"island",
"Japan",
"Japanese mythology",
"jealousy",
"Journey to the West",
"just as planned",
"juujin",
"kamikaze",
"kendo",
"kidnapping",
"killing criminals",
"Korea",
"lactation",
"large breasts",
"law and order",
"library",
"light-hearted",
"lingerie",
"live-action imagery",
"loli",
"long episodes",
"love between enemies",
"love polygon",
"macabre",
"mafia",
"magic",
"magic circles",
"magic weapons",
"magical girl",
"maid",
"main character dies",
"maintenance tags",
"male protagonist",
"male rape victim",
"mammary intercourse",
"manga",
"manipulation",
"martial arts",
"massacre",
"master-servant relationship",
"master-slave relation",
"masturbation",
"mecha",
"mechanical tentacle",
"medium awareness",
"merchandising show",
"mermaid",
"meta tags",
"Middle East",
"middle school",
"military",
"military is useless",
"mind fuck",
"misunderstanding",
"MMF threesome",
"MMM threesome",
"molestation",
"money",
"monster of the week",
"mother-daughter incest",
"mother-son incest",
"movie",
"multi-anime projects",
"multi-segment episodes",
"multiple couples",
"murder",
"murder of family members",
"music",
"musical band",
"mutation",
"mutilation",
"mystery",
"mythology",
"Nagasaki",
"narration",
"navel fuck",
"navy",
"nearly almighty protagonist",
"necrophilia",
"nervous breakdown",
"netorare",
"netori",
"new",
"ninja",
"nipple penetration",
"non-linear",
"Norse mythology",
"nostril hook",
"not for kids",
"novel",
"nudity",
"nun",
"nurse",
"nurse office",
"nyotaimori",
"occult",
"occupation and career",
"ocean",
"off-model animation",
"office lady",
"older female younger male",
"omnibus format",
"onahole",
"One Thousand and One Nights",
"onmyoudou",
"open-ended",
"oral",
"orgasm denial",
"orgy",
"origin",
"original work",
"otaku culture",
"other planet",
"out-of-body experience",
"outdoor sex",
"oyakodon",
"painting",
"pantsu",
"panty theft",
"pantyjob",
"paper clothes",
"parallel world",
"parasite",
"parental abandonment",
"parody",
"parricide",
"past",
"pegging",
"performance",
"photographic backgrounds",
"photography",
"pillory",
"piloted robot",
"pirate",
"place",
"plot continuity",
"plot twists",
"plot with porn",
"point of view",
"police",
"police are useless",
"pornography",
"post-apocalyptic",
"poverty",
"power corrupts",
"power suit",
"predominantly adult cast",
"predominantly female cast",
"predominantly male cast",
"pregnant sex",
"present",
"prison",
"promise",
"prostate massage",
"prostitution",
"proxy battles",
"psychoactive drugs",
"psychological",
"psychological manipulation",
"psychological sexual abuse",
"public sex",
"pussy sandwich",
"rape",
"real-world location",
"rebellion",
"recycled animation",
"red-light district",
"reincarnation",
"religion",
"remastered version available",
"restaurant",
"revenge",
"reverse harem",
"reverse spitroast",
"reverse trap",
"rimming",
"rivalry",
"robot",
"romance",
"rotten world",
"RPG",
"rugby",
"running gag",
"safer sex",
"sakura",
"samurai",
"scat",
"school clubs",
"school dormitory",
"school for the rich elite",
"school life",
"science fiction",
"scissoring",
"season",
"Secret Anima",
"Secret Anima Series",
"seiyuu",
"self-parody",
"setting",
"sex",
"sex change",
"sex doll",
"sex tape",
"sex toys",
"sex while on the phone",
"sexual abuse",
"sexual fantasies",
"shibari",
"Shinjuku",
"shinsengumi",
"shipboard",
"short episodes",
"short movie",
"short story collection",
"shota",
"shoujo ai",
"shounen ai",
"sibling rivalry",
"sibling yin yang",
"sister-sister incest",
"sixty-nine",
"skimpy clothing",
"slapstick",
"slavery",
"sleeping sex",
"slide show animation",
"slow when it comes to love",
"slums",
"small breasts",
"soapland",
"social class issues",
"social commentary",
"softball",
"some weird shit goin` on",
"South Korean production",
"space",
"space pirates",
"space travel",
"spacing out",
"spanking",
"special squads",
"speculative fiction",
"spellcasting",
"spirit realm",
"spirits",
"spiritual powers",
"spitroast",
"sports",
"spring",
"squirting",
"stand-alone movie",
"stereotypes",
"stomach bulge",
"stomach stretch",
"storytelling",
"strapon",
"strappado",
"strappado bondage",
"strong female lead",
"strong male lead",
"student government",
"submission",
"succubus",
"sudden girlfriend appearance",
"sudden naked girl appearance",
"suicide",
"sumata",
"summer",
"summoning",
"super deformed",
"super power",
"superhero",
"surreal",
"survival",
"suspension bondage",
"swimming",
"swordplay",
"table tennis",
"tales",
"tank warfare",
"teacher x student",
"technical aspects",
"tennis",
"tentacle",
"the arts",
"the power of love",
"themes",
"thievery",
"thigh sex",
"Three Kingdoms",
"threesome",
"threesome with sisters",
"thriller",
"throat fucking",
"time",
"time loop",
"time travel",
"Tokugawa period",
"Tokyo",
"torture",
"tournament",
"track and field",
"tragedy",
"tragic beginning",
"training",
"transforming craft",
"transforming weapons",
"trap",
"trapped",
"triple penetration",
"tropes",
"tsunami",
"TV censoring",
"twincest",
"ukiyo-e",
"uncle-niece incest",
"undead",
"under one roof",
"unexpected inheritance",
"uniform fetish",
"unintentional comedy",
"United States",
"university",
"unrequited love",
"unrequited shounen ai",
"unsorted",
"urethra penetration",
"urination",
"urophagia",
"vampire",
"Vanilla Series",
"video game development",
"violence",
"violent retribution for accidental infringement",
"virtual world",
"visible aura",
"visual novel",
"volleyball",
"voyeurism",
"waitress",
"wakamezake",
"wardrobe malfunction",
"water sex",
"watercolour style",
"wax play",
"Weekly Shounen Jump",
"whip",
"whipping",
"window fuck",
"winter",
"wooden horse",
"working life",
"world domination",
"World War II",
"wrestling",
"yaoi",
"Yokohama",
"youji play",
"yuri",
"zero to hero",
"zombie",
)
val ORDERS = arrayOf(
Pair("Alphabetical", "az-"),
Pair("Released", "rel-"),
Pair("Added", "add-"),
Pair("Bookmarked", "bkm-"),
Pair("Rated", "rtg-"),
Pair("Popular", "vtt-"),
Pair("Popular Today", "vdy-"),
Pair("Popular This Week", "vwk-"),
Pair("Popular This Month", "vmt-"),
Pair("Popular This Year", "vyr-"),
)
}
}