Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View 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"))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -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
}

View file

@ -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)
}

View 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>

View 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")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -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:"
}
}

View file

@ -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&#039;s Base", "Brain&#039;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"),
)
}
}

View file

@ -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)
}
}

View file

@ -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,
)
}
}