Skip to content
This repository was archived by the owner on Jul 8, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/src/main/java/com/exapps/anistream/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.media3.common.util.UnstableApi
import com.exapps.anistream.presentation.navigation.AniStreamNavGraph
import com.exapps.anistream.presentation.root.RootViewModel
import com.exapps.anistream.ui.theme.AniStreamTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
@UnstableApi
class MainActivity : ComponentActivity() {

private val rootViewModel: RootViewModel by viewModels()
Expand All @@ -31,7 +33,10 @@ class MainActivity : ComponentActivity() {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
AniStreamTheme(dynamicColor = rootState.dynamicColors) {
Surface(modifier = Modifier.fillMaxSize()) {
AniStreamNavGraph()
AniStreamNavGraph(
needsAnime3rbSession = rootState.needsAnime3rbSession,
onAnime3rbSessionReady = rootViewModel::syncAnime3rbSession,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.exapps.anistream.core.network

import com.exapps.anistream.core.webview.CloudflareChallengeSolver
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
Expand All @@ -21,9 +20,7 @@ class CloudflareChallengeInterceptor @Inject constructor(
}

response.close()
runBlocking {
solver.ensureClearance(request.url)
}
solver.markChallengeRequired()

return chain.proceed(
request.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface MutableCookieStore {
fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
fun loadForRequest(url: HttpUrl): List<Cookie>
fun saveFromHeader(url: HttpUrl, cookieHeader: String)
fun hasCookies(url: HttpUrl): Boolean
}

@Singleton
Expand Down Expand Up @@ -62,4 +63,6 @@ class InMemoryCookieJar @Inject constructor() : CookieJar, MutableCookieStore {

saveFromResponse(url, cookies)
}

override fun hasCookies(url: HttpUrl): Boolean = loadForRequest(url).isNotEmpty()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.exapps.anistream.core.webview

import android.webkit.CookieManager
import com.exapps.anistream.core.network.BrowserHeaders
import com.exapps.anistream.core.network.MutableCookieStore
import com.exapps.anistream.core.network.WebSessionStore
import okhttp3.HttpUrl.Companion.toHttpUrl
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class Anime3rbSessionBridge @Inject constructor(
private val cookieStore: MutableCookieStore,
private val sessionStore: WebSessionStore,
) {
fun hasSession(): Boolean = sessionStore.hasFreshSession(BASE_URL.toHttpUrl())

fun syncFromWebView(userAgent: String? = null): Boolean {
val url = BASE_URL.toHttpUrl()
val cookieHeader = CookieManager.getInstance().getCookie(BASE_URL).orEmpty()
val resolvedUserAgent = userAgent?.takeIf { it.isNotBlank() }
?: BrowserHeaders.FALLBACK_USER_AGENT
sessionStore.updateUserAgent(resolvedUserAgent)

cookieStore.saveFromHeader(url, cookieHeader)
val hasCookies = cookieStore.hasCookies(url)
if (hasCookies) {
sessionStore.markSessionAcquired(url)
}
return hasCookies
}

companion object {
const val BASE_URL = "https://anime3rb.com/"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.exapps.anistream.core.webview

import okhttp3.HttpUrl

interface CloudflareChallengeSolver {
suspend fun ensureClearance(url: HttpUrl)
fun markChallengeRequired()
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.exapps.anistream.core.webview

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class VisibleCloudflareChallengeSolver @Inject constructor() : CloudflareChallengeSolver {
private val _challengeRequired = MutableStateFlow(false)
val challengeRequired: StateFlow<Boolean> = _challengeRequired.asStateFlow()

override fun markChallengeRequired() {
_challengeRequired.value = true
}

fun markSolved() {
_challengeRequired.value = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi
import androidx.media3.transformer.Composition
import androidx.media3.transformer.EditedMediaItem
import androidx.media3.transformer.ExportException
Expand All @@ -34,6 +35,7 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

@AndroidEntryPoint
@UnstableApi
class AnimeDownloadService : LifecycleService() {

@Inject
Expand All @@ -53,6 +55,7 @@ class AnimeDownloadService : LifecycleService() {
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
when (intent?.action) {
ACTION_CANCEL -> {
activeTransformer?.cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.exapps.anistream.domain.model.UserPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
Expand All @@ -27,6 +28,7 @@ class UserPreferencesDataSource @Inject constructor(
preferSummary = prefs[Keys.PREFER_SUMMARY] ?: false,
cinemaMode = prefs[Keys.CINEMA_MODE] ?: false,
dynamicColors = prefs[Keys.DYNAMIC_COLORS] ?: true,
skipIntroSeconds = prefs[Keys.SKIP_INTRO_SECONDS] ?: 85,
)
}

Expand All @@ -46,10 +48,15 @@ class UserPreferencesDataSource @Inject constructor(
store.edit { it[Keys.DYNAMIC_COLORS] = enabled }
}

suspend fun setSkipIntroSeconds(seconds: Int) {
store.edit { it[Keys.SKIP_INTRO_SECONDS] = seconds.coerceIn(0, 180) }
}

private object Keys {
val AUTO_PLAY_NEXT = booleanPreferencesKey("auto_play_next")
val PREFER_SUMMARY = booleanPreferencesKey("prefer_summary")
val CINEMA_MODE = booleanPreferencesKey("cinema_mode")
val DYNAMIC_COLORS = booleanPreferencesKey("dynamic_colors")
val SKIP_INTRO_SECONDS = intPreferencesKey("skip_intro_seconds")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.exapps.anistream.data.local.WatchlistDao
import com.exapps.anistream.data.preferences.UserPreferencesDataSource
import com.exapps.anistream.data.scraper.AnimeExtractor
import com.exapps.anistream.domain.model.AnimeDetails
import com.exapps.anistream.domain.model.CatalogCategory
import com.exapps.anistream.domain.model.CatalogFilters
import com.exapps.anistream.domain.model.EpisodeStream
import com.exapps.anistream.domain.model.HomeFeed
Expand All @@ -30,6 +31,10 @@ class AnimeRepositoryImpl @Inject constructor(

override suspend fun getHomeFeed(): HomeFeed = extractor.getHomeFeed()

override suspend fun getCatalog(category: CatalogCategory, page: Int, filters: CatalogFilters): PaginatedTitles {
return extractor.getCatalog(category, page, filters)
}

override suspend fun getCatalog(page: Int, filters: CatalogFilters): PaginatedTitles {
return extractor.getCatalog(page, filters)
}
Expand Down Expand Up @@ -155,6 +160,10 @@ class AnimeRepositoryImpl @Inject constructor(
preferencesDataSource.setDynamicColors(enabled)
}

override suspend fun setSkipIntroSeconds(seconds: Int) {
preferencesDataSource.setSkipIntroSeconds(seconds)
}

private fun createEntity(
details: AnimeDetails,
existing: WatchlistAnimeEntity? = null,
Expand Down
Loading
Loading