diff --git a/src/pt/cinevision/AndroidManifest.xml b/src/pt/cinevision/AndroidManifest.xml new file mode 100644 index 000000000..e3fcf1e84 --- /dev/null +++ b/src/pt/cinevision/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/src/pt/cinevision/build.gradle b/src/pt/cinevision/build.gradle new file mode 100644 index 000000000..8805dd694 --- /dev/null +++ b/src/pt/cinevision/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'CineVision' + pkgNameSuffix = 'pt.cinevision' + extClass = '.CineVision' + extVersionCode = 1 + libVersion = '13' + containsNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/cinevision/res/mipmap-hdpi/ic_launcher.png b/src/pt/cinevision/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..aefc95764 Binary files /dev/null and b/src/pt/cinevision/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/cinevision/res/mipmap-mdpi/ic_launcher.png b/src/pt/cinevision/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..eb699a602 Binary files /dev/null and b/src/pt/cinevision/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/cinevision/res/mipmap-xhdpi/ic_launcher.png b/src/pt/cinevision/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..4ff41dcf9 Binary files /dev/null and b/src/pt/cinevision/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/cinevision/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/cinevision/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..1efa8bdbc Binary files /dev/null and b/src/pt/cinevision/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/cinevision/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/cinevision/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..e64b31dfb Binary files /dev/null and b/src/pt/cinevision/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/cinevision/res/web_hi_res_512.png b/src/pt/cinevision/res/web_hi_res_512.png new file mode 100644 index 000000000..74e07da29 Binary files /dev/null and b/src/pt/cinevision/res/web_hi_res_512.png differ diff --git a/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVConstants.kt b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVConstants.kt new file mode 100644 index 000000000..6ca905925 --- /dev/null +++ b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVConstants.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.animeextension.pt.cinevision + +object CVConstants { + 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 PREFIX_SEARCH = "slug:" + + const val PREFERRED_QUALITY = "preferred_quality" + const val DEFAULT_QUALITY = "720p" + val QUALITY_LIST = arrayOf("480p", "720p") + + const val PREFERRED_LANGUAGE = "preferred_language" + const val DEFAULT_LANGUAGE = "Legendado" + val LANGUAGE_LIST = arrayOf("Legendado", "Dublado") +} diff --git a/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVFilters.kt b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVFilters.kt new file mode 100644 index 000000000..00474647c --- /dev/null +++ b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVFilters.kt @@ -0,0 +1,63 @@ +package eu.kanade.tachiyomi.animeextension.pt.cinevision + +import eu.kanade.tachiyomi.animesource.model.AnimeFilter +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList + +object CVFilters { + + 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", CVFiltersData.genres) + + val filterList = AnimeFilterList( + AnimeFilter.Header(CVFiltersData.IGNORE_SEARCH_MSG), + GenreFilter() + ) + + data class FilterSearchParams( + val genre: String = "" + ) + + internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams { + return FilterSearchParams(filters.asQueryPart()) + } + + private object CVFiltersData { + + 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-online-1"), + Pair("Animação", "animacao-online"), + Pair("Aventura", "aventura-online"), + Pair("Comédia", "comedia-online"), + Pair("Crime", "crime-online"), + Pair("Documentário", "documentario-online"), + Pair("Drama", "drama-online"), + Pair("Família", "familia-online"), + Pair("Fantasia", "fantasia-online"), + Pair("Faroeste", "faroeste-online"), + Pair("Ficção científica", "ficcao-cientifica-online"), + Pair("Guerra", "guerra-online"), + Pair("Mistério", "misterio-online"), + Pair("Música", "musica-online"), + Pair("Romance", "romance-online"), + Pair("Terror", "terror-online"), + Pair("Thriller", "thriller-online") + ) + } +} diff --git a/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVUrlActivity.kt b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVUrlActivity.kt new file mode 100644 index 000000000..545684bdf --- /dev/null +++ b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CVUrlActivity.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.animeextension.pt.cinevision + +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://cinevisionv5.online.com/serie/ intents + * and redirects them to the main Aniyomi process. + */ +class CVUrlActivity : Activity() { + + private val TAG = "CVUrlActivity" + + 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 = CVConstants.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/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CineVision.kt b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CineVision.kt new file mode 100644 index 000000000..0bf6a04a1 --- /dev/null +++ b/src/pt/cinevision/src/eu/kanade/tachiyomi/animeextension/pt/cinevision/CineVision.kt @@ -0,0 +1,333 @@ +package eu.kanade.tachiyomi.animeextension.pt.cinevision + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.pt.cinevision.extractors.VidmolyExtractor +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.asObservableSuccess +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +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 +import java.text.SimpleDateFormat +import java.util.Locale + +class CineVision : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "CineVision" + + override val baseUrl = "https://cinevisionv5.online" + + 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", CVConstants.ACCEPT_LANGUAGE) + .add("User-Agent", CVConstants.USER_AGENT) + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + // ============================== Popular =============================== + override fun popularAnimeSelector(): String = "article.w_item_b > a" + + override fun popularAnimeRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + val img = element.selectFirst("img") + val url = element.selectFirst("a")?.attr("href") ?: element.attr("href") + anime.setUrlWithoutDomain(url) + anime.title = img.attr("alt") + anime.thumbnail_url = img.attr("data-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")) + episode.date_upload = element.selectFirst("span.date")?.text()?.toDate() ?: 0L + return episode + } + + // ============================ Video Links ============================= + + override fun videoListParse(response: Response): List