fix(sr/AnimeSrbija): Remake to work with new theme (#1673)

This commit is contained in:
Claudemirovsky
2023-06-03 10:57:31 +00:00
committed by GitHub
parent e55e75b833
commit 740b7ceda7
13 changed files with 699 additions and 130 deletions

View File

@ -1,2 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
<?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

@ -1,13 +1,18 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Anime Srbija'
pkgNameSuffix = 'sr.animesrbija'
extClass = '.AnimeSrbija'
extVersionCode = 3
isNsfw = false
libVersion = '13'
}
apply from: "$rootDir/common.gradle"
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext {
extName = 'Anime Srbija'
pkgNameSuffix = 'sr.animesrbija'
extClass = '.AnimeSrbija'
extVersionCode = 4
}
dependencies {
implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

View File

@ -1,23 +1,31 @@
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.animeextension.sr.animesrbija.extractors.FilemoonExtractor
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.animesource.online.AnimeHttpSource
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 okhttp3.Headers.Companion.toHeaders
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
class AnimeSrbija : ParsedAnimeHttpSource() {
class AnimeSrbija : AnimeHttpSource() {
override val name = "Anime Srbija"
@ -27,130 +35,137 @@ class AnimeSrbija : ParsedAnimeHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
// Popular Anime
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").last()!!.attr("href"))
anime.thumbnail_url = element.select("img").attr("src")
anime.title = element.select("img").attr("title")
// ============================== Popular ===============================
override fun popularAnimeParse(response: Response): AnimesPage {
val doc = response.asJsoup()
val animes = doc.parseAs<SearchPageDto>().anime.map(::parseAnime)
return anime
val hasNextPage = doc.selectFirst("ul.pagination span.next-page:not(.disabled)") != null
return AnimesPage(animes, hasNextPage)
}
override fun popularAnimeNextPageSelector(): String? {
return ".next"
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"
}
}
}
override fun popularAnimeRequest(page: Int): Request {
return GET("$baseUrl/anime-lista/page/$page/?order=popular")
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val links = response.asJsoup().parseAs<EpisodeVideo>().links
return links.flatMap(::getVideosFromURL)
}
override fun popularAnimeSelector(): String {
return ".film-list > .item"
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() }
}
// Latest anime
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").last()!!.attr("href"))
anime.thumbnail_url = element.select("img").attr("src")
anime.title = element.select("img").attr("title")
// =========================== Anime Details ============================
override fun animeDetailsParse(response: Response): SAnime {
val anime = response.asJsoup().parseAs<AnimeDetailsDto>().anime
return 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") }
}
}
}
override fun latestUpdatesNextPageSelector(): String? {
return ".next"
}
// =============================== Search ===============================
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/anime-lista/page/$page/?order=update")
}
override fun latestUpdatesSelector(): String {
return ".film-list > .item"
}
// Search anime
override fun searchAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").last()!!.attr("href"))
anime.thumbnail_url = element.select("img").attr("src")
anime.title = element.select("img").attr("title")
return anime
}
override fun searchAnimeNextPageSelector(): String? {
return ".next"
}
override fun getFilterList() = AnimeSrbijaFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
return GET("$baseUrl/page/$page/?s=$query")
}
override fun searchAnimeSelector(): String {
return ".film-list > .item"
}
// Episode
override fun episodeListSelector(): String {
return ".ep-item a"
}
override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create()
episode.setUrlWithoutDomain(element.attr("href"))
episode.name = element.text()
val episodeNumberString = element.text().removePrefix("Episode ")
episode.episode_number = if (episodeNumberString.toFloatOrNull() != null) episodeNumberString.toFloat() else 0.0f
return episode
}
// Video
override fun videoFromElement(element: Element): Video {
val source = element.attr("src").substringAfter("?file=")
val relative = source.substringAfter("/file/")
val testSource = "https://cdn.asroll.tk/file/$relative"
return Video(testSource, "AS Cloud", testSource)
}
override fun videoListSelector(): String {
return "iframe"
}
override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val episodeId = document.selectFirst("div.prevnext:nth-child(4)")!!.attr("data-post-id")
val nume: String = "1"
val referer = response.request.url.encodedPath
val newHeaderList = mutableMapOf(Pair("referer", baseUrl + referer))
headers.forEach { newHeaderList[it.first] = it.second }
val bodyString = "action=player_ajax&post=$episodeId&nume=$nume"
val body = bodyString.toRequestBody("application/x-www-form-urlencoded".toMediaType())
val iframe = client.newCall(POST("https://www.animesrbija.com/wp-admin/admin-ajax.php", newHeaderList.toHeaders(), body)).execute().asJsoup()
return iframe.select(videoListSelector()).map { videoFromElement(it) }
}
// Anime
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.select(".dc-title").text()
anime.genre = document.select(".dcis.dcis-01 a").joinToString(" ") { it.text() }
anime.description = document.select(".dci-desc p").text()
val status = document.select("div.dcis:nth-child(4)").text().substringAfter("Status: ")
anime.status = when {
(status.equals(" Currently Airing")) -> SAnime.ONGOING
(status.equals(" Finished Airing")) -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
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 anime
return GET(url)
}
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
val id = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/anime/$id"))
.asObservableSuccess()
.map(::searchAnimeByIdParse)
} else {
super.fetchSearchAnime(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 = (2023 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,
)
}
}

View File

@ -0,0 +1,28 @@
package eu.kanade.tachiyomi.animeextension.sr.animesrbija.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
class FilemoonExtractor(private val client: OkHttpClient) {
fun videosFromUrl(url: String): List<Video> {
return runCatching {
val doc = client.newCall(GET(url)).execute().asJsoup()
val jsEval = doc.selectFirst("script:containsData(eval)")!!.data()
val masterUrl = JsUnpacker.unpackAndCombine(jsEval)
?.substringAfter("{file:\"")
?.substringBefore("\"}")
?: return emptyList()
val masterPlaylist = client.newCall(GET(masterUrl)).execute().body.string()
val separator = "#EXT-X-STREAM-INF:"
masterPlaylist.substringAfter(separator).split(separator).map {
val quality = "Filemoon:" + it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p "
val videoUrl = it.substringAfter("\n").substringBefore("\n")
Video(videoUrl, quality, videoUrl)
}
}.getOrElse { emptyList() }
}
}