diff --git a/src/pt/flixei/AndroidManifest.xml b/src/pt/flixei/AndroidManifest.xml new file mode 100644 index 000000000..4bdf041ec --- /dev/null +++ b/src/pt/flixei/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/src/pt/flixei/build.gradle b/src/pt/flixei/build.gradle new file mode 100644 index 000000000..f7a57c451 --- /dev/null +++ b/src/pt/flixei/build.gradle @@ -0,0 +1,19 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} + +ext { + extName = 'Flixei' + pkgNameSuffix = 'pt.flixei' + extClass = '.Flixei' + extVersionCode = 1 +} + +dependencies { + implementation(project(":lib-streamtape-extractor")) + implementation(project(":lib-mixdrop-extractor")) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/flixei/res/mipmap-hdpi/ic_launcher.png b/src/pt/flixei/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..61d07595c Binary files /dev/null and b/src/pt/flixei/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/flixei/res/mipmap-mdpi/ic_launcher.png b/src/pt/flixei/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..8b2f31dad Binary files /dev/null and b/src/pt/flixei/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/flixei/res/mipmap-xhdpi/ic_launcher.png b/src/pt/flixei/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..58738047a Binary files /dev/null and b/src/pt/flixei/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/flixei/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/flixei/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..0dee2e438 Binary files /dev/null and b/src/pt/flixei/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/flixei/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/flixei/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..678679b54 Binary files /dev/null and b/src/pt/flixei/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/flixei/src/eu/kanade/tachiyomi/animeextension/pt/flixei/Flixei.kt b/src/pt/flixei/src/eu/kanade/tachiyomi/animeextension/pt/flixei/Flixei.kt new file mode 100644 index 000000000..4d4d85d48 --- /dev/null +++ b/src/pt/flixei/src/eu/kanade/tachiyomi/animeextension/pt/flixei/Flixei.kt @@ -0,0 +1,363 @@ +package eu.kanade.tachiyomi.animeextension.pt.flixei + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.AnimeDto +import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.ApiResultsDto +import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.EpisodeDto +import eu.kanade.tachiyomi.animeextension.pt.flixei.dto.PlayersDto +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.lib.mixdropextractor.MixDropExtractor +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +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 uy.kohesive.injekt.injectLazy + +class Flixei : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "Flixei" + + override val baseUrl = "https://flixei.com" + + override val lang = "pt-BR" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int): Request { + val body = "slider=3".toFormBody() + return POST("$baseUrl/includes/ajax/home.php", body = body) + } + + override fun popularAnimeParse(response: Response): AnimesPage { + val results = response.parseAs>() + val animes = results.items.values.map(::parseAnimeFromObject) + return AnimesPage(animes, false) + } + + private fun parseAnimeFromObject(anime: AnimeDto): SAnime { + return SAnime.create().apply { + title = anime.title + setUrlWithoutDomain("/assistir/filme/${anime.url}/online/gratis") + thumbnail_url = "$baseUrl/content/movies/posterPt/185/${anime.id}.webp" + } + } + + override fun popularAnimeFromElement(element: Element): SAnime { + throw UnsupportedOperationException("Not used.") + } + + override fun popularAnimeNextPageSelector(): String? { + throw UnsupportedOperationException("Not used.") + } + + override fun popularAnimeSelector(): String { + throw UnsupportedOperationException("Not used.") + } + + // ============================== Episodes ============================== + private fun getSeasonEps(seasonElement: Element): List { + val id = seasonElement.attr("data-load-episodes") + val sname = seasonElement.text() + val body = "getEpisodes=$id".toFormBody() + val response = client.newCall(POST("$EMBED_WAREZ_URL/serieAjax.php", body = body)).execute() + val episodes = response.parseAs>().items.values.map { + SEpisode.create().apply { + name = "Temp $sname: Ep ${it.name}" + episode_number = it.name.toFloatOrNull() ?: 0F + url = it.id + } + } + return episodes + } + + override fun episodeListParse(response: Response): List { + val docUrl = response.asJsoup().selectFirst("div#playButton")!! + .attr("onclick") + .substringAfter("'") + .substringBefore("'") + return if (response.request.url.toString().contains("/serie/")) { + client.newCall(GET(docUrl)).execute() + .asJsoup() + .select("div#seasons div.item[data-load-episodes]") + .flatMap(::getSeasonEps) + .reversed() + } else { + listOf( + SEpisode.create().apply { + name = "Filme" + episode_number = 1F + url = "$EMBED_WAREZ_URL/filme/" + docUrl.substringAfter("=") + }, + ) + } + } + override fun episodeFromElement(element: Element): SEpisode { + throw UnsupportedOperationException("Not used.") + } + + override fun episodeListSelector(): String { + throw UnsupportedOperationException("Not used.") + } + + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document): SAnime { + return SAnime.create().apply { + setUrlWithoutDomain(document.location()) + thumbnail_url = document.selectFirst("meta[property=og:image]")!!.attr("content") + val container = document.selectFirst("div.moviePresent")!! + with(container) { + title = selectFirst("h2.tit")!!.text() + genre = select("div.genres > span").eachText().joinToString() + author = getInfo("Diretor") + artist = getInfo("Produtoras") + description = buildString { + selectFirst("p")?.text()?.let { append(it + "\n\n") } + getInfo("Título")?.let { append("Título original: $it\n") } + getInfo("Serie de")?.let { append("ano: $it\n") } + getInfo("Elenco")?.let { append("Elenco: $it\n") } + getInfo("Qualidade")?.let { append("Qualidade: $it\n") } + } + } + } + } + + // ============================ Video Links ============================= + override fun videoListRequest(episode: SEpisode): Request { + val url = episode.url + return if (url.startsWith("https")) { + // Its an real url, maybe from a movie + GET(url, headers) + } else { + POST("$EMBED_WAREZ_URL/serieAjax.php", body = "getAudios=$url".toFormBody()) + } + } + + override fun videoListParse(response: Response): List