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,7 @@
ext {
extName = 'AnimeOnsen'
extClass = '.AnimeOnsen'
extVersionCode = 7
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View file

@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.animeextension.all.animeonsen
import eu.kanade.tachiyomi.network.POST
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
class AOAPIInterceptor(client: OkHttpClient) : Interceptor {
private val token: String
init {
token = try {
val body = """
{
"client_id": "f296be26-28b5-4358-b5a1-6259575e23b7",
"client_secret": "349038c4157d0480784753841217270c3c5b35f4281eaee029de21cb04084235",
"grant_type": "client_credentials"
}
""".trimIndent().toRequestBody("application/json".toMediaType())
val headers = Headers.headersOf("user-agent", AO_USER_AGENT)
val tokenResponse = client.newCall(
POST(
"https://auth.animeonsen.xyz/oauth/token",
headers,
body,
),
).execute().body.string()
val tokenObject = Json.decodeFromString<JsonObject>(tokenResponse)
tokenObject["access_token"]!!.jsonPrimitive.content
} catch (_: Throwable) {
""
}
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
return chain.proceed(newRequest)
}
}

View file

@ -0,0 +1,192 @@
package eu.kanade.tachiyomi.animeextension.all.animeonsen
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeDetails
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListItem
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListResponse
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.EpisodeDto
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.SearchResponse
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.VideoData
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.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeOnsen"
override val baseUrl = "https://animeonsen.xyz"
private val apiUrl = "https://api.animeonsen.xyz/v4"
override val lang = "all"
override val supportsLatest = false
override val client by lazy {
network.client.newBuilder()
.addInterceptor(AOAPIInterceptor(network.client))
.build()
}
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val json: Json by injectLazy()
override fun headersBuilder() = Headers.Builder().add("user-agent", AO_USER_AGENT)
// ============================== Popular ===============================
// The site doesn't have a popular anime tab, so we use the home page instead (latest anime).
override fun popularAnimeRequest(page: Int) =
GET("$apiUrl/content/index?start=${(page - 1) * 20}&limit=20")
override fun popularAnimeParse(response: Response): AnimesPage {
val responseJson = response.parseAs<AnimeListResponse>()
val animes = responseJson.content.map { it.toSAnime() }
// we can't (easily) serialize this thing because it returns a array with
// two types: a boolean and a integer.
val hasNextPage = responseJson.cursor.next.firstOrNull()?.jsonPrimitive?.boolean == true
return AnimesPage(animes, hasNextPage)
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
GET("$apiUrl/search/$query")
override fun searchAnimeParse(response: Response): AnimesPage {
val searchResult = response.parseAs<SearchResponse>().result
val results = searchResult.map { it.toSAnime() }
return AnimesPage(results, false)
}
// =========================== Anime Details ============================
override fun animeDetailsRequest(anime: SAnime) = GET("$apiUrl/content/${anime.url}/extensive")
override fun getAnimeUrl(anime: SAnime) = "$baseUrl/details/${anime.url}"
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
val details = response.parseAs<AnimeDetails>()
url = details.content_id
title = details.content_title ?: details.content_title_en!!
status = parseStatus(details.mal_data?.status)
author = details.mal_data?.studios?.joinToString { it.name }
genre = details.mal_data?.genres?.joinToString { it.name }
description = details.mal_data?.synopsis
thumbnail_url = "$apiUrl/image/210x300/${details.content_id}"
}
// ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime) = GET("$apiUrl/content/${anime.url}/episodes")
override fun episodeListParse(response: Response): List<SEpisode> {
val contentId = response.request.url.toString().substringBeforeLast("/episodes")
.substringAfterLast("/")
val responseJson = response.parseAs<Map<String, EpisodeDto>>()
return responseJson.map { (epNum, item) ->
SEpisode.create().apply {
url = "$contentId/video/$epNum"
episode_number = epNum.toFloat()
name = "Episode $epNum: ${item.name}"
}
}.sortedByDescending { it.episode_number }
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val videoData = response.parseAs<VideoData>()
val videoUrl = videoData.uri.stream
val subtitleLangs = videoData.metadata.subtitles
val headers = headersBuilder().add("referer", baseUrl).build()
val subs = videoData.uri.subtitles.sortSubs().map { (langPrefix, subUrl) ->
val language = subtitleLangs[langPrefix]!!
Track(subUrl, language)
}
val video = Video(videoUrl, "Default (720p)", videoUrl, headers, subtitleTracks = subs)
return listOf(video)
}
override fun videoListRequest(episode: SEpisode) = GET("$apiUrl/content/${episode.url}")
override fun videoUrlParse(response: Response) = throw UnsupportedOperationException()
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_SUB_KEY
title = PREF_SUB_TITLE
entries = PREF_SUB_ENTRIES
entryValues = PREF_SUB_VALUES
setDefaultValue(PREF_SUB_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)
}
// ============================= Utilities ==============================
private fun parseStatus(statusString: String?): Int {
return when (statusString?.trim()) {
"finished_airing" -> SAnime.COMPLETED
else -> SAnime.ONGOING
}
}
private fun AnimeListItem.toSAnime() = SAnime.create().apply {
url = content_id
title = content_title ?: content_title_en!!
thumbnail_url = "$apiUrl/image/210x300/$content_id"
}
private fun Map<String, String>.sortSubs(): List<Map.Entry<String, String>> {
val sub = preferences.getString(PREF_SUB_KEY, PREF_SUB_DEFAULT)!!
return entries.sortedWith(
compareBy { it.key.contains(sub) },
).reversed()
}
}
const val AO_USER_AGENT = "Aniyomi/app (mobile)"
private const val PREF_SUB_KEY = "preferred_subLang"
private const val PREF_SUB_TITLE = "Preferred sub language"
const val PREF_SUB_DEFAULT = "en-US"
private val PREF_SUB_ENTRIES = arrayOf(
"العربية", "Deutsch", "English", "Español (Spain)",
"Español (Latin)", "Français", "Italiano",
"Português (Brasil)", "Русский",
)
private val PREF_SUB_VALUES = arrayOf(
"ar-ME", "de-DE", "en-US", "es-ES",
"es-LA", "fr-FR", "it-IT",
"pt-BR", "ru-RU",
)

View file

@ -0,0 +1,83 @@
package eu.kanade.tachiyomi.animeextension.all.animeonsen.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonTransformingSerializer
@Serializable
data class AnimeListResponse(
val content: List<AnimeListItem>,
val cursor: AnimeListCursor,
)
@Serializable
data class AnimeListItem(
val content_id: String,
val content_title: String? = null,
val content_title_en: String? = null,
)
@Serializable
data class AnimeListCursor(val next: JsonArray)
@Serializable
data class AnimeDetails(
val content_id: String,
val content_title: String?,
val content_title_en: String?,
@Serializable(with = MalSerializer::class)
val mal_data: MalData?,
)
@Serializable
data class EpisodeDto(
@SerialName("contentTitle_episode_en")
val name: String,
)
@Serializable
data class MalData(
val genres: List<Genre>?,
val status: String?,
val studios: List<Studio>?,
val synopsis: String?,
)
@Serializable
data class Genre(val name: String)
@Serializable
data class Studio(val name: String)
@Serializable
data class VideoData(
val metadata: MetaData,
val uri: StreamData,
)
@Serializable
data class MetaData(val subtitles: Map<String, String>)
@Serializable
data class StreamData(
val stream: String,
val subtitles: Map<String, String>,
)
@Serializable
data class SearchResponse(
val status: Int,
val result: List<AnimeListItem>,
)
object MalSerializer : JsonTransformingSerializer<MalData>(MalData.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement =
when (element) {
is JsonPrimitive -> JsonObject(emptyMap())
else -> element
}
}