diff --git a/src/es/sololatino/build.gradle b/src/es/sololatino/build.gradle new file mode 100644 index 0000000000..5bfbd078f9 --- /dev/null +++ b/src/es/sololatino/build.gradle @@ -0,0 +1,20 @@ +ext { + extName = 'SoloLatino' + extClass = '.SoloLatino' + themePkg = 'dooplay' + baseUrl = 'https://sololatino.net' + overrideVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(":lib:streamwish-extractor")) + implementation(project(":lib:uqload-extractor")) + implementation(project(":lib:universal-extractor")) + implementation(project(':lib:streamhidevid-extractor')) + implementation(project(':lib:vidguard-extractor')) + implementation(project(':lib:voe-extractor')) + implementation(project(":lib:dood-extractor")) + implementation(project(':lib:cryptoaes')) +} \ No newline at end of file diff --git a/src/es/sololatino/res/mipmap-hdpi/ic_launcher.png b/src/es/sololatino/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..3425bd2cff Binary files /dev/null and b/src/es/sololatino/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/sololatino/res/mipmap-mdpi/ic_launcher.png b/src/es/sololatino/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c3b762c804 Binary files /dev/null and b/src/es/sololatino/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/sololatino/res/mipmap-xhdpi/ic_launcher.png b/src/es/sololatino/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c4050b52a9 Binary files /dev/null and b/src/es/sololatino/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/sololatino/res/mipmap-xxhdpi/ic_launcher.png b/src/es/sololatino/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ce5e048bc3 Binary files /dev/null and b/src/es/sololatino/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/sololatino/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/sololatino/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9a1cfd821a Binary files /dev/null and b/src/es/sololatino/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/sololatino/src/eu/kanade/tachiyomi/animeextension/es/sololatino/SoloLatino.kt b/src/es/sololatino/src/eu/kanade/tachiyomi/animeextension/es/sololatino/SoloLatino.kt new file mode 100644 index 0000000000..c99a1d2868 --- /dev/null +++ b/src/es/sololatino/src/eu/kanade/tachiyomi/animeextension/es/sololatino/SoloLatino.kt @@ -0,0 +1,498 @@ +package eu.kanade.tachiyomi.animeextension.es.sololatino + +import android.util.Base64 +import android.util.Log +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +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.lib.cryptoaes.CryptoAES +import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor +import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor +import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor +import eu.kanade.tachiyomi.lib.universalextractor.UniversalExtractor +import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor +import eu.kanade.tachiyomi.lib.vidguardextractor.VidGuardExtractor +import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor +import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.net.HttpURLConnection +import java.net.URL + +class SoloLatino : DooPlay( + "es", + "SoloLatino", + "https://sololatino.net", +) { + private val json by lazy { Json { ignoreUnknownKeys = true } } + + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/tendencias/page/$page") + + override fun popularAnimeSelector() = "article.item" + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/pelicula/estrenos/page/$page", headers) + + override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() + + override fun latestUpdatesSelector() = popularAnimeSelector() + + override fun popularAnimeFromElement(element: Element): SAnime { + return SAnime.create().apply { + val img = element.selectFirst("img")!! + val url = element.selectFirst("a")?.attr("href") ?: element.attr("href") + setUrlWithoutDomain(url) + title = img.attr("alt") + thumbnail_url = img.attr("data-srcset") + } + } + + override fun popularAnimeNextPageSelector(): String = "div.pagMovidy a" + + override fun episodeListParse(response: Response): List { + val doc = response.asJsoup() + val seasonList = doc.select("div#seasons div.se-c") + return if (seasonList.isEmpty()) { + SEpisode.create().apply { + setUrlWithoutDomain(doc.location()) + episode_number = 1F + name = episodeMovieText + }.let(::listOf) + } else { + seasonList.flatMap(::getSeasonEpisodes).reversed() + } + } + + override fun getSeasonEpisodes(season: Element): List { + val seasonName = season.attr("data-season") + return season.select("ul.episodios li").mapNotNull { element -> + runCatching { + episodeFromElement(element, seasonName) + }.onFailure { it.printStackTrace() }.getOrNull() + } + } + + override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException() + + override fun episodeFromElement(element: Element, seasonName: String): SEpisode { + return SEpisode.create().apply { + val epNum = element.selectFirst("div.numerando")?.text() + ?.trim() + ?.let { episodeNumberRegex.find(it)?.groupValues?.last() } ?: "0" + + val href = element.selectFirst("a[href]")!!.attr("href") + val episodeName = element.selectFirst("div.epst")?.text() ?: "Sin título" + + episode_number = epNum.toFloatOrNull() ?: 0F + date_upload = element.selectFirst("span.date")?.text()?.toDate() ?: 0L + + name = "T$seasonName - Episodio $epNum: $episodeName" + setUrlWithoutDomain(href) + } + } + + override fun videoListSelector() = "li.dooplay_player_option" + + override val episodeMovieText = "Película" + + override val episodeSeasonPrefix = "Temporada" + override val prefQualityTitle = "Calidad preferida" + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List