diff --git a/src/pt/animeshouse/AndroidManifest.xml b/src/pt/animeshouse/AndroidManifest.xml new file mode 100644 index 000000000..a71166334 --- /dev/null +++ b/src/pt/animeshouse/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/src/pt/animeshouse/build.gradle b/src/pt/animeshouse/build.gradle new file mode 100644 index 000000000..62206a521 --- /dev/null +++ b/src/pt/animeshouse/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Animes House' + pkgNameSuffix = 'pt.animeshouse' + extClass = '.AnimesHouse' + extVersionCode = 1 + libVersion = '13' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..9e65041b8 Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..d7fe121ea Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cb454dedf Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..27e20c734 Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..214c5f342 Binary files /dev/null and b/src/pt/animeshouse/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt new file mode 100644 index 000000000..2f5878233 --- /dev/null +++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHConstants.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.animeextension.pt.animeshouse + +object AHConstants { + const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7" + const val USER_AGENT = "Mozilla/5.0 (Linux; Android 10; SM-A307GT Build/QP1A.190711.020;) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36" + const val MSG_ERR_BODY = "Erro ao obter dados do episódio." + const val PREFERRED_QUALITY = "preferred_quality" + const val DEFAULT_QUALITY = "720p" + const val PREFIX_SEARCH = "slug:" + val QUALITY_LIST = arrayOf("240p", "360p", "480p", "720p", "1080p") +} diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt new file mode 100644 index 000000000..163e07bd2 --- /dev/null +++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHFilters.kt @@ -0,0 +1,65 @@ +package eu.kanade.tachiyomi.animeextension.pt.animeshouse + +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList + +object AHFilters { + + open class QueryPartFilter( + displayName: String, + val vals: Array> + ) : AnimeFilter.Select( + displayName, + vals.map { it.first }.toTypedArray() + ) { + fun toQueryPart() = vals[state].second + } + + private inline fun AnimeFilterList.asQueryPart(): String { + return this.filterIsInstance().joinToString("") { + (it as QueryPartFilter).toQueryPart() + } + } + + class GenreFilter : QueryPartFilter("Gênero", AHFiltersData.genres) + + val filterList = AnimeFilterList( + AnimeFilter.Header(AHFiltersData.IGNORE_SEARCH_MSG), + GenreFilter() + ) + + data class FilterSearchParams( + val genre: String = "" + ) + + internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { + return FilterSearchParams(filters.asQueryPart()) + } + + private object AHFiltersData { + + const val IGNORE_SEARCH_MSG = "NOTA: O filtro por gênero será IGNORADO ao usar a pesquisa por nome." + + val genres = arrayOf( + Pair("Ação", "acao"), + Pair("Aventura", "aventura"), + Pair("Artes Marciais", "artes-marciais"), + Pair("Drama", "drama"), + Pair("Ecchi", "ecchi"), + Pair("Escolar", "escolar"), + Pair("Esporte", "esporte"), + Pair("Fantasia", "fantasia"), + Pair("Ficção Científica", "ficcao-cientifica"), + Pair("Harém", "harem"), + Pair("Mecha", "mecha"), + Pair("Mistério", "misterio"), + Pair("Psicológico", "psicologico"), + Pair("Romance", "romance"), + Pair("Seinen", "seinen"), + Pair("Shounen", "shounen"), + Pair("Slice Of Life", "slice-of-life"), + Pair("Sobrenatural", "sobrenatural"), + Pair("Superpoderes", "superpoderes") + ) + } +} diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt new file mode 100644 index 000000000..cd0451551 --- /dev/null +++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AHUrlActivity.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.animeextension.pt.animeshouse + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +/** + * Springboard that accepts https://animeshouse.net/anime/ intents + * and redirects them to the main Aniyomi process. + */ +class AHUrlActivity : Activity() { + + private val TAG = "AHUrlActivity" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val slug = pathSegments[1] + val searchQuery = AHConstants.PREFIX_SEARCH + slug + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.ANIMESEARCH" + putExtra("query", searchQuery) + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, e.toString()) + } + } else { + Log.e(TAG, "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +} diff --git a/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt new file mode 100644 index 000000000..c3172475a --- /dev/null +++ b/src/pt/animeshouse/src/eu/kanade/tachiyomi/animeextension/pt/animeshouse/AnimesHouse.kt @@ -0,0 +1,317 @@ +package eu.kanade.tachiyomi.animeextension.pt.animeshouse + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EdifierExtractor +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.EmbedExtractor +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.GenericExtractor +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.JsUnpacker +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.McpExtractor +import eu.kanade.tachiyomi.animeextension.pt.animeshouse.extractors.MpFourDooExtractor +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.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.FormBody +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.lang.Exception + +class AnimesHouse : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "Animes House" + + override val baseUrl = "https://animeshouse.net" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client: OkHttpClient = network.client + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + .add("Accept-Language", AHConstants.ACCEPT_LANGUAGE) + .add("User-Agent", AHConstants.USER_AGENT) + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular =============================== + override fun popularAnimeSelector(): String = "div#featured-titles div.poster" + + override fun popularAnimeRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + val img = element.selectFirst("img") + anime.setUrlWithoutDomain(element.selectFirst("a").attr("href")) + anime.title = img.attr("alt") + anime.thumbnail_url = img.attr("src") + return anime + } + + override fun popularAnimeNextPageSelector() = throw Exception("not used") + + override fun popularAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + val animes = document.select(popularAnimeSelector()).map { element -> + popularAnimeFromElement(element) + } + return AnimesPage(animes, false) + } + + // ============================== Episodes ============================== + override fun episodeListSelector(): String = "ul.episodios > li" + + override fun episodeListParse(response: Response): List { + val doc = getRealDoc(response.asJsoup()) + val epList = doc.select(episodeListSelector()) + if (epList.size < 1) { + val episode = SEpisode.create() + episode.setUrlWithoutDomain(response.request.url.toString()) + episode.episode_number = 1F + episode.name = "Filme" + return listOf(episode) + } + return epList.reversed().map { episodeFromElement(it) } + } + + override fun episodeFromElement(element: Element): SEpisode { + val episode = SEpisode.create() + val origName = element.selectFirst("div.numerando").text() + + episode.episode_number = origName.substring(origName.indexOf("-") + 1) + .toFloat() + if ("Dub" in origName) 0.5F else 0F + episode.name = "Temp " + origName.replace(" - ", ": Ep ") + episode.setUrlWithoutDomain(element.selectFirst("a").attr("href")) + return episode + } + + // ============================ Video Links ============================= + private fun getPlayerUrl(player: Element): String { + val body = FormBody.Builder() + .add("action", "doo_player_ajax") + .add("post", player.attr("data-post")) + .add("nume", player.attr("data-nume")) + .add("type", player.attr("data-type")) + .build() + val doc = client.newCall( + POST("$baseUrl/wp-admin/admin-ajax.php", headers, body) + ) + .execute() + .asJsoup() + val iframe = doc.selectFirst("iframe") + return iframe.attr("src") + } + + override fun videoListParse(response: Response): List