diff --git a/src/pt/anidong/AndroidManifest.xml b/src/pt/anidong/AndroidManifest.xml new file mode 100644 index 000000000..2970f0d26 --- /dev/null +++ b/src/pt/anidong/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/pt/anidong/build.gradle b/src/pt/anidong/build.gradle new file mode 100644 index 000000000..5a4f19762 --- /dev/null +++ b/src/pt/anidong/build.gradle @@ -0,0 +1,14 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} + +ext { + extName = 'AniDong' + pkgNameSuffix = 'pt.anidong' + extClass = '.AniDong' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/anidong/res/mipmap-hdpi/ic_launcher.png b/src/pt/anidong/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..50be877d9 Binary files /dev/null and b/src/pt/anidong/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/anidong/res/mipmap-mdpi/ic_launcher.png b/src/pt/anidong/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..b23fff3ab Binary files /dev/null and b/src/pt/anidong/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/anidong/res/mipmap-xhdpi/ic_launcher.png b/src/pt/anidong/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71ddb7b8d Binary files /dev/null and b/src/pt/anidong/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/anidong/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/anidong/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..720b33eba Binary files /dev/null and b/src/pt/anidong/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/anidong/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/anidong/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..a0cb538f7 Binary files /dev/null and b/src/pt/anidong/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/anidong/src/eu/kanade/tachiyomi/animeextension/pt/anidong/AniDong.kt b/src/pt/anidong/src/eu/kanade/tachiyomi/animeextension/pt/anidong/AniDong.kt new file mode 100644 index 000000000..da8f6d7ac --- /dev/null +++ b/src/pt/anidong/src/eu/kanade/tachiyomi/animeextension/pt/anidong/AniDong.kt @@ -0,0 +1,258 @@ +package eu.kanade.tachiyomi.animeextension.pt.anidong + +import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.EpisodeDto +import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.EpisodeListDto +import eu.kanade.tachiyomi.animeextension.pt.anidong.dto.SearchResultDto +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 kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.injectLazy + +class AniDong : ParsedAnimeHttpSource() { + + override val name = "AniDong" + + override val baseUrl = "https://anidong.net" + + override val lang = "pt-BR" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + private val apiHeaders by lazy { + headersBuilder() // sets user-agent + .add("Referer", baseUrl) + .add("x-requested-with", "XMLHttpRequest") + .build() + } + + // ============================== Popular =============================== + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.attr("href")) + title = element.attr("title") + thumbnail_url = element.selectFirst("img")?.attr("src") + } + + override fun popularAnimeNextPageSelector() = null + override fun popularAnimeRequest(page: Int) = GET(baseUrl) + override fun popularAnimeSelector() = "article.top10_animes_item > a" + + // ============================== Episodes ============================== + override fun episodeFromElement(element: Element): SEpisode { + throw UnsupportedOperationException("Not used.") + } + + override fun episodeListSelector(): String { + throw UnsupportedOperationException("Not used.") + } + + override fun episodeListParse(response: Response): List { + val doc = getRealDoc(response.asJsoup()) + + val id = doc.selectFirst("link[rel=shortlink]")!!.attr("href").substringAfter("=") + val body = FormBody.Builder() + .add("action", "show_videos") + .add("anime_id", id) + .build() + + val res = client.newCall(POST("$baseUrl/api", headers = apiHeaders, body = body)).execute() + val data = json.decodeFromString(res.body.string()) + + return buildList { + data.episodes.forEach { add(episodeFromObject(it, "Episódio")) } + data.movies.forEach { add(episodeFromObject(it, "Filme")) } + data.ovas.forEach { add(episodeFromObject(it, "OVA")) } + sortByDescending { it.episode_number } + } + } + + private fun episodeFromObject(episode: EpisodeDto, prefix: String) = SEpisode.create().apply { + setUrlWithoutDomain(episode.epi_url) + episode_number = episode.epi_num.toFloatOrNull() ?: 0F + name = "$prefix ${episode.epi_num}" + } + + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document) = SAnime.create().apply { + val doc = getRealDoc(document) + val infos = doc.selectFirst("div.anime_infos")!! + + setUrlWithoutDomain(doc.location()) + title = infos.selectFirst("div > h3")!!.ownText() + thumbnail_url = infos.selectFirst("img")!!.attr("src") + genre = infos.select("div[itemprop=genre] a").eachText().joinToString() + artist = infos.selectFirst("div[itemprop=productionCompany]")!!.text() + + status = doc.selectFirst("div:contains(Status) span")?.text().let { + when { + it == null -> SAnime.UNKNOWN + it == "Completo" -> SAnime.COMPLETED + it.contains("Lançamento") -> SAnime.ONGOING + else -> SAnime.UNKNOWN + } + } + + description = buildString { + infos.selectFirst("div.anime_name + div.anime_info")?.text()?.let { + append("Nomes alternativos: $it\n") + } + + doc.selectFirst("div[itemprop=description]")?.text()?.let { + append("\n$it") + } + } + } + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List