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,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".tr.animeler.AnimelerUrlActivity"
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="animeler.me"
android:pathPattern="/anime/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,20 @@
ext {
extName = 'Animeler'
extClass = '.Animeler'
extVersionCode = 12
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:dood-extractor"))
implementation(project(":lib:filemoon-extractor"))
implementation(project(":lib:gdriveplayer-extractor"))
implementation(project(":lib:sibnet-extractor"))
implementation(project(":lib:streamlare-extractor"))
implementation(project(":lib:okru-extractor"))
implementation(project(":lib:streamtape-extractor"))
implementation(project(":lib:uqload-extractor"))
implementation(project(":lib:voe-extractor"))
implementation(project(":lib:vudeo-extractor"))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,374 @@
package eu.kanade.tachiyomi.animeextension.tr.animeler
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.AnimeEpisodes
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.FullAnimeDto
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.SearchRequestDto
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.SearchResponseDto
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.SingleDto
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.SourcesDto
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.VideoDto
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
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.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.gdriveplayerextractor.GdrivePlayerExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.sibnetextractor.SibnetExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.Jsoup
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Animeler : AnimeHttpSource(), ConfigurableAnimeSource {
override val name = "Animeler"
override val baseUrl = "https://animeler.me"
override val lang = "tr"
override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int) = searchOrderBy("total_kiranime_views", page)
override fun popularAnimeParse(response: Response): AnimesPage {
val results = response.parseAs<SearchResponseDto>()
val doc = Jsoup.parseBodyFragment(results.data)
val animes = doc.select("div.w-full:has(div.kira-anime)").map {
SAnime.create().apply {
thumbnail_url = it.selectFirst("img")?.attr("src")
with(it.selectFirst("h3 > a")!!) {
title = text()
setUrlWithoutDomain(attr("href"))
}
}
}
val page = response.request.url.queryParameter("page")?.toIntOrNull() ?: 1
val hasNextPage = page < results.pages
return AnimesPage(animes, hasNextPage)
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = searchOrderBy("kiranime_anime_updated", page)
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
// =============================== Search ===============================
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)
}
override fun getFilterList() = AnimelerFilters.FILTER_LIST
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = AnimelerFilters.getSearchParameters(filters)
val (meta, orderBy) = when (params.orderBy) {
"date", "title" -> Pair(null, params.orderBy)
else -> Pair(params.orderBy, "meta_value_num")
}
val single = SingleDto(
paged = page,
key = meta,
order = params.order,
orderBy = orderBy,
season = params.season.ifEmpty { null },
year = params.year.ifEmpty { null },
)
val taxonomies = with(params) {
listOf(genres, status, producers, studios, types).filter {
it.terms.isNotEmpty()
}
}
val requestDto = SearchRequestDto(single, query, query, taxonomies)
val requestData = json.encodeToString(requestDto)
return searchRequest(requestData, page)
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
private fun searchOrderBy(order: String, page: Int): Request {
val body = """
{
"keyword": "",
"query": "",
"single": {
"paged": $page,
"orderby": "meta_value_num",
"meta_key": "$order",
"order": "desc"
},
"tax": []
}
""".trimIndent()
return searchRequest(body, page)
}
private fun searchRequest(data: String, page: Int): Request {
val body = data.toRequestBody("application/json".toMediaType())
return POST("$baseUrl/wp-json/kiranime/v1/anime/advancedsearch?_locale=user&page=$page", headers, body)
}
// =========================== Anime Details ============================
private inline fun <reified T> Response.parseBody(): T {
val body = use { it.body.string() }
.substringAfter("const anime = ")
.substringBefore("};") + "}"
return json.decodeFromString<T>(body)
}
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
val animeDto = response.parseBody<FullAnimeDto>()
setUrlWithoutDomain(animeDto.url)
thumbnail_url = animeDto.thumbnail
title = animeDto.title
artist = animeDto.studios
author = animeDto.producers
genre = animeDto.genres
status = when {
animeDto.meta.aired.orEmpty().contains(" to ") -> SAnime.COMPLETED
else -> SAnime.UNKNOWN
}
description = buildString {
animeDto.post.post_content?.also { append(it + "\n") }
with(animeDto.meta) {
score?.takeIf(String::isNotBlank)?.also { append("\nScore: $it") }
native?.takeIf(String::isNotBlank)?.also { append("\nNative: $it") }
synonyms?.takeIf(String::isNotBlank)?.also { append("\nDiğer İsimleri: $it") }
rate?.takeIf(String::isNotBlank)?.also { append("\nRate: $it") }
premiered?.takeIf(String::isNotBlank)?.also { append("\nPremiered: $it") }
aired?.takeIf(String::isNotBlank)?.also { append("\nYayınlandı: $it") }
duration?.takeIf(String::isNotBlank)?.also { append("\nSüre: $it") }
}
}
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = response.parseBody<AnimeEpisodes>().episodes
return episodes.map {
SEpisode.create().apply {
setUrlWithoutDomain(it.url)
name = "Bölüm " + it.meta.number
episode_number = it.meta.number.toFloat()
date_upload = it.date.toDate()
}
}
}
// ============================ Video Links =============================
private val doodExtractor by lazy { DoodExtractor(client) }
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
private val gdrivePlayerExtractor by lazy { GdrivePlayerExtractor(client) }
private val okruExtractor by lazy { OkruExtractor(client) }
private val sibnetExtractor by lazy { SibnetExtractor(client) }
private val streamlareExtractor by lazy { StreamlareExtractor(client) }
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
private val uqloadExtractor by lazy { UqloadExtractor(client) }
private val voeExtractor by lazy { VoeExtractor(client) }
private val vudeoExtractor by lazy { VudeoExtractor(client) }
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
val iframeUrl = doc.selectFirst("div.episode-player-box > iframe")
?.run { attr("data-src").ifBlank { attr("src") } }
?: doc.selectFirst("script:containsData(embedUrl)")
?.data()
?.substringAfter("\"embedUrl\": \"")
?.substringBefore('"')
?: throw Exception("No video available.")
val playerBody = { it: String ->
FormBody.Builder()
.add("hash", iframeUrl.substringAfter("/video/"))
.add("r", "$baseUrl/")
.add("s", it)
.build()
}
val headers = headersBuilder()
.add("Origin", "https://" + iframeUrl.toHttpUrl().host) // just to be sure
.add("X-Requested-With", "XMLHttpRequest")
.build()
val actionUrl = "$iframeUrl?do=getVideo"
val players = client.newCall(POST(actionUrl, headers, playerBody(""))).execute()
.parseAs<SourcesDto>()
val chosenHosts = preferences.getStringSet(PREF_HOSTS_SELECTION_KEY, SUPPORTED_PLAYERS)!!
val filteredSources = players.sourceList.entries.filter { source ->
chosenHosts.any { it.contains(source.value, true) }
}
return filteredSources.parallelCatchingFlatMapBlocking {
val body = playerBody(it.key)
val res = client.newCall(POST(actionUrl, headers, body)).await()
.parseAs<VideoDto>()
videosFromUrl(res.videoSrc)
}
}
private fun videosFromUrl(url: String): List<Video> {
return when {
"dood" in url -> doodExtractor.videosFromUrl(url)
"drive.google" in url -> {
val newUrl = "https://gdriveplayer.to/embed2.php?link=$url"
gdrivePlayerExtractor.videosFromUrl(newUrl, "GdrivePlayer", headers)
}
"filemoon." in url -> filemoonExtractor.videosFromUrl(url)
"ok.ru" in url || "odnoklassniki.ru" in url -> okruExtractor.videosFromUrl(url)
"streamtape" in url -> streamtapeExtractor.videoFromUrl(url)?.let(::listOf)
"sibnet" in url -> sibnetExtractor.videosFromUrl(url)
"streamlare" in url -> streamlareExtractor.videosFromUrl(url)
"uqload" in url -> uqloadExtractor.videosFromUrl(url)
"voe." in url -> voeExtractor.videosFromUrl(url)
"vudeo." in url -> vudeoExtractor.videosFromUrl(url)
else -> null
} ?: emptyList()
}
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
entries = PREF_QUALITY_ENTRIES
entryValues = PREF_QUALITY_VALUES
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
MultiSelectListPreference(screen.context).apply {
key = PREF_HOSTS_SELECTION_KEY
title = PREF_HOSTS_SELECTION_TITLE
entries = PREF_HOSTS_SELECTION_ENTRIES
entryValues = PREF_HOSTS_SELECTION_ENTRIES
setDefaultValue(PREF_HOSTS_SELECTION_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
@Suppress("UNCHECKED_CAST")
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
}
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
private fun String.toDate(): Long {
return runCatching { DATE_FORMATTER.parse(trim())?.time }
.getOrNull() ?: 0L
}
private val qualityRegex by lazy { Regex("""(\d+)p""") }
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortedWith(
compareBy(
{ it.quality.contains(quality) },
{ qualityRegex.find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
}
const val PREFIX_SEARCH = "id:"
private val SUPPORTED_PLAYERS = setOf(
"doodstream.com",
"G.Drive",
"Moon",
"ok.ru",
"S.Tape",
"Sibnet",
"Streamlare",
"UQload",
"Voe",
"vudeo",
)
private const val PREF_QUALITY_KEY = "pref_quality_key"
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "720p"
private val PREF_QUALITY_ENTRIES = arrayOf("1080p", "720p", "480p", "360p")
private val PREF_QUALITY_VALUES = PREF_QUALITY_ENTRIES
private const val PREF_HOSTS_SELECTION_KEY = "pref_hosts_selection"
private const val PREF_HOSTS_SELECTION_TITLE = "Disable/enable video hosts"
private val PREF_HOSTS_SELECTION_ENTRIES = SUPPORTED_PLAYERS.toTypedArray()
private val PREF_HOSTS_SELECTION_DEFAULT = SUPPORTED_PLAYERS
}
}

View file

@ -0,0 +1,683 @@
package eu.kanade.tachiyomi.animeextension.tr.animeler
import eu.kanade.tachiyomi.animeextension.tr.animeler.dto.TaxonomyDto
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object AnimelerFilters {
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, Int>>) :
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.getFirst(): R = first { it is R } as R
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, Int>>,
name: String,
): TaxonomyDto {
return (getFirst<R>() as CheckBoxFilterList).state
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.let { TaxonomyDto(name, it) }
}
class GenresFilter : CheckBoxFilterList("Genres", AnimelerFiltersData.GENRES)
class StatusFilter : CheckBoxFilterList("Durumu", AnimelerFiltersData.STATUS)
class ProducersFilter : CheckBoxFilterList("Yapımcı", AnimelerFiltersData.PRODUCERS)
class StudiosFilter : CheckBoxFilterList("Stüdyo", AnimelerFiltersData.GENRES)
class TypesFilter : CheckBoxFilterList("Tür", AnimelerFiltersData.TYPES)
class OrderFilter : AnimeFilter.Sort(
"Order by",
AnimelerFiltersData.ORDERS.map { it.first }.toTypedArray(),
Selection(0, false),
)
class YearFilter : QueryPartFilter("Yil", AnimelerFiltersData.YEARS)
class SeasonFilter : QueryPartFilter("Sezon", AnimelerFiltersData.SEASONS)
val FILTER_LIST get() = AnimeFilterList(
OrderFilter(),
YearFilter(),
SeasonFilter(),
AnimeFilter.Separator(),
GenresFilter(),
StatusFilter(),
ProducersFilter(),
StudiosFilter(),
TypesFilter(),
)
data class FilterSearchParams(
val genres: TaxonomyDto = TaxonomyDto(),
val status: TaxonomyDto = TaxonomyDto(),
val producers: TaxonomyDto = TaxonomyDto(),
val studios: TaxonomyDto = TaxonomyDto(),
val types: TaxonomyDto = TaxonomyDto(),
val order: String = "desc",
val orderBy: String = "total_kiranime_views",
val year: String = "",
val season: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
val (order, orderBy) = filters.getFirst<OrderFilter>().state?.let {
val order = if (it.ascending) "asc" else "desc"
val orderBy = AnimelerFiltersData.ORDERS[it.index].second
Pair(order, orderBy)
} ?: Pair("desc", "total_kiranime_views")
return FilterSearchParams(
filters.parseCheckbox<GenresFilter>(AnimelerFiltersData.GENRES, "genre"),
filters.parseCheckbox<StatusFilter>(AnimelerFiltersData.STATUS, "status"),
filters.parseCheckbox<ProducersFilter>(AnimelerFiltersData.PRODUCERS, "producer"),
filters.parseCheckbox<StudiosFilter>(AnimelerFiltersData.STUDIOS, "studio"),
filters.parseCheckbox<TypesFilter>(AnimelerFiltersData.TYPES, "type"),
order,
orderBy,
filters.asQueryPart<YearFilter>(),
filters.asQueryPart<SeasonFilter>(),
)
}
private object AnimelerFiltersData {
val EVERY = Pair("Seçiniz", "")
val GENRES = arrayOf(
Pair("Action", 10),
Pair("Adult Cast", 459),
Pair("Adventure", 34),
Pair("Aksiyon", 158),
Pair("Antropomorfik", 220),
Pair("Arabalar", 192),
Pair("Aşk Üçgeni", 219),
Pair("Askeri", 184),
Pair("Avangart", 211),
Pair("Bilim Kurgu", 171),
Pair("Büyü", 159),
Pair("CGDCT", 215),
Pair("Childcare", 364),
Pair("Çocuk Bakımı", 216),
Pair("Çocuklar", 206),
Pair("Comedy", 95),
Pair("Comic", 228),
Pair("Dedektif", 221),
Pair("Delinquents", 405),
Pair("Doğaüstü Güçler", 176),
Pair("Dövüş Sanatları", 187),
Pair("Dram", 180),
Pair("Drama", 51),
Pair("Ecchi", 22),
Pair("Fantastik", 160),
Pair("Fantasy", 13),
Pair("Gag Humor", 397),
Pair("Gerilim", 172),
Pair("Girls Love", 65),
Pair("Gizem", 173),
Pair("Gore", 358),
Pair("Gourmet", 473),
Pair("Harem", 170),
Pair("Historical", 359),
Pair("Horror", 119),
Pair("İdol", 225),
Pair("Idols (Female)", 292),
Pair("Isekai", 196),
Pair("Iyashikei", 223),
Pair("Josei", 178),
Pair("Komedi", 168),
Pair("Korku", 174),
Pair("Kumar Oyunu", 222),
Pair("Macera", 161),
Pair("Mahou Shoujo", 214),
Pair("Martial Arts", 425),
Pair("Mecha", 193),
Pair("Medikal", 254),
Pair("Military", 394),
Pair("Mitoloji", 213),
Pair("Music", 522),
Pair("Müzik", 203),
Pair("Mystery", 76),
Pair("Mythology", 316),
Pair("Okul", 179),
Pair("OP M.C.", 541),
Pair("Oyun", 191),
Pair("Parodi", 197),
Pair("Polisiye", 186),
Pair("Psikolojik", 175),
Pair("Psychological", 303),
Pair("Rebirth", 517),
Pair("Reenkarnasyon", 217),
Pair("Reincarnation", 381),
Pair("Revenge", 518),
Pair("Romance", 29),
Pair("Romantic Subtext", 270),
Pair("Romantizm", 181),
Pair("Sahne Sanatçıları", 227),
Pair("Samuray", 188),
Pair("School", 289),
Pair("Sci-Fi", 45),
Pair("Seinen", 183),
Pair("Şeytan", 189),
Pair("Shoujo", 194),
Pair("Shoujo Ai", 212),
Pair("Shounen", 162),
Pair("Shounen Ai", 210),
Pair("Slice of Life", 128),
Pair("Spor", 207),
Pair("Sports", 144),
Pair("Strategy Game", 434),
Pair("Strateji Oyunu", 218),
Pair("Süper Güçler", 177),
Pair("Super Power", 362),
Pair("Supernatural", 49),
Pair("Survival", 415),
Pair("Suspense", 78),
Pair("Tarihi", 185),
Pair("Team Sports", 369),
Pair("Time Travel", 407),
Pair("Uzay", 190),
Pair("Vampir", 182),
Pair("Video Game", 402),
Pair("Visual Arts", 503),
Pair("Workplace", 462),
Pair("Yaşamdan Kesitler", 169),
Pair("Yemek", 204),
Pair("Yetişkin Karakterler", 226),
Pair("Zaman Yolculuğu", 224),
)
val STATUS = arrayOf(
Pair("Airing", 3),
Pair("Completed", 4),
Pair("Not Yet Aired", 244),
Pair("Upcoming", 2),
Pair("Upcomming", 205),
)
val PRODUCERS = arrayOf(
Pair("A-Sketch", 137),
Pair("ABC Animation", 60),
Pair("ADK Emotions", 299),
Pair("ADK Marketing Solutions", 106),
Pair("Ai Addiction", 79),
Pair("Aiming", 384),
Pair("Akita Shoten", 373),
Pair("Amusement Media Academy", 130),
Pair("Animation Do", 340),
Pair("Animax", 491),
Pair("Aniplex", 30),
Pair("APDREAM", 109),
Pair("AQUAPLUS", 236),
Pair("arma bianca", 80),
Pair("ASCII Media Works", 529),
Pair("Ashi Productions", 338),
Pair("Asmik Ace", 347),
Pair("AT-X", 81),
Pair("Atelier Musa", 314),
Pair("Avex Entertainment", 327),
Pair("Avex Pictures", 266),
Pair("B.CMAY PICTURES", 267),
Pair("Bandai", 334),
Pair("Bandai Namco Arts", 74),
Pair("Bandai Namco Entertainment", 336),
Pair("Bandai Namco Filmworks", 231),
Pair("Bandai Namco Music Live", 232),
Pair("Bandai Spirits", 11),
Pair("Bandai Visual", 337),
Pair("BeDream", 284),
Pair("Being", 305),
Pair("Bergamo", 110),
Pair("Beyond C.", 392),
Pair("Bibury Animation CG", 489),
Pair("bilibili", 151),
Pair("Bit grooove promotion", 58),
Pair("BS Asahi", 263),
Pair("BS Fuji", 12),
Pair("BS NTV", 67),
Pair("BS11", 107),
Pair("Bushiroad", 241),
Pair("Bushiroad Creative", 276),
Pair("Bushiroad Move", 277),
Pair("C-one", 131),
Pair("CG Year", 282),
Pair("China Literature Limited", 199),
Pair("Chiptune", 127),
Pair("CHOCOLATE", 482),
Pair("Chosen", 429),
Pair("Chugai Mining", 450),
Pair("Cloud Art", 239),
Pair("Cloud22", 248),
Pair("Contents Seed", 68),
Pair("Crest", 510),
Pair("Crunchyroll", 141),
Pair("CTW", 348),
Pair("Culture Entertainment", 300),
Pair("CyberAgent", 295),
Pair("Cygames", 514),
Pair("DAX Production", 163),
Pair("Days", 269),
Pair("Delfi sound", 557),
Pair("DeNA", 291),
Pair("Dentsu", 91),
Pair("Disney Platform Distribution", 485),
Pair("DMM Music", 96),
Pair("DMM pictures", 97),
Pair("DMM.com", 438),
Pair("Docomo Anime Store", 21),
Pair("Dream Shift", 111),
Pair("dugout", 124),
Pair("Egg Firm", 35),
Pair("Energy Studio", 273),
Pair("Enterbrain", 343),
Pair("Epicross", 297),
Pair("Exa International", 344),
Pair("F.M.F", 513),
Pair("flying DOG", 83),
Pair("Foch Films", 436),
Pair("Frontier Works", 82),
Pair("Fuji Creative", 401),
Pair("Fuji TV", 14),
Pair("Fujimi Shobo", 315),
Pair("FuRyu", 36),
Pair("Futabasha", 349),
Pair("FUTURE LEAP", 350),
Pair("Geek Pictures", 472),
Pair("Genco", 84),
Pair("Geneon Universal Entertainment", 117),
Pair("Gentosha Comics", 376),
Pair("Glovision", 325),
Pair("Good Smile Company", 233),
Pair("Good Smile Film", 458),
Pair("GREE", 37),
Pair("GREE Entertainment", 377),
Pair("Grooove", 342),
Pair("Hakuhodo", 367),
Pair("Hakuhodo DY Media Partners", 15),
Pair("Hakuhodo DY Music &amp; Pictures", 38),
Pair("Hakusensha", 320),
Pair("Half H.P Studio", 25),
Pair("Half HP Studio", 508),
Pair("Happinet Phantom Studios", 246),
Pair("Heart Company", 474),
Pair("High Energy Studio", 447),
Pair("HM Heros", 283),
Pair("Hobby Japan", 464),
Pair("HoriPro International", 307),
Pair("Ichijinsha", 132),
Pair("INCS toenter", 365),
Pair("Infinite", 257),
Pair("INSPION Edge", 408),
Pair("iQIYI", 274),
Pair("IRMA LA DOUCE", 148),
Pair("Jinnan Studio", 102),
Pair("JR East Marketing &amp; Communications", 378),
Pair("Jumondo", 260),
Pair("K contents", 383),
Pair("Kadokawa", 26),
Pair("Kadokawa Media House", 27),
Pair("Kadokawa Shoten", 332),
Pair("Kanetsu Investment", 133),
Pair("Kansai Telecasting", 253),
Pair("KDDI", 393),
Pair("King Records", 237),
Pair("Kizuna AI", 520),
Pair("KLab", 234),
Pair("KlockWorx", 40),
Pair("Kodansha", 98),
Pair("Konami", 339),
Pair("Konami Cross Media NY", 433),
Pair("Konami Digital Entertainment", 301),
Pair("Kuaikan Manhua", 479),
Pair("Kyoraku Industrial Holdings", 157),
Pair("Lantis", 52),
Pair("Lawson", 333),
Pair("Lawson HMV Entertainment", 138),
Pair("Legs", 521),
Pair("LHL Culture", 456),
Pair("MAGES.", 238),
Pair("Magic Bus", 487),
Pair("Magic Capsule", 88),
Pair("MAGNET", 309),
Pair("Mainichi Broadcasting System", 75),
Pair("Marui Group", 360),
Pair("Marvelous", 69),
Pair("Marvelous AQL", 311),
Pair("MediaNet", 85),
Pair("MediBang", 398),
Pair("Medicos Entertainment", 108),
Pair("Medo", 574),
Pair("Micro House", 379),
Pair("Micro Magazine Publishing", 380),
Pair("Mixer", 507),
Pair("Movic", 16),
Pair("Muse Communication", 142),
Pair("My Theater D.D.", 261),
Pair("Nagoya Broadcasting Network", 61),
Pair("NBCUniversal Entertainment Japan", 70),
Pair("NetEase", 388),
Pair("Netflix", 403),
Pair("NewGin", 385),
Pair("NHK", 356),
Pair("NHK Enterprises", 357),
Pair("NichiNare", 439),
Pair("Nichion", 440),
Pair("Nihon Ad Systems", 103),
Pair("Nikkatsu", 368),
Pair("Nippon Animation", 494),
Pair("Nippon Columbia", 28),
Pair("Nippon Television Music", 488),
Pair("Nippon Television Network", 255),
Pair("Nitroplus", 154),
Pair("NTT Plala", 310),
Pair("Overlap", 278),
Pair("Paper Plane Animation Studio", 443),
Pair("Pia", 419),
Pair("Pierrot", 399),
Pair("Pony Canyon", 59),
Pair("Pony Canyon Enterprise", 413),
Pair("PRA", 317),
Pair("Precious tone", 553),
Pair("Production Ace", 293),
Pair("Production I.G", 414),
Pair("Pure Arts", 426),
Pair("Q-Tec", 104),
Pair("Quaras", 477),
Pair("Rakuonsha", 140),
Pair("Ranzai Studio", 428),
Pair("Rialto Entertainment", 149),
Pair("Saber Links", 461),
Pair("Sammy", 72),
Pair("SB Creative", 41),
Pair("Seikaisha", 155),
Pair("Shochiku", 328),
Pair("Shogakukan", 54),
Pair("Shogakukan Music &amp; Digital Entertainment", 478),
Pair("Shogakukan-Shueisha Productions", 153),
Pair("Shounen Gahousha", 123),
Pair("Showgate", 318),
Pair("Shueisha", 32),
Pair("Shufunotomo", 453),
Pair("Sonilude", 135),
Pair("Sony Music Entertainment", 92),
Pair("Sony Music Solutions", 471),
Pair("Sony Pictures Entertainment", 476),
Pair("Sound Team Don Juan", 500),
Pair("Square Enix", 46),
Pair("Starchild Records", 323),
Pair("Starry Cube", 352),
Pair("Straight Edge", 47),
Pair("Stray Cats", 302),
Pair("Studio Easter", 290),
Pair("Studio Hibari", 331),
Pair("Studio Mausu", 48),
Pair("Sumzap", 126),
Pair("Sun TV", 346),
Pair("TBS", 272),
Pair("TC Entertainment", 312),
Pair("Tencent", 445),
Pair("Tencent Animation &amp; Comics", 209),
Pair("Tencent Games", 326),
Pair("Tencent Penguin Pictures", 201),
Pair("TMS Entertainment", 389),
Pair("TO Books", 468),
Pair("Toei animation", 166),
Pair("Toei Video", 113),
Pair("Tohan Corporation", 361),
Pair("Tohjak", 490),
Pair("TOHO animation", 17),
Pair("Tohokushinsha Film Corporation", 143),
Pair("Tokyo MX", 63),
Pair("Toy's Factory", 370),
Pair("Trinity Sound", 329),
Pair("TV Aichi", 294),
Pair("TV Asahi", 371),
Pair("TV Tokyo", 164),
Pair("TV Tokyo Music", 324),
Pair("TVA advance", 470),
Pair("Twin Engine", 281),
Pair("Ultra Super Pictures", 20),
Pair("Universal Music Japan", 353),
Pair("VAP", 256),
Pair("Visual Arts", 64),
Pair("Vobile Japan", 391),
Pair("Wanda Media", 288),
Pair("Warner Bros. Japan", 43),
Pair("WOWMAX", 504),
Pair("WOWOW", 87),
Pair("Xuanshi Tangmen", 448),
Pair("Yahoo! Japan", 396),
Pair("Yokohama Animation Lab", 441),
Pair("Yomiko Advertising", 330),
Pair("Yomiuri Advertising", 73),
Pair("Yomiuri Shimbun", 114),
Pair("Yomiuri Telecasting", 363),
Pair("Yomiuri TV Enterprise", 57),
Pair("Yostar", 558),
Pair("Youku", 268),
Pair("Yuewen Animation &amp; Comics", 467),
)
val STUDIOS = arrayOf(
Pair("2:10 AM Animation", 430),
Pair("8bit", 44),
Pair("A-1 Pictures", 116),
Pair("A.C.G.T.", 265),
Pair("Actas", 568),
Pair("Ajia-do", 526),
Pair("Arvo animation", 145),
Pair("Asahi Production", 509),
Pair("Ashi Productions", 465),
Pair("AtelierPontdarc", 247),
Pair("Axsiz", 567),
Pair("B.CMAY PICTURES", 480),
Pair("Bakken Record", 481),
Pair("Bandai Namco Pictures", 355),
Pair("Bibury Animation Studios", 387),
Pair("Big firebird culture", 93),
Pair("bilibili", 374),
Pair("Blade", 279),
Pair("Bones", 90),
Pair("Brain's Base", 484),
Pair("Bug films", 569),
Pair("BYMENT", 435),
Pair("C-Station", 502),
Pair("C2C", 375),
Pair("CG Year", 411),
Pair("China south angel", 571),
Pair("Chongzhuo Animation", 412),
Pair("Chosen", 285),
Pair("Clap", 540),
Pair("Cloud Hearts", 437),
Pair("CloverWorks", 264),
Pair("Connect", 451),
Pair("CygamesPictures", 519),
Pair("Da huoniao donghua", 94),
Pair("Dancing CG Studio", 304),
Pair("David Production", 152),
Pair("DC Impression Vision", 466),
Pair("Diomedéa", 252),
Pair("Djinn Power", 527),
Pair("DLE", 382),
Pair("Doga Kobo", 115),
Pair("Drive", 296),
Pair("EKACHI EPILKA", 195),
Pair("Elite Animation", 444),
Pair("EMT Squared", 306),
Pair("Encourage Films", 147),
Pair("ENGI", 156),
Pair("feel.", 250),
Pair("Foch", 536),
Pair("Foch Films", 200),
Pair("Gaina", 251),
Pair("Gallop", 432),
Pair("GARDEN", 416),
Pair("Garden Culture", 275),
Pair("Geek Toys", 24),
Pair("Gekkou", 505),
Pair("Geno Studio", 280),
Pair("GIFTanimation", 242),
Pair("Gohands", 561),
Pair("Good smile company", 417),
Pair("Graphinica", 118),
Pair("Haoliners Animation League", 442),
Pair("Heart &amp; soul animation", 455),
Pair("Ilca", 556),
Pair("J.C.Staff", 39),
Pair("Kinema Citrus", 243),
Pair("Kung Fu Frog Animation", 524),
Pair("Kyoto Animation", 341),
Pair("Lapin track", 562),
Pair("Larx entertainment", 563),
Pair("Lay-duce", 409),
Pair("Lerche", 229),
Pair("Liber", 460),
Pair("LIDENFILMS", 139),
Pair("Liyu culture", 532),
Pair("Lx animation studio", 531),
Pair("Madhouse", 120),
Pair("Magic Bus", 308),
Pair("Maho Film", 53),
Pair("MAPPA", 125),
Pair("Millepensee", 495),
Pair("Motion Magic", 287),
Pair("Movic", 418),
Pair("NAZ", 262),
Pair("New deer", 544),
Pair("Nexus", 386),
Pair("Nhk", 533),
Pair("Nhk enterprises", 534),
Pair("Nice Boat Animation", 496),
Pair("Nippon animation", 535),
Pair("Nomad", 134),
Pair("OLM", 410),
Pair("Olm team yoshioka", 564),
Pair("Orange", 452),
Pair("Oriental Creative Color", 528),
Pair("Original force", 530),
Pair("P.A. Works", 62),
Pair("Passion paint animation", 576),
Pair("Passione", 249),
Pair("Pencil Lead Animate", 525),
Pair("Pia", 420),
Pair("Pie in the sky", 554),
Pair("Pierrot", 71),
Pair("Pierrot Plus", 313),
Pair("Pine jam", 538),
Pair("Polygon Pictures", 475),
Pair("Pony canyon", 421),
Pair("Production I.G", 77),
Pair("Project No.9", 112),
Pair("Qingxiang Culture", 427),
Pair("Qiyuan Yinghua", 457),
Pair("Quad", 483),
Pair("Quyue Technology", 493),
Pair("Revoroot", 230),
Pair("Rocen", 498),
Pair("Ruo Hong Culture", 259),
Pair("Satelight", 122),
Pair("Sb creative", 422),
Pair("Seven", 351),
Pair("Seven Arcs", 286),
Pair("Shaft", 235),
Pair("Shenman entertainment", 572),
Pair("Shin-Ei Animation", 486),
Pair("Shirogumi", 552),
Pair("Signal.MD", 523),
Pair("Silver", 146),
Pair("SILVER LINK.", 271),
Pair("Soyep", 202),
Pair("Sparkly Key Animation Studio", 208),
Pair("Staple Entertainment", 240),
Pair("Studio 3Hz", 258),
Pair("Studio 4°C", 395),
Pair("Studio A-CAT", 366),
Pair("Studio bind", 570),
Pair("Studio Blanc.", 449),
Pair("Studio Deen", 86),
Pair("Studio Elle", 511),
Pair("Studio Flad", 400),
Pair("Studio ghibli", 560),
Pair("Studio gokumi", 566),
Pair("Studio Jemi", 245),
Pair("Studio Kafka", 501),
Pair("Studio Kai", 129),
Pair("Studio LAN", 298),
Pair("Studio Lings", 499),
Pair("Studio Mir", 404),
Pair("studio MOTHER", 345),
Pair("Studio Palette", 497),
Pair("Studio Signpost", 492),
Pair("Sunrise", 55),
Pair("Sunrise beyond", 555),
Pair("SynergySP", 506),
Pair("Telecom animation film", 150),
Pair("Tencent Penguin Pictures", 454),
Pair("Tezuka Productions", 66),
Pair("TMS Entertainment", 99),
Pair("TNK", 319),
Pair("Toei Animation", 101),
Pair("Toho", 423),
Pair("Tokyo mx", 424),
Pair("Trigger", 18),
Pair("TROYCA", 390),
Pair("Typhoon Graphics", 512),
Pair("ufotable", 33),
Pair("Wawayu Animation", 515),
Pair("White Fox", 105),
Pair("Wit Studio", 136),
Pair("Wolfsbane", 354),
Pair("Wonder Cat Animation", 446),
Pair("Xuni Ying Ye", 516),
Pair("Yokohama Animation Lab", 372),
Pair("Yostar pictures", 559),
Pair("Youku", 573),
Pair("Yumeta Company", 167),
Pair("Zero-G", 463),
Pair("Zexcs", 537),
)
val TYPES = arrayOf(
Pair("Movie", 165),
Pair("ONA", 89),
Pair("OVA", 121),
Pair("Special", 198),
Pair("TV", 19),
)
val ORDERS = arrayOf(
Pair("Popüler", "total_kiranime_views"),
Pair("Favori", "bookmark_count"),
Pair("Başlık", "title"),
Pair("Yayımlandı", "date"),
Pair("Güncellendi", "kiranime_anime_updated"),
)
val YEARS = arrayOf(EVERY) + (2024 downTo 1990).map {
Pair(it.toString(), it.toString())
}.toTypedArray()
val SEASONS = arrayOf(
EVERY,
Pair("Kış", "winter"),
Pair("Spring", "spring"),
Pair("Summer", "summer"),
Pair("Sonbahar", "fall"),
)
}
}

View file

@ -0,0 +1,41 @@
package eu.kanade.tachiyomi.animeextension.tr.animeler
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://animeler.me/anime/<item> intents
* and redirects them to the main Aniyomi process.
*/
class AnimelerUrlActivity : 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", "${Animeler.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,105 @@
package eu.kanade.tachiyomi.animeextension.tr.animeler.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class SearchResponseDto(val data: String, val pages: Int)
@Serializable
data class PostDto(
val post_title: String,
val post_content: String? = null,
)
@Serializable
data class ThumbnailDto(private val featured_url: JsonPrimitive) {
val url = if (featured_url.isString) featured_url.content else null
}
@Serializable
data class TaxonomyDto(val taxonomy: String = "", val terms: List<Int> = emptyList())
@Serializable
data class SearchRequestDto(
val single: SingleDto,
val keyword: String,
val query: String,
val tax: List<TaxonomyDto>,
)
@Serializable
data class SingleDto(
val paged: Int,
@SerialName("meta_key")
val key: String?,
val order: String,
val orderBy: String,
val season: String?,
val year: String?,
)
@Serializable
data class FullAnimeDto(
val url: String,
val post: PostDto,
val meta: MetaDto,
private val taxonomies: TaxonomiesDto,
private val image: String = "",
private val images: ThumbnailDto? = null,
) {
val thumbnail = image.ifEmpty { images?.url }
val title = post.post_title
@Serializable
data class MetaDto(
val native: String? = null,
val synonyms: String? = null,
val score: String? = null,
val premiered: String? = null,
val aired: String? = null,
val duration: String? = null,
val rate: String? = null,
)
@Serializable
data class TaxonomiesDto(
val producer: List<ItemDto> = emptyList(),
val studio: List<ItemDto> = emptyList(),
val genre: List<ItemDto> = emptyList(),
)
val genres = taxonomies.genre.parseItems()
val studios = taxonomies.studio.parseItems()
val producers = taxonomies.producer.parseItems()
}
@Serializable
data class ItemDto(val name: String)
private fun List<ItemDto>.parseItems() = joinToString { it.name }.takeIf(String::isNotBlank)
@Serializable
data class AnimeEpisodes(val episodes: List<EpisodeDto>)
@Serializable
data class EpisodeDto(
val url: String,
val post: EpisodePostDto,
val meta: EpisodeMetaDto,
) {
@Serializable
data class EpisodeMetaDto(val number: String)
@Serializable
data class EpisodePostDto(val post_modified_gmt: String? = null)
val date = post.post_modified_gmt ?: ""
}
@Serializable
data class SourcesDto(val sourceList: Map<String, String>)
@Serializable
data class VideoDto(val videoSrc: String)