diff --git a/src/all/missav/AndroidManifest.xml b/src/all/missav/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/all/missav/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/all/missav/build.gradle b/src/all/missav/build.gradle new file mode 100644 index 0000000000..2b684e3aa8 --- /dev/null +++ b/src/all/missav/build.gradle @@ -0,0 +1,19 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +ext { + extName = 'MissAV' + pkgNameSuffix = 'all.missav' + extClass = '.MissAV' + extVersionCode = 1 + containsNsfw = true +} + +dependencies { + implementation(project(':lib-unpacker')) + implementation(project(':lib-playlist-utils')) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/missav/res/mipmap-hdpi/ic_launcher.png b/src/all/missav/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0310483009 Binary files /dev/null and b/src/all/missav/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/missav/res/mipmap-mdpi/ic_launcher.png b/src/all/missav/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a76872ae96 Binary files /dev/null and b/src/all/missav/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/missav/res/mipmap-xhdpi/ic_launcher.png b/src/all/missav/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..59d6dfdb77 Binary files /dev/null and b/src/all/missav/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/missav/res/mipmap-xxhdpi/ic_launcher.png b/src/all/missav/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..09573e1541 Binary files /dev/null and b/src/all/missav/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/missav/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/missav/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ca73427866 Binary files /dev/null and b/src/all/missav/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/missav/res/web_hi_res_512.png b/src/all/missav/res/web_hi_res_512.png new file mode 100644 index 0000000000..2b9568fd79 Binary files /dev/null and b/src/all/missav/res/web_hi_res_512.png differ diff --git a/src/all/missav/src/eu/kanade/tachiyomi/animeextension/all/missav/MissAV.kt b/src/all/missav/src/eu/kanade/tachiyomi/animeextension/all/missav/MissAV.kt new file mode 100644 index 0000000000..aa7ef09a72 --- /dev/null +++ b/src/all/missav/src/eu/kanade/tachiyomi/animeextension/all/missav/MissAV.kt @@ -0,0 +1,165 @@ +package eu.kanade.tachiyomi.animeextension.all.missav + +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.AnimesPage +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.AnimeHttpSource +import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils +import eu.kanade.tachiyomi.lib.unpacker.Unpacker +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MissAV : AnimeHttpSource(), ConfigurableAnimeSource { + + override val name = "MissAV" + + override val lang = "all" + + override val baseUrl = "https://missav.com" + + override val supportsLatest = true + + override val client = network.cloudflareClient + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + private val playlistExtractor by lazy { + PlaylistUtils(client, headers) + } + + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun popularAnimeRequest(page: Int) = + GET("$baseUrl/en/today-hot?page=$page", headers) + + override fun popularAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + + val entries = document.select("div.thumbnail").map { element -> + SAnime.create().apply { + element.select("a.text-secondary").let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + thumbnail_url = element.selectFirst("img")?.attr("abs:data-src") + } + } + + val hasNextPage = document.selectFirst("a[rel=next]") != null + + return AnimesPage(entries, hasNextPage) + } + + override fun latestUpdatesRequest(page: Int) = + GET("$baseUrl/en/new?page=$page", headers) + + override fun latestUpdatesParse(response: Response) = popularAnimeParse(response) + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder().apply { + val genre = filters.firstInstanceOrNull()?.selected + if (query.isNotEmpty()) { + addEncodedPathSegments("en/search") + addPathSegment(query.trim()) + } else if (genre != null) { + addEncodedPathSegments(genre) + } else { + addEncodedPathSegments("en/new") + } + filters.firstInstanceOrNull()?.selected?.let { + addQueryParameter("sort", it) + } + addQueryParameter("page", page.toString()) + }.build().toString() + + return GET(url, headers) + } + + override fun getFilterList() = getFilters() + + override fun searchAnimeParse(response: Response) = popularAnimeParse(response) + + override fun animeDetailsParse(response: Response): SAnime { + val document = response.asJsoup() + + return SAnime.create().apply { + title = document.select("h1.text-base").text() + genre = document.select("div.text-secondary > a[href*=/genres/]").joinToString { it.text() } + description = document.select("div.mb-1").text() + author = document.select("div.text-secondary > a[href*=/makers/]").joinToString { it.text() } + artist = document.select("div.text-secondary > a[href*=/actresses/]").joinToString { it.text() } + status = SAnime.COMPLETED + thumbnail_url = document.select("video.player").attr("abs:data-poster") + } + } + + override fun fetchEpisodeList(anime: SAnime): Observable> { + return Observable.just( + listOf( + SEpisode.create().apply { + url = anime.url + name = "Episode" + }, + ), + ) + } + + override fun videoListParse(response: Response): List