mirror of
https://github.com/yuzono/anime-extensions.git
synced 2026-10-20 10:51:36 +00:00
* Fix miruro issues
* Revert version
* Fix null titles (again)
* Update src/en/miruro/src/eu/kanade/tachiyomi/animeextension/en/miruro/Miruro.kt
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* Fix review issues
* Add provider toggle
* Improve Miruro provider robustness with retry, fallback, and graceful error handling
- Make decryptResponse graceful (Log.e + return empty instead of throw)
- Eliminate volatile mutable state (currentEpisodeData, currentAnilistId)
- Embed episode data and anilistId in pipe URL JSON for thread-safety
- Derive subTypes dynamically from episodesObj.keys()
- Add safePipeApiCall with retry for 444/5xx errors (2 retries, 500ms delay)
- Build cross-provider episode map at episodeListParse time
- Embed fallback provider data in episode URLs for zero-cost fallback
- Add video-level provider fallback in videoListParse
- Remove dead parseEpisodesFromProvider code
* Bump Miruro extVersionCode to 3
* Refactor Miruro: eliminate volatile state, deduplicate episode paths, refine retry logic
- Replace cachedAnimeMeta @Volatile var with thread-safe LRU LinkedHashMap
cache (max 16 entries, access-order eviction) via getOrCreateMeta/getMeta
helpers - eliminates race conditions from shared mutable state
- Unify three nearly-identical episode building blocks (primary, merge
fallback, non-merge fallback) into a single provider loop with
merge/non-merge guards
- Extract stripHtml() private function from inline HTML stripping chain
- Refine safePipeApiCall to only retry on 429/444/502/503/504 instead
of all 5xx codes
- Bump extVersionCode 3 -> 4
* Fix Miruro: cascade fallback on provider errors, skip 444 retries
- 444 is a permanent 'content not available' from provider, not transient
- Remove 444 from retryable codes in safePipeApiCall
- Return immediately on 444 instead of wasting time retrying
- Primary provider failure now triggers fallback chain immediately
- Track primaryFailed flag from 444 or parse exceptions
- Fallback runs on primaryFailed OR empty videos
- Exponential backoff for transient errors (429/502/503/504)
- 1s, 2s, 3s delays instead of flat 500ms
- maxRetries bumped from 2 to 3
- Fallback tries all sub-types per provider, not just preferred
- Log empty/null streams arrays for easier debugging
- Bump extVersionCode 4 -> 5
* Fix Miruro: handle unreleased series and provider 444 errors gracefully
- Add validateResponse() to catch 500 (unreleased series) and 444
(provider doesn't have content) before decrypting
- animeDetailsParse + episodeListParse now show clear error messages
instead of crashing on garbage response bodies
- IOException messages are user-facing in the host app
- Bump extVersionCode 5 -> 6
* Refactor Miruro: extract DTO and extractor into separate files, remove duplicate code
Split monolithic Miruro.kt into MiruroDto.kt (serialization DTOs and Json parser) and
MiruroExtractor.kt (video extraction, circuit breaker, decryption, fallback traversal).
Remove duplicate code block (second copies of buildPipeRequest, buildPipeQuery,
safePipeApiCall, decryptResponse, etc.), fix nested lambda ambiguity in subtitle
mapping, add missing resolveTitleFromDto method, fix kotlinx.serialization.Json
import path, add jsonObject import, remove unused imports.
* Fix Miruro: use dynamic year generation in filters
Replace hardcoded 2026 year with Calendar.getInstance().get(Calendar.YEAR) so
filter year options stay current without manual updates.
* Bump Miruro extVersionCode to 7
Version bump for refactor and filter improvements.
* Align Miruro with live site: dynamic config, fix provider order, remove kuz
* Fix version
* Fix Miruro: extract media object from /info response to restore descriptions
* Fix Miruro: wire dub filter, make cache thread-safe, fix resource leaks, improve robustness
- Wire DubLanguageFilter into searchAnimeRequest (dub query param)
- Make animeMetaCache thread-safe (Collections.synchronizedMap + synchronized)
- Convert AnimeMeta to regular class with @Volatile on mutable fields
- Fix resource leak in videoListParse (nullable primaryResponse, finally close, 444 early exit)
- Add toIntOrNull guard in episodeListRequest with descriptive IOException
- Add synchronized(this) double-check locking in fetchConfig
- Pre-compile regex patterns as companion constants (BR_REGEX, CLOSE_P_REGEX, HTML_TAG_REGEX, QUALITY_REGEX)
- Extract truncateDescription() and formatSubTypeLabel() helpers (dedup)
- Add TRANSIENT_RETRY_CODES companion constant
- Add explicit JSONArray/JSONObject branches in buildPipeQuery
- Add logging to 5 swallowed exception catch blocks
* Fix Miruro: remove unused code, fix extractor leak, harden types and null safety
- Remove unused baseUrl from MiruroExtractor constructor
- Fix safePipeApiCall resource leak (close before new call, null guard on return)
- Add h-sub/embed cases to extractor subType labels
- Add TRANSIENT_RETRY_CODES companion constant in extractor
- Remove unused DTOs (PipePayload, AnimeBrowseResponse, PageInfoDto, EpisodesResponseDto, ProviderEpisodeDto)
- Remove unused JsonElement import from MiruroDto
- Convert AnilistMalIdResponse and JikanEpisodesDto nested classes to data class
- Make jsonParser internal visibility
- Replace !! with ?. null-safe handling in MiruroFilters.parseCheckbox
- Remove exitProcess(0) from MiruroUrlActivity (was killing host app process)
* Fix Miruro: add genre/tag exclusion filters, remove invalid Hentai genre
Convert GenreFilter and TagsFilter from CheckBox to TriState to support include/exclude/ignore. Wire excludedGenre and excludedTag API params in searchAnimeRequest. Remove Hentai from genre list (not valid per AniList). Bump extVersionCode to 4.
* Fix Miruro: remove unused JsonObject import
* fix(en/miruro): replace provider aliases with real display names
Replace coded aliases (kiwi, bee, hop, etc.) with actual streaming service names (AnimePahe, Anikoto, Zoro, etc.) in preferences and episode scanlator labels. Add PROVIDER_DISPLAY_NAMES map and providerDisplayName() function. Sort videos by preferred provider first. Bump extVersionCode to 3.
* refactor(en/miruro): add provider names to video quality labels and improve extractor logging
Prefix Video.quality with provider display name (e.g. 'AnimePahe - 1080p Sub'). Add providerDisplayName() and PROVIDER_DISPLAY_NAMES to extractor companion object. Improve circuit breaker log messages to show both display name and alias key.
* fix(en/miruro): show display names in preference summary and all providers in scanlator
* fix(en/miruro): default show provider names in scanlator to false and improve title
* fix(en/miruro): aggregate sub-type labels across all providers in scanlator
* fix(en/miruro): filter scanlator to sub/dub only and clean up serialization roundtrip
- Add SCANLATOR_SUB_TYPES whitelist (sub, dub) to filter out non-display
sub-type keys like 'es', 'ssub', 'h-sub' from scanlator label
- Add SUB_TYPE_DISPLAY_ORDER for consistent scanlator label ordering
- Replace org.json ↔ kotlinx.serialization double-serialization with
direct buildFallbackProvidersJson/buildFallbackSubTypesJson helpers
- Fix MutableMap type (Map→MutableMap) resolving 'No set operator' error
- Remove duplicate PROVIDER_DISPLAY_NAMES from MiruroExtractor, delegate
to Miruro.providerDisplayName()
- Bump extVersionCode 2 → 3
* Fix version
* feat(en/miruro): add stream format preference and label HLS/MP4 in video quality
Add Preferred Stream Format preference (HLS/MP4/All, default HLS) that sorts preferred format first as a fallback - non-preferred formats are never discarded, just ranked lower. Include all stream types instead of silently skipping non-HLS. Label stream type (HLS/MP4) in quality string so users can distinguish them. Bump extVersionCode to 3.
* fix(en/miruro): rename MP4 stream preference to Embed to match Miruro terminology
* fix(en/miruro): improve stream format preference labels
* refactor(en/miruro): remove embed providers and stream format preference
- Remove 5 embed providers (bun, nun, twin, cog, telli) from defaultConfig
- Update provider list to HLS-only: kiwi, bee, bonk, ally, moo, hop, dune
- Remove stream format preference (HLS/Embed/First available) — now always HLS
- Remove embed from SUB_TYPE_DISPLAY_ORDER and formatSubTypeLabel
- Remove TRANSIENT_RETRY_CODES and preferredStreamFormat delegate
- Simplify videoListParse — direct parseStreamsFromResponse call, no retry/fallback
- Remove buildFallbackProvidersJson and buildFallbackSubTypesJson helpers
- Bump extVersionCode 3 → 4
* refactor(en/miruro): remove proxy, retry, and fallback logic from extractor and DTOs
- Remove buildProxiedUrl, obfuscateUrl, fnv1a32, PROXY_* constants
- Remove circuit breaker (providerFailureState, FailureState, isProviderCircuitOpen)
- Remove recordProviderFailure/recordProviderSuccess
- Remove safePipeApiCall (retry with exponential backoff)
- Remove fetchFallbackVideos and buildPipeQueryMap
- Simplify parseStreamsFromResponse — remove proxyConfig, cors, episodeId params
- Remove ProxyConfigDto from DTOs
- Simplify ProviderConfigDto — remove parent, relationship, visible, player, proxy, cors, fallback
- Fix referer check: != null → isNotEmpty() (field has default value, never null)
* feat(en/miruro): add AniTaku (twin) and AnimeGG (cog) providers
* feat(en/miruro): dynamically fetch providers from config API
Fetch available providers from the site's config API on extension load
instead of hardcoding them. Filters out embed providers (identified by
relationship=embed + player=iframe in config) so only native providers
appear in preferences and episode resolution.
- Add parent, relationship, visible, player fields to ProviderConfigDto
- Replace static PROVIDER_DISPLAY_NAMES/PREF_PROVIDER_ENTRIES/VALUES
with mutable instance vars populated from config API
- MiruroExtractor now takes resolveDisplayName lambda instead of
calling static companion method
- Auto-reset stale preferred provider when config changes
- Episode fallback respects config provider priority order
- findMaxEpisodeNumber scans all providers when preferred is missing
- Remove twin/cog from defaults (they are embeds of bonk/moo)
- Bump extVersionCode to 6
* fix(en/miruro): remove duplicate providerOrder local variable
The same val providerOrder was declared twice in the same function
scope (line 549 and 592), causing a compile error. Removed the second
declaration since the first is already in scope.
* Spotless
* fix(en/miruro): extract m3u8 from kwik embed pages via JsUnpacker
Embed streams returned kwik.cx HTML page URLs as playable video URLs, causing
unsupported file format errors. Now fetches the kwik /e/ page, unpacks the packed
JavaScript, and extracts the actual .m3u8 URL. Also adds isActive field to StreamDto.
* feat(en/miruro): add stream type preference, highest available quality, and more truncation options
- Add Preferred Stream Type preference (HLS/Embed/All, default HLS) that filters
videos in sort with fallback to full list if filter yields empty
- Add Highest Available as default quality option (sorts by actual resolution
descending instead of pinning a specific quality)
- Add 750 and 75 character description truncation options
* feat(en/miruro): async config fetch, dynamic provider mapping, and debug logging
Migrate synchronous blocking config fetch to cached async system with
SharedPreferences persistence. Config is fetched on extension load and
re-fetched on mirror change; cold starts use cached prefs until fresh
data arrives.
Add dynamic provider alias-to-display-name mapping using config API data:
- Parent-aware naming for embed providers (e.g. 'Bun (via Bee)')
- Respect visible flag to filter out hidden providers
- Append capability labels to preference entries (e.g. 'AnimePahe (Sub, Download)')
Fix ProxyConfigDto deserialization — API returns boolean for embed
providers but object for native providers. Add ProxyConfigSerializer to
handle both forms gracefully.
Add comprehensive Log.d debug logging throughout Miruro.kt and
MiruroExtractor.kt. Use TAG constant instead of hardcoded strings.
Bump extVersionCode 4 → 6.
* feat(en/miruro): add MAL episode title support via Jikan API
Fetch episode titles from Jikan alongside filler data in a single pagination
pass. Cache titles in AnimeMeta, resolve via new resolveEpisodeTitles()
method with cache-first logic that shares Jikan calls with filler resolution.
Add Episode Title Source preference (MAL/Provider) defaulting to MAL.
Provider titles serve as fallback when no MAL title is available for a
given episode number.
* chore(en/miruro): bump extVersionCode for MAL episode titles
* fix(en/miruro): use lenient Json parser for proxy config deserialization
The ProxyConfigSerializer used the default Json instance which does not
ignore unknown keys, causing a warning when the API returns new fields
like 'segments' in the proxy config object. Switch to jsonParser which
has ignoreUnknownKeys = true.
* feat(en/miruro): add dynamic mirror fetching from status page API
Fetch available mirrors from status.miruro.com API, filter by heartbeat
health status, persist to SharedPreferences, and populate mirror
preference dynamically. Migrate rateLimitHost calls to kotlin.time
Duration API.
* chore(en/miruro): bump extVersionCode for dynamic mirrors
* fix(en/miruro): place episode 0 before episode 1 in all sort orders
Separate episode 0 entries from the main list and prepend them
to the front, so descending sort yields 0, 12, 11, ..., 1 instead
of 12, 11, ..., 1, 0. Ascending order was already correct but is
now explicit about the split.
* miruro: add missing providers (moo, cog) and improve embed extraction
- Add moo (Moon) and cog (Cog) to provider defaults and display names
- Add dune and kuz to known display names
- Replace custom kwik embed extraction with OmniEmbedExtractor
- Add fallback provider support when primary provider returns no videos
- Bump extVersionCode to 10
* lib: add omniembedextractor and kwikextractor libraries
* Integrate all video extractor libs into OmniEmbedExtractor
Add 26 extractors beyond the original 8 (34 total):
- Tier 1 (simple): Voe, Streamlare, StreamHub, VidGuard, Sendvid, StreamDav, StreamSilk, Vido, Vudeo, Upstream, Sibnet, Rumble, Amazon, Fusevideo, Lulu, Buzzheavier, Fastream, VidBom, PixelDrain, MixDrop
- Tier 2 (suspend): VidMoly, VidHide, StreamPlay, StreamUp, Uqload, Blogger
Domain mappings cover 50+ embed domains used across extensions.
* miruro: address code review feedback
- Remove unused companion providerDisplayName (instance method handles fallback)
- Remove unused referer param from OmniEmbedExtractor.extractVideos
- Derive Jikan maxPages from maxEpisode instead of hard-coding 10
- Gate Log.d calls behind BuildConfig.DEBUG via logD helper
- Track mirror fetch job and add close() to cancel CoroutineScope
* Revert unnecessary changes
* Revert "Revert unnecessary changes"
This reverts commit c05093891f.
* Revert UrlActivitiy
* Cleanup
* Suggestion
* refactor
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* PR changes
* Fix okhttp
* lib: add anilib library for AniList GraphQL API
* refactor(en/miruro): replace Jikan and inline AniList code with anilib
* Init
* fix(cloudscraper): address code review compilation and runtime issues
- Evaluate URL_HELPER before shims that reference __cf_parseUrl (BrowserEnvironment)
- Add missing kotlinx.serialization.Serializable import for CvParams (JsdSolver)
- Fix isLenientOnJson -> isLenient in Json builder (JsdSolver)
- Fix key-quoting regex to produce valid JSON with opening quotes (JsdSolver)
- Replace non-existent Cookie.EXPIRES_AT_OTHER with direct expiresAt comparison (CookieCache)
- Remove unused defaultTtlMillis constructor parameter (CookieCache)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(en/miruro): add "Include all provider streams" preference
Adds a settings toggle (default off) that controls whether video streams
from all available providers are included in the player, or only the
preferred provider's streams.
When off (default): only the preferred provider's streams are shown
(fallbacks still used when primary is empty).
When on: streams from all providers are fetched and shown.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(lib/anilib): expand AniLib with rate-limit handling, caching, paginated queries, ani.zip, and description builder
- Add rate-limit handling: throttle() enforces min interval (667ms normal,
2000ms degraded), handle429() reads Retry-After, handleRateLimitHeaders()
reads X-RateLimit-Remaining, state persisted to SharedPreferences
- Add inline SharedPreferences caching with configurable TTL (default 15min)
- Add fetchSearch, fetchTrending, fetchSeasonal, fetchRelatedMedia queries
- Add fetchEpisodeTitles() for ani.zip integration returning EpisodeTitlesResult
- Expand MediaSnapshot with format, episodes, duration, season, seasonYear,
startDate, endDate, synonyms, averageScore, popularity, tags, relations,
characters, trailer, countryOfOrigin, source, siteUrl
- Add PageData, PageInfo, FuzzyDate, MediaTag, MediaConnection, MediaEdge,
RelatedMediaNode, CharacterNode, CharacterName, CharacterImage, Trailer,
AnizipResponse, AnizipEpisode, EpisodeTitlesResult DTOs
- Migrate executeQuery() to graphQLPost()/parseGraphQLAs() from core
- Add buildDescription() extension on MediaSnapshot with DescriptionOptions
* refactor(en/miruro): prioritize AniLib data over pipe API in anime details, enrich episodes with ani.zip titles
- animeDetailsParse now tries AniLib.fetchMediaDetails() first for richer
AniList GraphQL data, falling back to pipe API DTO then raw JSONObject
- Add buildFromSnapshot() using AniLib.resolveTitle/resolveCoverUrl/
resolveMainStudio and buildDescription()
- Add buildFromDto() using same AniLib helpers via MediaTitle/MediaCoverImage/
MediaStudios construction
- Extract mapStatus() as shared helper for all three parse paths
- Remove resolveTitleFromDto() (fully replaced by AniLib.resolveTitle())
- Replace FQCN aniyomi.lib.anilib.X with short-form imports
- Enrich episode names from ani.zip when only "Episode X" is available
* fix(en/miruro): bump extVersionCode to 6
* feat(lib/anilib): add AniFiller integration with episode classification types and cached dataset fetch
* feat(en/miruro): add filler episode handling preferences and integrate AniFiller data in episode list
* fix(lib/anilib): fix GraphQL deserialization, add unified filter/browse method, migrate Miruro browse to AniLib
Replace AniListResponse<T> with correct DTO types for parseGraphQLAs.
parseGraphQLAs already unwraps the GraphQL {data} envelope, so
AniListResponse<T> (which has its own data field) caused a
mismatch — after unwrapping, JSON is {Page:{...}} not
{data:{Page:{...}}}.
- Add PageDataWrapper with @SerialName("Page") property matching
post-unwrapping JSON shape (same pattern as MediaData with
@SerialName("Media"))
- Change all executeQuery calls: Page queries use PageDataWrapper,
Media queries use MediaData directly
- Remove AniListResponse<T> (no longer needed)
- Add MediaFilter data class with toVariables() for parameterized
GraphQL queries
- Add fetchFilteredMedia with FILTERED_MEDIA_QUERY supporting all
AniList filter parameters
- Refactor fetchSearchAnime/fetchTrending/fetchSeasonal to delegate
to fetchFilteredMedia
- Add convenience wrappers: fetchPopular, fetchUpcoming,
fetchRecentlyUpdated
- Migrate Miruro browse/search from pipe API to AniLib directly
- Add toMediaFilter() extension on FilterSearchParams
- Add animeFromMediaSnapshot() converter and applyDubPostFilter()
- Bump extVersionCode to 8
* refactor(lib/anilib): restyle description builder with markdown formatting and star ratings
Use markdown bold labels, star ratings, and structured line-per-field
layout instead of plain Unicode symbols (⊛⊞│) and cramped single-line
info. This matches the styling patterns used by Animetsu, Cineby, and
AnikotoTheme in the repo.
- Add 5-star score visualization (★★★★☆ 85%) replacing plain ★ 85%
- Replace Unicode bullets with **Bold:** labels for genres/tags/studio
- Replace │ inline separator with line-per-field layout for readability
- Use [linked titles] for relations when AniList siteUrl is available
- Use [▶ Watch Trailer](url) instead of raw Trailer: URL
- Use *italic* for synonyms section
- Use --- horizontal rule before synopsis instead of bare em-dash
- Use Airing instead of Releasing for RELEASING status
- Use ordinal episodes (12 episodes) instead of abbrev (12 eps)
- DescriptionOptions API unchanged — fully backward-compatible
* Fix title pref
* fix(lib/anilib): fix AnizipEpisode deserialization, add airing dates and filler episode data
- AnizipEpisode.title was String? but API returns JSON object (en/x-jat/ja), causing silent null. Add AnizipTitle DTO and resolvedTitle computed property.
- AnizipEpisode had no @SerialName for aired field; add airDate/airdate with correct SerialNames and resolvedAirDate computed property.
- Make EpisodeTitlesResult @Serializable with pre-parsed airDates map.
- Add FillerEpisodeData(type, airDate, title) DTO with parsed epoch millis.
- Add @Serializable to FillerType enum (was missing, blocked compilation of FillerEpisodeData).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(lib/anilib): add caching and date parsing to fetchAiringSchedule, fetchEpisodeTitles, fetchFillerData
- Add SharedPreferences caching (15-min TTL) to fetchAiringSchedule via executeQuery with cacheKey.
- Extract extractAiringSchedule(snapshot) and buildScheduleMap(media) as public helpers to reuse schedule data from already-fetched fetchMediaDetails cache.
- Add SharedPreferences caching (15-min TTL) to fetchEpisodeTitles; parse resolvedAirDate via anizipDateFormat.tryParse() into airDates map.
- Rewrite fetchFillerData to return FillerEpisodeData with parsed airDate (epoch millis via anifillerDateFormat.tryParse) and title from AniFillerEpisode.
- Move anizipDateFormat and anifillerDateFormat lazy vals above KDoc blocks for correct doc-comment association.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(en/miruro): fix episode airing dates, titles, and filler marks with enriched fallback chains
- Rewrite episodeListParse enrichment: try fetchMediaDetails cache first for schedule data (reuses 15-min cache), fall back to standalone fetchAiringSchedule only on failure.
- Date fallback chain: AniList airingSchedule → ani.zip airDates → AniFiller airDate.
- Title enrichment: try ani.zip resolvedTitle first, then AniFiller title as last resort.
- Filler marking via FillerEpisodeData with [Filler], [Mixed Canon], [Anime Canon] scanlator tags.
- Use Math.round() instead of .toInt() for Int map lookups to fix Float truncation bug on fractional episodes.
- Bump extVersionCode 8 → 10.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* https://developer.android.com/studio/known-issues#android-studio-quail-1-parallel-sync-execution-issue
* fix(lib/anilib): make AniFillerMappings.malId nullable to handle null JSON values
AniFiller dataset can contain null mal_id for anime without a MyAnimeList entry. The non-nullable Int type caused kotlinx.serialization to throw 'Unexpected symbol n in numeric literal' at path $[175].mappings.mal_id.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(en/miruro): remove brackets from filler episode scanlator marks
Change filler mark labels from [Filler], [Mixed Canon], [Anime Canon] to Filler, Mixed Canon, Anime Canon in episode scanlator field.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(en/miruro): add CloudScraperInterceptor and zstd decompression for pipe API requests
Miruro.tv pipe API requests were failing with HTTP 403 due to Cloudflare
TLS fingerprinting blocking OkHttp's non-browser TLS stack. Additionally,
the server now responds with content-encoding: zstd instead of gzip.
Changes:
- Add CloudScraperInterceptor (QuickJS-based headless CF bypass) to OkHttp client
- Add ZstdDecompressInterceptor as a network interceptor for zstd decompression
- Bump extVersionCode 10 -> 11
- Add dependencies: lib:cloudscraper, com.github.luben:zstd-jni:1.5.6-9@aar
The CloudScraperInterceptor solves JSD, legacy IUAM, and managed CF challenges
without a WebView, and caches cf_clearance cookies across requests. The
ZstdDecompressInterceptor strips the Content-Encoding header and decompresses
the response body before OkHttp's BridgeInterceptor processes it.
* fix(cloudscraper): resolve compilation errors in cloudscraper library
Cloudscraper library from feature/cflib merge had several compilation issues:
- BrowserEnvironment: const val with trimMargin() requires runtime
evaluation; changed URL_HELPER to @JvmField val
- ChallengeDetector/JsdSolver: $cv and $params interpreted as Kotlin
string templates; escaped as ${'$'}cv and ${'$'}params
- JsdSolver: extractCookies returns immutable Map<String,String>;
changed to toMutableMap() for putAll support
- LegacyIuamSolver/ManagedChallengeSolver: non-local return statements
inside withQuickJsTimeout lambda (not inline); restructured to use
captured result vars returned after the lambda completes
Also fixes in Miruro.kt:
- Buffer.writeTo(zstd) corrected to Buffer.readFrom(zstd) (InputStream vs OutputStream)
- Removed broken .addHeader() call on OkHttpClient.Builder (not a valid API)
* fix(cloudscraper): resolve concurrent QuickJS stack overflow
- Replace per-call Executors.newSingleThreadExecutor() with shared
ThreadPoolExecutor(4, 4, 60s) daemon threads in QuickJsExt
- Add explicit StackOverflowError catch mapped to CloudscraperException
- Ensure engine is always closed in finally block
- Remove @Synchronized from CloudScraperInterceptor.intercept() which
was serializing all HTTP requests unnecessarily
- Per-host locking via CookieCache.lockForHost() already handles
concurrent access correctly
* fix(cloudscraper): harden browser environment and JS solvers
BrowserEnvironment:
- Add JS-level recursion guard (RECURSION_GUARD_SHIM) with
__cf_callDepth counter and __cf_maxCallDepth=256 limit
- Timer callbacks reset call depth before execution for fresh budget
- Add WebGLRenderingContext and WebGL2RenderingContext stubs with
full extension object hierarchy
- Add AudioContext and OfflineAudioContext stubs with realistic
method signatures (createOscillator, createAnalyser, createGain)
- Add Intl.DateTimeFormat, Intl.NumberFormat, and Intl.Collator stubs
with functional format/compare methods
Security hardening:
- Fix JS injection in ManagedChallengeSolver and LegacyIuamSolver:
escape backslashes and double quotes in domain string interpolation
- Add protocol whitelist (javascript:/data:) to resolveUrl() in all
three solvers (JsdSolver, ManagedChallengeSolver, LegacyIuamSolver)
to prevent SSRF via crafted challenge URLs
* feat(cloudscraper): add TLS/JA3 fingerprint spoofing via impersonator-bctls
Integrate zhkl0228/impersonator (BouncyCastle-based TLS fingerprint
spoofing) to make CloudScraper's HTTP requests appear as real Chrome
browser TLS handshakes to Cloudflare.
- Add impersonator-bctls 1.0.13 dependency (pure Java, no native code,
no OkHttp classpath conflicts)
- Create impersonated OkHttpClient internally using ImpersonatorFactory
.chrome() with spoofed SSLContext for Chrome's JA3 fingerprint
- All challenge-solving requests and cookie-carrying retries now use the
impersonated client, bypassing Cloudflare's TLS fingerprint checks
- Add impersonatedClient constructor parameter for users who want to
provide their own pre-configured client
- Use solveClient.newCall() directly for retry instead of chain.proceed()
to ensure the TLS fingerprint is applied to the final request
* fix(cloudscraper): use impersonator-bctls 1.0.12 (latest on JitPack)
* fix(cloudscraper): use ImpersonatorFactory.android() (chrome() doesn't exist)
* feat(en/miruro): shape requests with browser-fingerprint interceptor to bypass WAF block; drop zstd-jni native dep
Miruro's Cloudflare edge enforces a WAF custom-rule block (not a solvable
challenge — the body is the 'Sorry, you have been blocked' page with
cf-error-details and no cf-mitigated header) on pipe-API requests whose
header set does not match a real Chrome browser's fetch(). Live curl
against /api/secure/pipe?e=<base64url> with only UA+Accept+Referer
returns 403; the same probe with the full browser-shape set
(sec-fetch-*, sec-ch-ua-*, Origin, Accept-Language q=0.9, zstd opt-in)
returns 200 + x-obfuscated: 2.
CloudScraperInterceptor cannot fix custom-rule blocks — its isBlockPage
path passes through unresolved by design (no challenge to solve). Rewiring
miruro through the lib was a dead end.
Fix: add MiruroBrowserFingerprintInterceptor, an internal network
interceptor that browser-shapes every miruro request with a Chrome 148
desktop fingerprint. Classifies pipe-context (path /api/*: Sec-Fetch-Dest
empty, Sec-Fetch-Mode cors, Sec-Fetch-Site same-origin, Origin set) vs
navigate-context (warmup GET / and /watch/<id>: Sec-Fetch-Dest document,
Sec-Fetch-Mode navigate, Sec-Fetch-Site none on cold, no Origin).
Overwrites WAF-fingerprint headers (Accept-Encoding/Accept-Language/
User-Agent/Sec-Ch-Ua/Sec-Ch-Ua-Mobile/Sec-Ch-Ua-Platform/Sec-Fetch-*/
Origin) so caller intent cannot leak a bot signal; fill-if-absent on
body-shape headers (Accept, Referer).
Stripped the now-redundant manual UA/Accept/Accept-Language/Referer
from ensureBaseVisit, ensureWatchPageVisited, and buildPipeRequest
(now bare GET(url) + bare Request.Builder().url(...).build()) — the new
interceptor is the single source of truth for fingerprint shaping.
Bumped USER_AGENT from private to internal so the same-package
interceptor can read it.
Second prod crash unmasked by the WAF fix: java.lang.NoClassDefFoundError
clinit failed for com.github.luben.zstd.ZstdInputStreamNoFinalizer. The
zstd-jni native library (libzstd-jni-*.so) wasn't being packaged into the
extension APK under kei.plugins.extension.legacy — the static initializer
that calls System.loadLibrary fails on real Android devices (confirmed on
Samsung SM-S928U, Android 16). Before this fingerprint fix, the WAF
returned 403 HTML (Content-Encoding: identity), so ZstdDecompressInterceptor
short-circuited and never instantiated ZstdInputStream; now that the WAF
passes us through, server honored our zstd advertisement and the broken
native-lib path executed for the first time in prod.
Fix for the crash: drop the zstd token from Accept-Encoding (advertise
only gzip, deflate, br), remove the ZstdDecompressInterceptor class
entirely, and drop the zstd-jni AAR dependency. The server responds with
content-encoding: br instead, which aniyomi's host okhttp3.
CompressionInterceptor handles transparently (no native lib). Verified
via live curl + full decode walk: brotli-decompress → base64url-decode →
XOR with PIPE_KEY → gzip-decompress (XOR'd payload has gzip magic
1f 8b 08 00) → valid UTF-8 JSON {streaming:{bonk:{...},...}}.
MiruroExtractor.decryptResponse already does base64url→XOR→GZIPInputStream,
so it needs zero changes.
Also dropped the now-unused project(':lib:cloudscraper') dep from
miruro gradle — the lib itself is untouched and still available for
other extensions that need challenge-solving.
Build verification: cleaned and assembled successfully on Windows.
Real-device test on Samsung SM-S928U / Android 16 / WebView 149.0.7827.163
passed (no NoClassDefFoundError, pipe API returns real JSON)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(lib/cloudscraper): remove obsolete LegacyIuam and ManagedChallenge solvers
These solver paths produced zero production wins on modern Cloudflare edge
configs and were already documented as removed in the parent interceptor's
own KDoc. Modern CF managed v2/v3 challenges ('Just a moment…') require
fingerprint signals (real canvas/WebGL rendering, pointer events, focus
state) that are not reproducible in a headless QuickJS DOM shim — the
math-based JSchl computation that LegacyIuamSolver/ManagedChallengeSolver
performed is no longer sufficient to earn cf_clearance on edges that have
escalated to challenge v2/v3.
The CloudScraperInterceptor now relies on its WebView fallback path
(aniyomi.lib.cloudflareinterceptor.CloudflareInterceptor) for unclassifiable
or managed challenges; the JSD fast-path (JsdSolver) handles the simpler
window.__CF$cv$params variants that DO solve in headless environments.
Files removed:
* ManagedChallengeSolver.kt — QuickJS-DOM-shim attempt at managed v2/v3
challenges (the approach pioneered by go-cfscraper). Failed on modern
edges for the canvas/WebGL/pointer/focus reasons cited above.
* LegacyIuamSolver.kt — Cloudflare IUAM (I'm Under Attack Mode) and V1
classic JS challenges (jschl_vc / jschl_answer / pass). Both mostly
extinct on modern CF; legacy edges that still serve them are typically
solvable by JsdSolver's pre-challenge detection path instead.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(lib/cloudscraper): rewrite JSD solver, browser environment, and QuickJS worker config
Rewrites the three coupled JSD-solver pieces as one unit so each one
compiles against a consistent sibling API. This commit would not build
if split (each file references members the others expose in their new
shape), so all three land together.
* BrowserEnvironment.kt — Refactored from one monolithic shim block into
modular constant blocks (URL_HELPER, RECURSION_GUARD_SHIM, CONSOLE_SHIM,
TIMERS_SHIM, ATOB_BTOA_SHIM, TEXT_CODEC_SHIM, windowShim, NAVIGATOR_SHIM,
SCREEN_SHIM, documentShim, PERFORMANCE_SHIM, CRYPTO_SHIM, MISC_SHIM).
Each block is evaluated in its own QuickJs.evaluate call so a syntax
error in one block surfaces with a clear boundary instead of aborting
the entire install. Fingerprint values (navigator.userAgentData.brands,
navigator.platform, sec-ch-ua, screen dimensions, language) are now
derived from the supplied userAgent string so the navigator/brands/
platform all agree — Cloudflare flags mismatches between reported UA
and navigator.userAgentData.brands / navigator.platform.
* JsdSolver.kt — Adds initialCookies passthrough so cookies warmed via
the host client's jar reach the QuickJS script execution; tracks the
sensor POST URL separately (SensorResult.challengeUrl) for telemetry;
tightens retry to RETRYABLE_ERRORS only (script-content errors like
NO_SENSOR_PAYLOAD / NO_CLEARANCE_COOKIE are deterministic for a fixed
script+env so retrying them only burns another scriptTimeoutMs);
drops scriptTimeoutMs default from 10s to 8s for faster fallback to
WebView.
* QuickJsExt.kt — Worker thread stack size raised to 16 MB (private
WORKER_STACK_SIZE) so both QuickJs.create() and evaluate() run on the
same 16 MB-stack thread. Without this, the CashApp binding sizes
QuickJS's internal 1 MB C-stack limit from the creating thread, so
creating the engine on a small-stack thread and evaluating on a
large-stack worker causes an instant stack overflow on the very first
evaluate call (related to the prior 'resolve concurrent QuickJS stack
overflow' commit but stabilizing the worker thread itself too).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(lib/cloudscraper): harden interceptor + detector + cookie cache and add diagnostics
Pairs together the runtime-side hardening flow + a new debug helper.
* CloudScraperInterceptor.kt — Removed the broken impersonator-bctls
SSLContext path (BCJSSE raised TlsFatalAlert during TLS 1.3 handshake
on every modern CF edge — see upstream issues zhkl0228/impersonator#8/#9)
and now layers keiyoushi.lib.tlsspoof.SpoofedTlsSupport on the host
client builder instead. Restated the KDoc as a hybrid solver: headless
JSD fast path + WebView fallback for unclassifiable/managed challenges.
The X-Cloudscraper-Skip marker header on internal retry leg is preserved
for downstream consumer diagnostics.
* ChallengeDetector.kt — Reduced the challenge taxonomy to binary
JSD / UNSOLVABLE. The old multi-type taxonomy (MANAGED / LEGACY_IUAM /
TURNSTILE) carried metadata only the dead solvers consumed; modernized
to match the 'no managed/legacy solver' reality.
* CookieCache.kt — make lockForHost non-reentrant (a second attempt by
the same thread returns false so the lock now guards against duplicate
solve attempts within the same call stack). unlockForHost remove the
ReentrantLock from the hostLocks map when its queue is empty, so the
map no longer grows one entry per host ever visited.
* CloudScraperDebug.kt (NEW) — Runtime-flag-gated diagnostic tiers
(always / verbose / trace) for log-noisy JSD sensor debugging. Flags
are runtime not compile-time, so a developer can flip them on without
rebuilding cloudscraper itself.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* chore(lib/cloudscraper): wire test sourceset and add JSD solver unit + integration tests
Splits build config + tests + fixtures together because the test
sourceset needs the new build.gradle.kts deps to compile against
cloudscraper's main sourceset; splitting would leave the new test
sourceset non-compiling and the build config change meaningless
without tests.
build.gradle.kts:
* Adds the 'test' android sourceSet wired to test/java + test/kotlin
so JVM unit tests can run against this android library module.
* testOptions: isIncludeAndroidResources=false, isReturnDefaultValues=true
so Log.w/Log.e calls in the unit under test don't try to load the
Android framework (we are testing parsing/cache/regex logic, not
real-device logging).
* Replaces implementation(libs.impersonator) with
implementation(project(":lib:cloudflareinterceptor")) and
implementation(project(":lib:tlsspoof")) — the runtime path now uses
keiyoushi.lib.tlsspoof.SpoofedTlsSupport instead of the broken
impersonator-bctls SSLContext path (BouncyCastle JSSE raised
TlsFatalAlert during TLS 1.3 handshake on every modern CF edge —
see upstream issues zhkl0228/impersonator#8/#9).
* Adds testImplementation deps for kotlin-test-junit, okhttp,
kotlinx.serializaton.json, and quickjs so tests can construct
HttpUrl/Cookie instances, parse JSON, and run the real sensor
script under QuickJS for integration coverage.
Tests (test/kotlin/keiyoushi/lib/cloudscraper/, 6 files):
* ChallengeDetectorTest.kt — verifies JSD detection, UNSOLVABLE
classification of managed/legacy/turnstile/403/503 variants, and
regex-pattern extraction of __CF$cv$params metadata.
* CookieCacheTest.kt — covers cache hit/miss/stale/expiry, per-host
lock non-reentrancy (the new lockForHost behavior from the prior
commit), and hostLocks-map cleanup when the queue is empty.
* JsdSolverExtractCookiesTest.kt — Set-Cookie parsing from sensor
response, including the cf_clearance cookie and any sibling cookies
the challenge endpoint emits.
* JsdSolverExtractDeobfuscationPiecesTest.kt — verifies the
deobfuscation-piece extraction (D-array entries, handler chains)
from a real captured cf_jsd_main.js sensor script.
* JsdSolverParseCvParamsTest.kt — parses a real __CF$cv$params JSON
blob into the typed struct the solver consumes.
* JsdSolverRealScriptTest.kt — end-to-end integration: loads the
real captured cf_jsd_main.js fixture + cf_jsd_sensor_payload.txt,
runs the full QuickJS install-evaluate-sensor-POST path, asserts
a non-empty sensor payload is produced. Marked as the slowest test
in the suite but kept in the regular unit run for regression
coverage on the JSD fast path.
Fixtures (test/fixtures/, 4 files):
* cf_challenge_403.html — a captured managed-challenge 403 page used
by ChallengeDetectorTest to verify UNSOLVABLE classification of
non-JSD challenges.
* cf_jsd_main.js — the real captured JSD sensor script used by
JsdSolverExtractDeobfuscationPiecesTest and JsdSolverRealScriptTest.
Single-line file (minified source) so line is long; fixture kept
as one file rather than reformatted so byte-for-byte matches what
Cloudflare serves.
* cf_jsd_sensor_payload.txt — the request body the sensor script
POSTs to /__cf_chl_rcv, used by JsdSolverRealScriptTest as the
expected output shape for the replicated sensor generation.
* cf_jsd_sensor_response.txt — empty placeholder documenting the
response shape from the challenge endpoint (real responses are
server-generated and short-lived; left empty so we don't pin to
one CF variant's content).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(lib:tlsspoof,lib:cloudflareinterceptor,gradle): add TLS spoofing support and update CloudflareInterceptor fallback
Adds the new :lib:tlsspoof module + wires it into both the Cloudflare
fallback path and the version catalog as one atomic unit. The five
files land together because each pair would otherwise be broken:
- tlsspoof's build.gradle.kts consumes the conscrypt dep that the
decorator-domain KDoc in TlsSpoof.kt references
- SpoofedTlsSupport.applyTo() calls TlsSpoof.install()
- CloudflareInterceptor now imports SpoofedTlsSupport to install
the spoofed stack on the WebView-bypass client; references the
new keiyoushi.lib.tlsspoof package
- libs.versions.toml drops the failed impersonator-bctls entry
(now superseded by :lib:tlsspoof via Conscrypt)
lib/tlsspoof (NEW):
* build.gradle.kts — aliases kei.plugins.library; depends on
org.conscrypt:conscrypt-android:2.5.2 (the Maven-published
Conscrypt build that ships GREASE / ALPS / X25519MLKEM768,
giving a Chromium-adjacent JA3 instead of Android's default).
* TlsSpoof.kt — pure-JVM Android TLS spoofing library. Installs the
upstream Conscrypt JSSE provider at JCA position 1 (idempotent —
Security.insertProviderElement returns the prior position if
already installed). Every SSLContext.getInstance("TLS") OkHttp
then creates internally uses Conscrypt instead of Android's bundled
variant. Background KDoc documents why this exists: prior
impersonator-bctls attempt failed (BCJSSE TLS 1.3 TlsFatalAlert on
modern CF edges — upstream issues zhkl0228/impersonator#8/#9), and
modern CF edge configs (e.g. Miruro.tv) reject Android-default JA3
at the TLS handshake layer before challenge JS even runs. Includes
the design-tradeoff honestly: pure-JVM cannot reproduce a per-byte
Chrome JA3 (Chrome's ClientHello byte ordering is owned by
Conscrypt's internal ClientHello builder, not by OkHttp or by the
JCA caller), but the Chromium-adjacent JA3 is enough to pass CF's
fingerprint gate for the sites tested.
* SpoofedTlsSupport.kt — applyTo(OkHttpClient.Builder) entry point.
Calls TlsSpoof.install() then filters the builder's connectionSpecs
down to OkHttp's MODERN_TLS profile (TLS 1.2/1.3 only, AEAD cipher
suites, same set Chrome 131 offers). CLEARTEXT preserved so plain
HTTP calls still work. Mutation is idempotent. Does NOT disable
cert pinning or trust verification — the spoofed handshake still
validates against the system trust store.
lib/cloudflareinterceptor (MODIFIED):
* CloudflareInterceptor.kt — rewrote KDoc to be honest about the
hybrid reality (headless cannot solve modern managed/Turnstile).
New per-host locks (hostLocks ConcurrentHashMap<String, ReentrantLock>)
deduplicate concurrent solves for the same host. In-memory
CookieCache with 25-minute TTL avoids burning a fresh WebView for
every request; cf_clearance is bound to TLS/JA3+UA+IP and is
intentionally NOT persisted across process death. Caller-UA
passthrough — uses request UA if present, falls back to current
Chrome 131 / Windows UA, because CF re-challenges sessions that
flip UA between the original request and the WebView. Detects
cf-mitigated: challenge header alongside the legacy
Server: cloudflare + 403/503 signaling. Cookie sync to OkHttp
client uses the REAL request URL (scheme + host + path), not a
fake http:// URL — the old code's cookies were silently discarded.
Caller-contract note: must be added as a network interceptor
(not app interceptor) so the WebView solve and post-solve retry
both flow through the rest of the chain (decompression, rate
limiting, etc.).
gradle/libs.versions.toml (MODIFIED):
* Drops the impersonator entry (com.github.zhkl0228:impersonator-bctls)
now that nothing in the tree depends on it. :lib:tlsspoof is the
replacement.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(lib:m3u8server): add full-buffer AutoDetector, AES-128 HLS, header threading, fallback retry, and UpstreamStatusException
AutoDetector: detectInterleavedSkips() full-buffer scan for JPEG/PNG/GIF junk at any offset (not just prefix). M3u8HttpServer: full-buffer download + non-junk chunk assembly, processSegmentUrl takes keyUrl+iv for AES-128-CBC decryption, modifyM3u8Content parses #EXT-X-KEY and injects key/iv on segment URLs, createLocalUrl accepts referer+userAgent encoded as URL params, handleM3u8Request/handleSegmentRequest read from session.parameters, fetchM3u8Content catches IOException and retries via fallbackClient, UpstreamStatusException maps upstream codes to NanoHTTPD Status via passThroughStatus. M3u8Integration: processM3u8Video extracts Referer+UA from video.headers, constructor accepts fallbackClient. M3u8ServerManager: processM3u8Url threads referer+userAgent, constructor accepts fallbackClient.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(lib:megacloudextractor,lib:rapidcloudextractor,lib:dopeflixextractor): graceful 410 handling via requestNewKeyOrNull
Replace hard-fail requestNewKey() with null-tolerant requestNewKeyOrNull() across MegaCloud, RapidCloud, and DopeFlix extractors. When the keys.json GitHub raw endpoint returns 410 ( Gone) or fails, sources are silently skipped via mapNotNull instead of crashing the entire extraction path with IllegalStateException.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(en/miruro): route HLS streams through ultracloud.cc proxy with XOR obfuscation
Add buildProxiedUrl() that wraps raw owocdn.top stream URLs through vault01/02.ultracloud.cc using XOR obfuscation with VITE_PROXY_OBF_KEY and base64url encoding. Proxy server selection via FNV-1a hash of episodeId|anilistId mod 2, matching the frontend's Xb() function. The proxy URL encodes the raw stream.referer from the pipe API (e.g. https://kwik.cx/) — not the resolveHlsReferer output — because the proxy uses the encoded referer to fetch upstream. Video headers carry Referer+Origin set to mirrorBaseUrl so the m3u8 server sends browser-like CORS headers to the proxy. parseStreamsFromResponse accepts episodeId+anilistId params for the hash seed. build.gradle wires m3u8server, cloudflareinterceptor, megacloudextractor, and rapidcloudextractor dependencies.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(en/miruro): thread episodeId/anilistId and PROXY_KEY from Miruro.kt into extractor
Add PROXY_KEY constant (hex decode of a54d389c18527d9fd3e7f0643e27edbe) alongside existing PIPE_KEY. Pass PROXY_KEY as new constructor param to MiruroExtractor. In videoListParse, extract episodeId and anilistId from the episodeData JSON and thread them through all three parseStreamsFromResponse call sites (primary, sub-types, and fallback providers) so the extractor can select the correct proxy server via FNV-1a hash.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* docs(en/miruro): add frontend reverse-engineering research artifacts
Bundle analysis from miruro.tv WatchRoute JS: proxy URL construction (ix/ax XOR+base64url), FNV-1a proxy selection (Xb), deobfuscation (ox/dx), recursive m3u8 fetch (px), and URL verification (gx). Secrets dump from env2.js with both XOR keys and proxy URLs. Discovered endpoints, security report, and README with critical findings.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(en/miruro): remove dead code
Remove unused parseAnimeListResponse (deprecated, never called),
activeMirrorReferer (only consumer was resolveHlsReferer), and
resolveHlsReferer (private, never called).
* feat(lib:anilib): extend with includeNSFW filter, new query methods, and utility extensions
- Add includeNSFW parameter to all fetching methods (fetchFilteredMedia, fetchSearchAnime, fetchTrending, fetchSeasonal, fetchPopular, fetchUpcoming, fetchRecentlyUpdated)
- Add fetchTopAnime, fetchAiringThisSeason, fetchGenres, fetchTags, fetchStudioProductions, fetchMediaTrends methods
- Add MediaSnapshot extension properties (isAdultContent, formattedStatus, streamingLinks, externalLinks, rankings, stats, etc.)
- Add MediaFilter.Builder for fluent filter construction
- Add new DTOs: MediaStreamingEpisode, MediaExternalLink, MediaRank, MediaStats, MediaTrend, Studio, GenreCollectionData
- Extend MediaSnapshot with meanScore, trending, favourites, isLicensed, hashtag, streamingEpisodes, externalLinks, rankings, stats fields
- Update fetchMediaDetails query to include new fields
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(en/miruro): add Show NSFW preference toggle
Adds a toggle to include adult/NSFW anime in search and browse results. Defaults to off. Includes disclaimer that Miruro providers seldom have fully NSFW anime (hentai) available regardless.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* docs(miruro): Remove falsely committed research artifacts
* fix(lib:anilib): pass isAdult=true instead of null to include adult content
AniList API defaults to excluding adult content when the isAdult variable is omitted. Setting isAdult=null (which caused omission) was therefore equivalent to having the NSFW toggle off. Now explicitly sends isAdult=true when the user enables the toggle.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(lib:anilib): move MediaFilter.Builder into MediaFilter companion class
Relocates the Builder class from file-level to inside the MediaFilter data class body, improving discoverability and encapsulation.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(en/miruro): wire includeNSFW to all AniLib fetch calls and parallelize episode metadata
Threads the includeNSFW preference into fetchRecentlyUpdated, fetchSearchAnime, and fetchFilteredMedia so the NSFW toggle takes effect across latest, text search, and filter search. Also parallelizes three independent AniLib network calls (airing schedule, episode titles, filler data) via runBlocking/coroutineScope/async, switches animeMetaCache from synchronized LinkedHashMap to ConcurrentHashMap, uses parallelCatchingFlatMapBlocking for fallback provider requests, and fixes label variable ordering in the debug interceptor.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(en/miruro): optimize XOR decryption in-place and disable m3u8 server for multi streams
Replaces mapIndexed + toByteArray with an in-place XOR loop to avoid allocating an intermediate array. Also sets withM3u8Server=false for MegaCloud multi-type streams so extraction uses the direct video URL instead of routing through the m3u8 server proxy.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(lib:anilib): relocate to lib-multisrc with multisrc plugin and improve error handling
Move anilib from lib/ to lib-multisrc/ using the multisrc plugin format with baseVersionCode. Package changed from aniyomi.lib.anilib to eu.kanade.tachiyomi.multisrc.anilib. Added proper non-2xx HTTP status code handling: executeQuery now reads response bodies on failure, extracts GraphQL error messages, and logs them with status codes. Similar improvements applied to fetchEpisodeTitles and loadAniFillerIndex.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* refactor(en/miruro): update anilib dependency and imports for new multisrc location
Update build.gradle dependency from lib:anilib to lib-multisrc:anilib. Update all imports from aniyomi.lib.anilib to eu.kanade.tachiyomi.multisrc.anilib in Miruro.kt and MiruroFilters.kt.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(lib:anilib): show all content when NSFW toggle is enabled
When includeNSFW is true, omit isAdult parameter to show all anime
(adult + non-adult). Previously set isAdult=true which showed only
adult content.
* anilib: improve rate limit handling
- Fix throttle() race condition with synchronized block
- Only backoff when X-RateLimit-Remaining == 0 (was <= 1)
- Add degraded mode recovery after 5-minute cooldown
- Persist degraded state to SharedPreferences
* Delete cf_challenge_403.html
* Delete cf_jsd_main.js
* Delete cf_jsd_sensor_payload.txt
* Delete cf_jsd_sensor_response.txt
* Delete ChallengeDetectorTest.kt
* Delete CookieCacheTest.kt
* Delete JsdSolverExtractCookiesTest.kt
* Delete JsdSolverExtractDeobfuscationPiecesTest.kt
* Delete JsdSolverParseCvParamsTest.kt
* Delete JsdSolverRealScriptTest.kt
* Remove tests
* refactor(anilib): Move anilib again
* refactor(anilib): Move anilib again again
* feat(en/miruro): route browse/search through Miruro pipe API with AniLib fallback
getPopularAnime, getLatestUpdates, and getSearchAnime (both text-search and filter-browse paths) now hit Miruro's own pipe 'search' and 'search/browse' endpoints first, keeping AniList GraphQL (AniLib) as a synchronous fallback so a Miruro backend hiccup never blanks the page. The filter UI now drives the pipe's native genre/tag/format/status/season/year/dubLanguage query params — dub filtering happens at the source instead of as a client-side post-filter on the pipe path. Pipe responses are decoded with the existing XOR+gzip path and parsed via the same parseAnimeFromMediaObj used by the info endpoint; media shape is identical.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* Remove broken libraries and fix miruro build chores
* Remove bad miruro lib
* Remove bad miruro lib (again)
---------
Co-authored-by: Amnesia <amnesia@example.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Amnesia <anon@example.com>
Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
9 lines
221 B
INI
9 lines
221 B
INI
org.gradle.caching=true
|
|
org.gradle.configuration-cache=true
|
|
org.gradle.configureondemand=true
|
|
org.gradle.jvmargs=-Xmx6144m
|
|
org.gradle.parallel=true
|
|
|
|
kotlin.stdlib.default.dependency=false
|
|
org.gradle.tooling.parallel=true
|