Initial commit
14
src/sr/animebalkan/build.gradle
Normal file
|
@ -0,0 +1,14 @@
|
|||
ext {
|
||||
extName = 'AnimeBalkan'
|
||||
extClass = '.AnimeBalkan'
|
||||
themePkg = 'animestream'
|
||||
baseUrl = 'https://animebalkan.org'
|
||||
overrideVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:okru-extractor"))
|
||||
implementation(project(":lib:googledrive-extractor"))
|
||||
}
|
BIN
src/sr/animebalkan/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/sr/animebalkan/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
src/sr/animebalkan/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
src/sr/animebalkan/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/sr/animebalkan/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,65 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animebalkan
|
||||
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animebalkan.extractors.MailRuExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.googledriveextractor.GoogleDriveExtractor
|
||||
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
|
||||
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class AnimeBalkan : AnimeStream(
|
||||
"sr",
|
||||
"AnimeBalkan",
|
||||
"https://animebalkan.org",
|
||||
) {
|
||||
override val animeListUrl = "$baseUrl/animesaprevodom"
|
||||
|
||||
override val dateFormatter by lazy {
|
||||
SimpleDateFormat("MMMM d, yyyy", Locale("bs")) // YES, Bosnian
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun getHosterUrl(element: Element): String {
|
||||
if (element.text().contains("Server AB")) {
|
||||
return element.attr("value")
|
||||
}
|
||||
|
||||
return super.getHosterUrl(element)
|
||||
}
|
||||
|
||||
private val gdriveExtractor by lazy { GoogleDriveExtractor(client, headers) }
|
||||
private val mailruExtractor by lazy { MailRuExtractor(client, headers) }
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
|
||||
override fun getVideoList(url: String, name: String): List<Video> {
|
||||
return when {
|
||||
"Server OK" in name || "ok.ru" in url -> okruExtractor.videosFromUrl(url)
|
||||
"Server Ru" in name || "mail.ru" in url -> mailruExtractor.videosFromUrl(url)
|
||||
"Server GD" in name || "google.com" in url -> {
|
||||
// We need to do that bc the googledrive extractor is garbage.
|
||||
val newUrl = when {
|
||||
url.contains("uc?id=") -> url
|
||||
else -> {
|
||||
val id = url.substringAfter("/d/").substringBefore("/")
|
||||
"https://drive.google.com/uc?id=$id"
|
||||
}
|
||||
}
|
||||
gdriveExtractor.videosFromUrl(newUrl)
|
||||
}
|
||||
"Server AB" in name && baseUrl in url -> {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
val videoUrl = doc.selectFirst("source")?.attr("src")
|
||||
?: return emptyList()
|
||||
listOf(Video(videoUrl, "Server AB - Default", videoUrl))
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override val prefQualityValues = arrayOf("1080p", "720p", "480p", "360p", "240p")
|
||||
override val prefQualityEntries = prefQualityValues
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animebalkan.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MailRuExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
private val json: Json by injectLazy()
|
||||
private val urlRegex by lazy { "^//".toRegex() }
|
||||
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
val document = client.newCall(GET(url, headers)).execute()
|
||||
.asJsoup()
|
||||
|
||||
val metaUrl = document.selectFirst("script:containsData(metadataUrl)")
|
||||
?.data()
|
||||
?.run {
|
||||
substringAfter("metadataUrl\":\"")
|
||||
.substringBefore("\"")
|
||||
.replace(urlRegex, "https://") // Fix URLs
|
||||
} ?: return emptyList()
|
||||
|
||||
val metaHeaders = headers.newBuilder()
|
||||
.set("Referer", url)
|
||||
.set("Origin", "https://${url.toHttpUrl().host}")
|
||||
.build()
|
||||
|
||||
val metaResponse = client.newCall(GET(metaUrl, metaHeaders)).execute()
|
||||
|
||||
val metaJson = json.decodeFromString<MetaResponse>(
|
||||
metaResponse.body.string(),
|
||||
)
|
||||
|
||||
val videoKey = metaResponse.headers.firstOrNull {
|
||||
it.first.equals("set-cookie", true) && it.second.startsWith("video_key", true)
|
||||
}?.second?.substringBefore(";") ?: ""
|
||||
|
||||
val videoHeaders = metaHeaders.newBuilder()
|
||||
.set("Cookie", videoKey)
|
||||
.build()
|
||||
|
||||
return metaJson.videos.map {
|
||||
val videoUrl = it.url
|
||||
.replace(urlRegex, "https://")
|
||||
.replace(".mp4", ".mp4/stream.mpd")
|
||||
|
||||
Video(videoUrl, "Mail.ru ${it.key}", videoUrl, videoHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MetaResponse(val videos: List<VideoObject>)
|
||||
|
||||
@Serializable
|
||||
data class VideoObject(val url: String, val key: String)
|
||||
}
|
23
src/sr/animesrbija/AndroidManifest.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".sr.animesrbija.AnimeSrbijaUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="www.animesrbija.com"
|
||||
android:pathPattern="/anime/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
12
src/sr/animesrbija/build.gradle
Normal file
|
@ -0,0 +1,12 @@
|
|||
ext {
|
||||
extName = 'Anime Srbija'
|
||||
extClass = '.AnimeSrbija'
|
||||
extVersionCode = 9
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:filemoon-extractor'))
|
||||
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
|
||||
}
|
BIN
src/sr/animesrbija/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/sr/animesrbija/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
src/sr/animesrbija/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
src/sr/animesrbija/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/sr/animesrbija/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,170 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animesrbija
|
||||
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.AnimeDetailsDto
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.EpisodeVideo
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.EpisodesDto
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.LatestUpdatesDto
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.PagePropsDto
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.SearchAnimeDto
|
||||
import eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto.SearchPageDto
|
||||
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.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class AnimeSrbija : AnimeHttpSource() {
|
||||
|
||||
override val name = "Anime Srbija"
|
||||
|
||||
override val baseUrl = "https://www.animesrbija.com"
|
||||
|
||||
override val lang = "sr"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val doc = response.asJsoup()
|
||||
val animes = doc.parseAs<SearchPageDto>().anime.map(::parseAnime)
|
||||
|
||||
val hasNextPage = doc.selectFirst("ul.pagination span.next-page:not(.disabled)") != null
|
||||
return AnimesPage(animes, hasNextPage)
|
||||
}
|
||||
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/filter?sort=popular&page=$page")
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val data = response.asJsoup().parseAs<EpisodesDto>()
|
||||
return data.episodes.map {
|
||||
SEpisode.create().apply {
|
||||
setUrlWithoutDomain("/epizoda/${it.slug}")
|
||||
name = "Epizoda ${it.number}"
|
||||
episode_number = it.number.toFloat()
|
||||
if (it.filler) scanlator = "filler"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val links = response.asJsoup().parseAs<EpisodeVideo>().links
|
||||
return links.flatMap(::getVideosFromURL)
|
||||
}
|
||||
|
||||
private fun getVideosFromURL(url: String): List<Video> {
|
||||
val trimmedUrl = url.trim('!')
|
||||
return runCatching {
|
||||
when {
|
||||
"filemoon" in trimmedUrl ->
|
||||
FilemoonExtractor(client).videosFromUrl(trimmedUrl)
|
||||
".m3u8" in trimmedUrl ->
|
||||
listOf(Video(trimmedUrl, "Internal Player", trimmedUrl))
|
||||
else -> emptyList()
|
||||
}
|
||||
}.getOrElse { emptyList() }
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val anime = response.asJsoup().parseAs<AnimeDetailsDto>().anime
|
||||
|
||||
return SAnime.create().apply {
|
||||
setUrlWithoutDomain("/anime/${anime.slug}")
|
||||
thumbnail_url = baseUrl + anime.imgPath
|
||||
title = anime.title
|
||||
status = when (anime.status) {
|
||||
"Završeno" -> SAnime.COMPLETED
|
||||
"Emituje se" -> SAnime.ONGOING
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
artist = anime.studios.joinToString()
|
||||
genre = anime.genres.joinToString()
|
||||
|
||||
description = buildString {
|
||||
anime.season?.let { append("Sezona: $it\n") }
|
||||
anime.aired?.let { append("Datum: $it\n") }
|
||||
anime.subtitle?.let { append("Alternativni naziv: $it\n") }
|
||||
anime.desc?.let { append("\n\n$it") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun getFilterList() = AnimeSrbijaFilters.FILTER_LIST
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val params = AnimeSrbijaFilters.getSearchParameters(filters)
|
||||
val url = buildString {
|
||||
append("$baseUrl/filter?page=$page&sort=${params.sortby}")
|
||||
if (query.isNotBlank()) append("&search=$query")
|
||||
params.parsedCheckboxes.forEach {
|
||||
if (it.isNotBlank()) append("&$it")
|
||||
}
|
||||
}
|
||||
|
||||
return GET(url)
|
||||
}
|
||||
|
||||
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
||||
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||
val id = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/anime/$id"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
} else {
|
||||
super.getSearchAnime(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response)
|
||||
return AnimesPage(listOf(details), false)
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val data = response.asJsoup().parseAs<LatestUpdatesDto>()
|
||||
val animes = data.animes.map(::parseAnime)
|
||||
return AnimesPage(animes, false)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private inline fun <reified T> Document.parseAs(): T {
|
||||
val nextData = selectFirst("script#__NEXT_DATA__")!!
|
||||
.data()
|
||||
.substringAfter(":")
|
||||
.substringBeforeLast("},\"page\"") + "}"
|
||||
return json.decodeFromString<PagePropsDto<T>>(nextData).data
|
||||
}
|
||||
|
||||
private fun parseAnime(item: SearchAnimeDto): SAnime {
|
||||
return SAnime.create().apply {
|
||||
setUrlWithoutDomain("/anime/${item.slug}")
|
||||
thumbnail_url = baseUrl + item.imgPath
|
||||
title = item.title
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREFIX_SEARCH = "id:"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animesrbija
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
|
||||
object AnimeSrbijaFilters {
|
||||
open class QueryPartFilter(
|
||||
displayName: String,
|
||||
val vals: Array<Pair<String, String>>,
|
||||
) : AnimeFilter.Select<String>(
|
||||
displayName,
|
||||
vals.map { it.first }.toTypedArray(),
|
||||
) {
|
||||
fun toQueryPart() = vals[state].second
|
||||
}
|
||||
|
||||
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) :
|
||||
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
|
||||
return (getFirst<R>() as QueryPartFilter).toQueryPart()
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||
return first { it is R } as R
|
||||
}
|
||||
|
||||
private inline fun <reified R> AnimeFilterList.parseCheckbox(
|
||||
options: Array<Pair<String, String>>,
|
||||
name: String,
|
||||
): String {
|
||||
return (getFirst<R>() as CheckBoxFilterList).state
|
||||
.mapNotNull { checkbox ->
|
||||
when {
|
||||
checkbox.state -> {
|
||||
options.find { it.first == checkbox.name }!!.second
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}.joinToString("&$name=").let {
|
||||
when {
|
||||
it.isBlank() -> ""
|
||||
else -> "$name=$it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SortFilter : QueryPartFilter("Sortiraj po", AnimeSrbijaFiltersData.SORTBY)
|
||||
class GenresFilter : CheckBoxFilterList("Žanrove", AnimeSrbijaFiltersData.GENRES)
|
||||
class SeasonFilter : CheckBoxFilterList("Sezonu", AnimeSrbijaFiltersData.SEASONS)
|
||||
class TypeFilter : CheckBoxFilterList("Tip", AnimeSrbijaFiltersData.TYPES)
|
||||
class YearFilter : CheckBoxFilterList("Godinu", AnimeSrbijaFiltersData.YEARS)
|
||||
class StudioFilter : CheckBoxFilterList("Studio", AnimeSrbijaFiltersData.STUDIOS)
|
||||
class TranslatorFilter : CheckBoxFilterList("Prevodioca", AnimeSrbijaFiltersData.TRANSLATORS)
|
||||
class StatusFilter : CheckBoxFilterList("Status", AnimeSrbijaFiltersData.STATUS)
|
||||
|
||||
val FILTER_LIST: AnimeFilterList
|
||||
get() = AnimeFilterList(
|
||||
SortFilter(),
|
||||
AnimeFilter.Separator(),
|
||||
GenresFilter(),
|
||||
SeasonFilter(),
|
||||
TypeFilter(),
|
||||
YearFilter(),
|
||||
StudioFilter(),
|
||||
TranslatorFilter(),
|
||||
StatusFilter(),
|
||||
)
|
||||
|
||||
data class FilterSearchParams(
|
||||
val sortby: String = "",
|
||||
val genres: String = "",
|
||||
val seasons: String = "",
|
||||
val types: String = "",
|
||||
val years: String = "",
|
||||
val studios: String = "",
|
||||
val translators: String = "",
|
||||
val status: String = "",
|
||||
) {
|
||||
val parsedCheckboxes by lazy {
|
||||
listOf(
|
||||
genres,
|
||||
seasons,
|
||||
types,
|
||||
years,
|
||||
studios,
|
||||
translators,
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||
if (filters.isEmpty()) return FilterSearchParams()
|
||||
|
||||
return FilterSearchParams(
|
||||
filters.asQueryPart<SortFilter>(),
|
||||
filters.parseCheckbox<GenresFilter>(AnimeSrbijaFiltersData.GENRES, "genre"),
|
||||
filters.parseCheckbox<SeasonFilter>(AnimeSrbijaFiltersData.SEASONS, "season"),
|
||||
filters.parseCheckbox<TypeFilter>(AnimeSrbijaFiltersData.TYPES, "type"),
|
||||
filters.parseCheckbox<YearFilter>(AnimeSrbijaFiltersData.YEARS, "year"),
|
||||
filters.parseCheckbox<StudioFilter>(AnimeSrbijaFiltersData.STUDIOS, "studio"),
|
||||
filters.parseCheckbox<TranslatorFilter>(AnimeSrbijaFiltersData.TRANSLATORS, "translator"),
|
||||
filters.parseCheckbox<StatusFilter>(AnimeSrbijaFiltersData.STATUS, "status"),
|
||||
)
|
||||
}
|
||||
|
||||
private object AnimeSrbijaFiltersData {
|
||||
val SORTBY = arrayOf(
|
||||
Pair("Najgledanije", "popular"),
|
||||
Pair("MAL Ocena", "rating"),
|
||||
Pair("Novo", "new"),
|
||||
)
|
||||
|
||||
val GENRES = arrayOf(
|
||||
Pair("Akcija", "Akcija"),
|
||||
Pair("Avantura", "Avantura"),
|
||||
Pair("Boys Love", "Boys Love"),
|
||||
Pair("Komedija", "Komedija"),
|
||||
Pair("Drama", "Drama"),
|
||||
Pair("Ecchi", "Ecchi"),
|
||||
Pair("Girls Love", "Girls Love"),
|
||||
Pair("Harem", "Harem"),
|
||||
Pair("Istorijski", "Istorijski"),
|
||||
Pair("Horor", "Horor"),
|
||||
Pair("Isekai", "Isekai"),
|
||||
Pair("Josei", "Josei"),
|
||||
Pair("Borilačke veštine", "Borilačke veštine"),
|
||||
Pair("Mecha", "Mecha"),
|
||||
Pair("Vojska", "Vojska"),
|
||||
Pair("Muzika", "Muzika"),
|
||||
Pair("Misterija", "Misterija"),
|
||||
Pair("Psihološki", "Psihološki"),
|
||||
Pair("Romansa", "Romansa"),
|
||||
Pair("Škola", "Škola"),
|
||||
Pair("Naučna fantastika", "Naučna fantastika"),
|
||||
Pair("Seinen", "Seinen"),
|
||||
Pair("Shoujo", "Shoujo"),
|
||||
Pair("Shounen", "Shounen"),
|
||||
Pair("Svakodnevnica", "Svakodnevnica"),
|
||||
Pair("Svemir", "Svemir"),
|
||||
Pair("Sport", "Sport"),
|
||||
Pair("Natprirodno", "Natprirodno"),
|
||||
Pair("Super Moći", "Super Moći"),
|
||||
Pair("Vampiri", "Vampiri"),
|
||||
)
|
||||
|
||||
val SEASONS = arrayOf(
|
||||
Pair("Leto", "Leto"),
|
||||
Pair("Proleće", "Proleće"),
|
||||
Pair("Zima", "Zima"),
|
||||
Pair("Jesen", "Jesen"),
|
||||
)
|
||||
|
||||
val TYPES = arrayOf(
|
||||
Pair("TV", "TV"),
|
||||
Pair("Film", "Film"),
|
||||
Pair("ONA", "ONA"),
|
||||
Pair("OVA", "OVA"),
|
||||
Pair("Specijal", "Specijal"),
|
||||
)
|
||||
|
||||
val YEARS = (2024 downTo 1960).map {
|
||||
Pair(it.toString(), it.toString())
|
||||
}.toTypedArray()
|
||||
|
||||
val STUDIOS = arrayOf(
|
||||
Pair("8bit", "8bit"),
|
||||
Pair("A-1 Pictures", "A-1 Pictures"),
|
||||
Pair("A.C.G.T.", "A.C.G.T."),
|
||||
Pair("AHA Entertainment", "AHA Entertainment"),
|
||||
Pair("AIC Spirits", "AIC Spirits"),
|
||||
Pair("APPP", "APPP"),
|
||||
Pair("AXsiZ", "AXsiZ"),
|
||||
Pair("Ajia-Do", "Ajia-Do"),
|
||||
Pair("Akatsuki", "Akatsuki"),
|
||||
Pair("Animation Do", "Animation Do"),
|
||||
Pair("Arms", "Arms"),
|
||||
Pair("Artland", "Artland"),
|
||||
Pair("Asahi Production", "Asahi Production"),
|
||||
Pair("Ascension", "Ascension"),
|
||||
Pair("AtelierPontdarc", "AtelierPontdarc"),
|
||||
Pair("B.CMAY PICTURES", "B.CMAY PICTURES"),
|
||||
Pair("Bakken Record", "Bakken Record"),
|
||||
Pair("Bandai Namco Pictures", "Bandai Namco Pictures"),
|
||||
Pair("Bee Train", "Bee Train"),
|
||||
Pair("Bibury Animation CG", "Bibury Animation CG"),
|
||||
Pair("Bibury Animation Studios", "Bibury Animation Studios"),
|
||||
Pair("Blade", "Blade"),
|
||||
Pair("Bones", "Bones"),
|
||||
Pair("Brain's Base", "Brain's Base"),
|
||||
Pair("Brain's Base", "Brain's Base"),
|
||||
Pair("Bridge", "Bridge"),
|
||||
Pair("C2C", "C2C"),
|
||||
Pair("Children's Playground Entertainment", "Children's Playground Entertainment"),
|
||||
Pair("CloverWorks", "CloverWorks"),
|
||||
Pair("CoMix Wave Films", "CoMix Wave Films"),
|
||||
Pair("Connect", "Connect"),
|
||||
Pair("Creators in Pack", "Creators in Pack"),
|
||||
Pair("CygamesPictures", "CygamesPictures"),
|
||||
Pair("DLE", "DLE"),
|
||||
Pair("DR Movie", "DR Movie"),
|
||||
Pair("Daume", "Daume"),
|
||||
Pair("David Production", "David Production"),
|
||||
Pair("Diomedéa", "Diomedéa"),
|
||||
Pair("Doga Kobo", "Doga Kobo"),
|
||||
Pair("Drive", "Drive"),
|
||||
Pair("EMT Squared", "EMT Squared"),
|
||||
Pair("Encourage Films", "Encourage Films"),
|
||||
Pair("Ezόla", "Ezόla"),
|
||||
Pair("Fanworks", "Fanworks"),
|
||||
Pair("Felix Film", "Felix Film"),
|
||||
Pair("Flat Studio", "Flat Studio"),
|
||||
Pair("Frederator Studios", "Frederator Studios"),
|
||||
Pair("Fuji TV", "Fuji TV"),
|
||||
Pair("GEEK TOYS", "GEEK TOYS"),
|
||||
Pair("GEMBA", "GEMBA"),
|
||||
Pair("Gainax", "Gainax"),
|
||||
Pair("Gallop", "Gallop"),
|
||||
Pair("Geek Toys", "Geek Toys"),
|
||||
Pair("Geno Studio", "Geno Studio"),
|
||||
Pair("GoHands", "GoHands"),
|
||||
Pair("Gonzo", "Gonzo"),
|
||||
Pair("Graphinica", "Graphinica"),
|
||||
Pair("Group TAC", "Group TAC"),
|
||||
Pair("HORNETS", "HORNETS"),
|
||||
Pair("Hal Film Maker", "Hal Film Maker"),
|
||||
Pair("Hoods Drifters Studio", "Hoods Drifters Studio"),
|
||||
Pair("Hoods Entertainment", "Hoods Entertainment"),
|
||||
Pair("J.C.Staff", "J.C.Staff"),
|
||||
Pair("Jinnis Animation Studios", "Jinnis Animation Studios"),
|
||||
Pair("Kamikaze Douga", "Kamikaze Douga"),
|
||||
Pair("Kenji Studio", "Kenji Studio"),
|
||||
Pair("Khara", "Khara"),
|
||||
Pair("Kinema Citrus", "Kinema Citrus"),
|
||||
Pair("Kyoto Animation", "Kyoto Animation"),
|
||||
Pair("LIDENFILMS", "LIDENFILMS"),
|
||||
Pair("LandQ studios", "LandQ studios"),
|
||||
Pair("Lapin Track", "Lapin Track"),
|
||||
Pair("Larx Entertainment", "Larx Entertainment"),
|
||||
Pair("Lay-duce", "Lay-duce"),
|
||||
Pair("Lerche", "Lerche"),
|
||||
Pair("Liber", "Liber"),
|
||||
Pair("MAPPA", "MAPPA"),
|
||||
Pair("Madhouse", "Madhouse"),
|
||||
Pair("Maho Film", "Maho Film"),
|
||||
Pair("Manglobe", "Manglobe"),
|
||||
Pair("Marvy Jack", "Marvy Jack"),
|
||||
Pair("Millepensee", "Millepensee"),
|
||||
Pair("NAZ", "NAZ"),
|
||||
Pair("Nexus", "Nexus"),
|
||||
Pair("Nomad", "Nomad"),
|
||||
Pair("Nut", "Nut"),
|
||||
Pair("OLM", "OLM"),
|
||||
Pair("ORENDA", "ORENDA"),
|
||||
Pair("OZ", "OZ"),
|
||||
Pair("Okuruto Noboru", "Okuruto Noboru"),
|
||||
Pair("Orange", "Orange"),
|
||||
Pair("Ordet", "Ordet"),
|
||||
Pair("P.A. Works", "P.A. Works"),
|
||||
Pair("Parrot", "Parrot"),
|
||||
Pair("Passione", "Passione"),
|
||||
Pair("Pastel", "Pastel"),
|
||||
Pair("Pierrot Plus", "Pierrot Plus"),
|
||||
Pair("Pierrot", "Pierrot"),
|
||||
Pair("Pine Jam", "Pine Jam"),
|
||||
Pair("Platinum Vision", "Platinum Vision"),
|
||||
Pair("Polygon Pictures", "Polygon Pictures"),
|
||||
Pair("Production +h.", "Production +h."),
|
||||
Pair("Production GoodBook", "Production GoodBook"),
|
||||
Pair("Production I.G", "Production I.G"),
|
||||
Pair("Production IMS", "Production IMS"),
|
||||
Pair("Production Reed", "Production Reed"),
|
||||
Pair("Project No.9", "Project No.9"),
|
||||
Pair("Quad", "Quad"),
|
||||
Pair("Quebico", "Quebico"),
|
||||
Pair("Revoroot", "Revoroot"),
|
||||
Pair("SANZIGEN", "SANZIGEN"),
|
||||
Pair("SILVER LINK.", "SILVER LINK."),
|
||||
Pair("Saetta", "Saetta"),
|
||||
Pair("Satelight", "Satelight"),
|
||||
Pair("Science SARU", "Science SARU"),
|
||||
Pair("Seven Arcs Pictures", "Seven Arcs Pictures"),
|
||||
Pair("Seven Arcs", "Seven Arcs"),
|
||||
Pair("Shaft", "Shaft"),
|
||||
Pair("Shin-Ei Animation", "Shin-Ei Animation"),
|
||||
Pair("Shuka", "Shuka"),
|
||||
Pair("Signal.MD", "Signal.MD"),
|
||||
Pair("Sola Digital Arts", "Sola Digital Arts"),
|
||||
Pair("Studio 3Hz", "Studio 3Hz"),
|
||||
Pair("Studio 4°C", "Studio 4°C"),
|
||||
Pair("Studio Bind", "Studio Bind"),
|
||||
Pair("Studio Blanc", "Studio Blanc"),
|
||||
Pair("Studio Colorido", "Studio Colorido"),
|
||||
Pair("Studio Comet", "Studio Comet"),
|
||||
Pair("Studio Daisy", "Studio Daisy"),
|
||||
Pair("Studio Deen", "Studio Deen"),
|
||||
Pair("Studio Eromatick", "Studio Eromatick"),
|
||||
Pair("Studio Fantasia", "Studio Fantasia"),
|
||||
Pair("Studio Ghibli", "Studio Ghibli"),
|
||||
Pair("Studio Gokumi", "Studio Gokumi"),
|
||||
Pair("Studio Hibari", "Studio Hibari"),
|
||||
Pair("Studio Kafka", "Studio Kafka"),
|
||||
Pair("Studio Kai", "Studio Kai"),
|
||||
Pair("Studio Mir", "Studio Mir"),
|
||||
Pair("Studio Palette", "Studio Palette"),
|
||||
Pair("Studio PuYUKAI", "Studio PuYUKAI"),
|
||||
Pair("Studio Rikka", "Studio Rikka"),
|
||||
Pair("Studio VOLN", "Studio VOLN"),
|
||||
Pair("Studio elle", "Studio elle"),
|
||||
Pair("Sublimation", "Sublimation"),
|
||||
Pair("Sunrise", "Sunrise"),
|
||||
Pair("Sunwoo Entertainment", "Sunwoo Entertainment"),
|
||||
Pair("SynergySP", "SynergySP"),
|
||||
Pair("T-Rex", "T-Rex"),
|
||||
Pair("TMS Entertainment", "TMS Entertainment"),
|
||||
Pair("TNK", "TNK"),
|
||||
Pair("TROYCA", "TROYCA"),
|
||||
Pair("TYO Animations", "TYO Animations"),
|
||||
Pair("Tatsunoko Production", "Tatsunoko Production"),
|
||||
Pair("Tear Studio", "Tear Studio"),
|
||||
Pair("Telecom Animation Film", "Telecom Animation Film"),
|
||||
Pair("Tezuka Productions", "Tezuka Productions"),
|
||||
Pair("Thundray", "Thundray"),
|
||||
Pair("Toei Animation", "Toei Animation"),
|
||||
Pair("Triangle Staff", "Triangle Staff"),
|
||||
Pair("Trigger", "Trigger"),
|
||||
Pair("Typhoon Graphics", "Typhoon Graphics"),
|
||||
Pair("White Fox", "White Fox"),
|
||||
Pair("Wit Studio", "Wit Studio"),
|
||||
Pair("Wolfsbane", "Wolfsbane"),
|
||||
Pair("Xebec", "Xebec"),
|
||||
Pair("Yokohama Animation Lab", "Yokohama Animation Lab"),
|
||||
Pair("Yostar Pictures", "Yostar Pictures"),
|
||||
Pair("Yumeta Company", "Yumeta Company"),
|
||||
Pair("Zero-G Room", "Zero-G Room"),
|
||||
Pair("Zero-G", "Zero-G"),
|
||||
Pair("Zexcs", "Zexcs"),
|
||||
Pair("animate Film", "animate Film"),
|
||||
Pair("asread.", "asread."),
|
||||
Pair("domerica", "domerica"),
|
||||
Pair("feel.", "feel."),
|
||||
Pair("l-a-unch・BOX", "l-a-unch・BOX"),
|
||||
Pair("ufotable", "ufotable"),
|
||||
)
|
||||
|
||||
val TRANSLATORS = arrayOf(
|
||||
Pair("6paths", "6paths"),
|
||||
Pair("AnimeOverdose", "AnimeOverdose"),
|
||||
Pair("AnimeSrbija", "AnimeSrbija"),
|
||||
Pair("BG-anime", "BG-anime"),
|
||||
Pair("EpicMan", "EpicMan"),
|
||||
Pair("Ich1ya", "Ich1ya"),
|
||||
Pair("Midor1ya", "Midor1ya"),
|
||||
Pair("Netflix", "Netflix"),
|
||||
Pair("Zmajevakugla.rs", "Zmajevakugla.rs"),
|
||||
Pair("Zmajče", "Zmajče"),
|
||||
Pair("kikirikisemenke", "kikirikisemenke"),
|
||||
Pair("lofy", "lofy"),
|
||||
Pair("meru", "meru"),
|
||||
Pair("rueno", "rueno"),
|
||||
Pair("trytofindme", "trytofindme"),
|
||||
)
|
||||
|
||||
val STATUS = arrayOf(
|
||||
Pair("Emituje se", "Emituje se"),
|
||||
Pair("Uskoro", "Uskoro"),
|
||||
Pair("Završeno", "Završeno"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animesrbija
|
||||
|
||||
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://www.animesrbija.com/anime/<item> intents
|
||||
* and redirects them to the main Aniyomi process.
|
||||
*/
|
||||
class AnimeSrbijaUrlActivity : Activity() {
|
||||
|
||||
private val tag = javaClass.simpleName
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val item = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", "${AnimeSrbija.PREFIX_SEARCH}$item")
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package eu.kanade.tachiyomi.animeextension.sr.animesrbija.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PagePropsDto<T>(@SerialName("pageProps") val data: T)
|
||||
|
||||
@Serializable
|
||||
data class SearchPageDto(val anime: List<SearchAnimeDto>)
|
||||
|
||||
@Serializable
|
||||
data class SearchAnimeDto(
|
||||
val title: String,
|
||||
val slug: String,
|
||||
val img: String,
|
||||
) {
|
||||
val imgPath by lazy { "/_next/image?url=$img&w=1080&q=75" }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class LatestUpdatesDto(
|
||||
@SerialName("newEpisodes")
|
||||
val data: List<LatestEpisodeUpdateDto>,
|
||||
) {
|
||||
@Serializable
|
||||
data class LatestEpisodeUpdateDto(val anime: SearchAnimeDto)
|
||||
|
||||
val animes by lazy { data.map { it.anime } }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AnimeDetailsDto(val anime: AnimeDetailsData)
|
||||
|
||||
@Serializable
|
||||
data class AnimeDetailsData(
|
||||
val aired: String?,
|
||||
val desc: String?,
|
||||
val genres: List<String>,
|
||||
val img: String,
|
||||
val season: String?,
|
||||
val slug: String,
|
||||
val status: String?,
|
||||
val studios: List<String>,
|
||||
val subtitle: String?,
|
||||
val title: String,
|
||||
) {
|
||||
val imgPath by lazy { "/_next/image?url=$img&w=1080&q=75" }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EpisodesDto(val anime: EpisodeListDto) {
|
||||
@Serializable
|
||||
data class EpisodeListDto(val episodes: List<EpisodeDto>)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeDto(
|
||||
val slug: String,
|
||||
val number: Int,
|
||||
val filler: Boolean,
|
||||
)
|
||||
|
||||
val episodes by lazy { anime.episodes }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EpisodeVideo(val episode: PlayersDto) {
|
||||
@Serializable
|
||||
data class PlayersDto(
|
||||
val player1: String?,
|
||||
val player2: String?,
|
||||
val player3: String?,
|
||||
val player4: String?,
|
||||
val player5: String?,
|
||||
)
|
||||
|
||||
val links by lazy {
|
||||
listOfNotNull(
|
||||
episode.player1,
|
||||
episode.player2,
|
||||
episode.player3,
|
||||
episode.player4,
|
||||
episode.player5,
|
||||
)
|
||||
}
|
||||
}
|