diff --git a/src/all/animeonsen/AndroidManifest.xml b/src/all/animeonsen/AndroidManifest.xml new file mode 100644 index 000000000..7b3ef3e7c --- /dev/null +++ b/src/all/animeonsen/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/all/animeonsen/build.gradle b/src/all/animeonsen/build.gradle new file mode 100644 index 000000000..56e228e8e --- /dev/null +++ b/src/all/animeonsen/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'AnimeOnsen' + pkgNameSuffix = 'all.animeonsen' + extClass = '.AnimeOnsen' + extVersionCode = 1 + libVersion = '13' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..61a1f08f0 Binary files /dev/null and b/src/all/animeonsen/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..4a26d43bc Binary files /dev/null and b/src/all/animeonsen/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..ec6eaa8ac Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..26387f979 Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b575c034e Binary files /dev/null and b/src/all/animeonsen/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/animeonsen/res/web_hi_res_512.png b/src/all/animeonsen/res/web_hi_res_512.png new file mode 100644 index 000000000..2ca8cc988 Binary files /dev/null and b/src/all/animeonsen/res/web_hi_res_512.png differ diff --git a/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt new file mode 100644 index 000000000..8d6f3cc44 --- /dev/null +++ b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AOAPIInterceptor.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.animeextension.all.animeonsen + +import android.util.Base64 +import android.util.Log +import eu.kanade.tachiyomi.network.GET +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response + +class AOAPIInterceptor(client: OkHttpClient) : Interceptor { + + private val token: String + + init { + val cookie = client.cookieJar + .loadForRequest("https://animeonsen.xyz".toHttpUrl()) + .find { it.name == "ao.session" }?.value + ?: client.newCall(GET("https://animeonsen.xyz")).execute().header("set-cookie") + + token = String( + Base64.decode( + java.net.URLDecoder.decode(cookie, "utf-8"), + Base64.DEFAULT + ) + ) + + Log.i("bruh", token) + } + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + val newRequest = originalRequest.newBuilder() + .addHeader("Authorization", "Bearer $token") + .build() + + return chain.proceed(newRequest) + } +} diff --git a/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt new file mode 100644 index 000000000..eb49b909e --- /dev/null +++ b/src/all/animeonsen/src/eu/kanade/tachiyomi/animeextension/all/animeonsen/AnimeOnsen.kt @@ -0,0 +1,168 @@ +package eu.kanade.tachiyomi.animeextension.all.animeonsen + +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.SearchResponse +import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.VideoData +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.network.asObservableSuccess +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import kotlin.Exception + +class AnimeOnsen : 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 + + private val cfClient = network.cloudflareClient + + override val client: OkHttpClient = network.client.newBuilder() + .addInterceptor(AOAPIInterceptor(cfClient)) + .build() + + private val json = Json { + ignoreUnknownKeys = true + } + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + // ============================== Popular =============================== + // The site doesn't have a popular anime tab, so we use the home page instead (latest anime). + override fun popularAnimeRequest(page: Int): Request = + GET("$apiUrl/content/index?start=${(page - 1) * 20}&limit=20") + + override fun popularAnimeParse(response: Response): AnimesPage { + val responseJson = json.decodeFromString(response.body!!.string()) + val animes = responseJson.content.map { + it.toSAnime() + } + val hasNextPage = responseJson.cursor.next.firstOrNull()?.jsonPrimitive?.boolean == true + return AnimesPage(animes, hasNextPage) + } + + // ============================== Episodes ============================== + override fun episodeListParse(response: Response): List { + val episodes = response.asJsoup().select("div.episode-list > a") + return episodes.map { + val num = it.attr("data-episode") + val episodeSpan = it.select("div.episode > span.general") + val titleSpan = it.select("div.episode > span.title") + SEpisode.create().apply { + url = it.attr("href") + .substringAfter("/watch/") + .replace("?episode=", "/video/") + episode_number = num.toFloat() + name = episodeSpan.text() + ": " + titleSpan.text() + } + }.reversed() + } + + override fun episodeListRequest(anime: SAnime): Request { + return GET("$baseUrl/details/${anime.url}") + } + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List