Keiyoushi Network Import (Core) (#952)

* Reviews & cleanup

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Fix covers for AniList

* Fix Miruro ratelimit on host

* Lazy client in multi-src class & update playlistClient

* Bumping versions

* docs: update AGENTS.md and CONTRIBUTING.md for core utilities and versioning details

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
Alpha-782
2026-09-16 21:18:18 +03:00
committed by Cuong-Tran
parent 3e58e74fed
commit 0d77c6253f
23 changed files with 139 additions and 94 deletions

View File

@@ -6,14 +6,14 @@
## Project
Yuzono Anikku/Aniyomi anime extensions – Kotlin + Jsoup/OkHttp scrapers. Each extension is a Gradle module `src/<lang>/<source>`; shared CMS logic lives as themes in `lib-multisrc/<theme>`; shared helpers in `lib/` and `core/utils` (`keiyoushi.utils`).
Yuzono Anikku/Aniyomi anime extensions – Kotlin + Jsoup/OkHttp scrapers. Each extension is a Gradle module `src/<lang>/<source>`; shared CMS logic lives as themes in `lib-multisrc/<theme>`; shared helpers in `lib/` and `core` (`keiyoushi.utils`, `keiyoushi.network`).
## Commands
- Build single extension: `./gradlew src:<lang>:<source>:assembleDebug` (e.g. `./gradlew src:en:anizone:assembleDebug`)
- Build all: `./gradlew assembleDebug` (avoid – loads every module; prefer loading subset in `settings.gradle.kts`)
- Lint/format check: `./gradlew spotlessCheck` (CI) / `./gradlew spotlessApply` (local)
- Test/verify: compile via Android Studio before PR (required by `CONTRIBUTING.md#submitting-the-changes` ~ `1309`)
- Test/verify: compile via Android Studio before PR (required by `CONTRIBUTING.md#submitting-the-changes` ~ `1369`)
## Structure
@@ -21,14 +21,14 @@ Yuzono Anikku/Aniyomi anime extensions – Kotlin + Jsoup/OkHttp scrapers. Each
- `src/<lang>/<source>/src/eu/kanade/tachiyomi/animeextension/<lang>/<source>/` – source code (package must match)
- `lib-multisrc/<theme>/build.gradle.kts` – theme base (`baseVersionCode`, `alias(kei.plugins.multisrc)`)
- `lib-multisrc/<theme>/src/.../multisrc/<theme>/` – abstract theme class `extends AnimeHttpSource`
- `lib/` – reusable libs (`lib-cookieinterceptor`, `lib-cryptoaes`, etc.)
- `core/utils` – `parseAs`, `toJsonRequestBody`, `tryParse`, `extractNextJs`, `absUrl` – use these, no custom JSON/regex/date helpers
- `lib/` – reusable libs (`lib-cryptoaes`, `lib-playlistutils`, extractors, etc.)
- `core` – `keiyoushi.utils` (`parseAs`, `toJsonRequestBody`, `tryParse`, `extractNextJs`, `absUrl`) and `keiyoushi.network` (`rateLimit`, `addCookie`, `OkHttpClient.get`/`post`) – use these, no custom JSON/regex/date/interceptor helpers
## Conventions
See `CONTRIBUTING.md` for full rules. Critical for agents:
- Check `lib/` and `core/utils` (`keiyoushi.utils`) first – reuse existing libs/helpers instead of custom boilerplate (`parseAs`/`toJsonRequestBody`/`tryParse`/`extractNextJs`/`absUrl`, `lib-cookieinterceptor`/`lib-cryptoaes`/etc.). See `CONTRIBUTING.md#core-dependencies`.
- Check `lib/` and `core` (`keiyoushi.utils` for parsing/prefs, `keiyoushi.network` for clients) first – reuse existing libs/helpers instead of custom boilerplate (`parseAs`/`toJsonRequestBody`/`tryParse`/`extractNextJs`/`absUrl`, `rateLimit`/`addCookie`, `lib-cryptoaes`/etc.). See `CONTRIBUTING.md#core-dependencies`.
- Kotlin + Android; web scraping via CSS selectors, OkHttp, Jsoup.
- Do not use `data class` for `@Serializable` DTOs unless needed; camelCase fields, `@SerialName` only when JSON key differs.
- Use `response.parseAs<T>()`, `response.asJsoup()`, `SimpleDateFormat(...).tryParse()`, `element.absUrl("href")` + `setUrlWithoutDomain()`.
@@ -37,7 +37,7 @@ See `CONTRIBUTING.md` for full rules. Critical for agents:
## Versioning – bump once per PR, theme bump propagates
Source of truth: `CONTRIBUTING.md:286-308` (individual) + `CONTRIBUTING.md:1061-1117` (themes) + `gradle/build-logic/src/main/kotlin/PluginExtensionLegacy.kt:61`:
Source of truth: `CONTRIBUTING.md:287-309` (individual) + `CONTRIBUTING.md:1124-1176` (themes) + `gradle/build-logic/src/main/kotlin/PluginExtensionLegacy.kt:61`:
```kotlin
versionCode = if (theme == null) extVersionCode else theme.baseVersionCode + overrideVersionCode
@@ -48,17 +48,17 @@ Rules:
- Individual extension (no theme): increment `extVersionCode` by **1** if code affecting users changed. Bump **once per PR** – do not increment multiple times across commits.
- Theme (`lib-multisrc/<theme>/build.gradle.kts`): increment `baseVersionCode` by **1** when theme logic changes.
- When `baseVersionCode` is bumped, **do not** bump `overrideVersionCode` for extensions using that theme in the same PR – the addition already bumps every extension's effective `versionCode`. Only bump `overrideVersionCode` when the individual extension itself changed independently of the theme.
- Checklist mirrors this: `CONTRIBUTING.md#pull-request-checklist` ~ `1316-1317` and `.github/pull_request_template.md:3-4`.
- Checklist mirrors this: `CONTRIBUTING.md#pull-request-checklist` ~ `1376-1377` and `.github/pull_request_template.md:3-4`.
## Boundaries
- Do not change `versionName` manually (generated `14.<versionCode>`).
- Do not commit `web_hi_res_512.png` (delete after Icon Generator).
- Do not push to `upstream` (`no_pushing` – fork workflow `CONTRIBUTING.md:168-196`). Use `origin` (your fork) for PRs.
- Do not push to `upstream` (`no_pushing` – fork workflow `CONTRIBUTING.md:170-196`). Use `origin` (your fork) for PRs.
- Never commit secrets, keystore, or `local.properties`.
## PR Instructions
- Follow `CONTRIBUTING.md#submitting-the-changes` ~ `1290-1324` checklist; test build in Android Studio.
- Follow `CONTRIBUTING.md#submitting-the-changes` ~ `1353-1384` checklist; test build in Android Studio.
- Title: keep concise; reference issues (`Closes #xyz`).
- One version bump per module per PR as above.

View File

@@ -29,15 +29,14 @@ or fixing it directly by submitting a Pull Request.
- [Adding a lib dependency](#adding-a-lib-dependency)
- [i18n library](#i18n-library)
- [Creating a new lib](#creating-a-new-lib)
- [keiyoushi.utils (core utilities)](#keiyoushiutils-core-utilities)
- [Core utilities (keiyoushi.utils and keiyoushi.network)](#core-utilities-keiyoushiutils-and-keiyoushinetwork)
- [JSON parsing - `parseAs`](#json-parsing---parseas)
- [JSON serialization - `toJsonString` / `toJsonRequestBody`](#json-serialization---tojsonstring--tojsonrequestbody)
- [JSON models (DTOs) and serialization](#json-models-dtos-and-serialization)
- [Protobuf parsing and serialization - `parseAsProto` / `toRequestBodyProto`](#protobuf-parsing-and-serialization---parseasproto--torequestbodyproto)
- [Date parsing - `tryParse` helpers](#date-parsing---tryparse-helpers)
- [Date parsing - `tryParse`](#date-parsing---tryparse)
- [HTTP requests - `OkHttpClient.get` / `post` / `put` / `head`](#http-requests---okhttpclientget--post--put--head)
- [Custom cookies - `addCookie`](#custom-cookies---addcookie)
- [WebView execution - `runWebView` / `getLocalStorage`](#webview-execution---runwebview--getlocalstorage)
- [Filter helpers - `firstInstance` / `firstInstanceOrNull`](#filter-helpers---firstinstance--firstinstanceornull)
- [Next.js data extraction - `extractNextJs` / `extractNextJsRsc`](#nextjs-data-extraction---extractnextjs--extractnextjsrsc)
- [Extracting URLs - `setUrlWithoutDomain` + `absUrl`](#extracting-urls---seturlwithoutdomain--absurl)
@@ -307,7 +306,7 @@ apply plugin: "kei.plugins.extension.legacy"
| `extVersionCode` | The extension version code. This must be a positive integer and incremented with any change to the code. Do not bump for changes that do not affect users, such as changing a private function to a public function. |
| `isNsfw` | Flag to indicate that a source contains NSFW content. Should always be set explicitly to either `true` or `false`. Falls back to `false` if not set. |
The extension's version name is generated automatically by concatenating `16` and `extVersionCode`. With the example used above, the version would be `16.1`.
The extension's version name is generated automatically by concatenating `14` and the resolved version code. With the example used above, the version would be `14.1`.
### Core dependencies
@@ -425,11 +424,11 @@ dependencies {
Place your code in the package `keiyoushi.lib.<mylibname>` or `aniyomi.lib.<mylibname>`. Document public API with KDoc so
contributors can understand the lib without needing to read `CONTRIBUTING.md`.
#### keiyoushi.utils (core utilities)
#### Core utilities (keiyoushi.utils and keiyoushi.network)
The `core/utils` module provides a set of shared extension functions that are available to all extensions
The `core` module provides a set of shared extension functions that are available to all extensions
without any extra Gradle dependency. Prefer using these helpers instead of implementing your own equivalents, as they provide standardized and maintained solutions.
The utilities live in the `keiyoushi.utils` package and are imported individually.
Parsing and preference helpers live in the `keiyoushi.utils` package, HTTP client helpers in `keiyoushi.network`, and they are imported individually.
##### JSON parsing - `parseAs`
@@ -563,6 +562,61 @@ Two common mistakes to avoid:
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT)
```
##### HTTP requests - `OkHttpClient.get` / `post` / `put` / `head`
`keiyoushi.network` provides suspend helpers that build the `Request` and await the call in a single
step, so you don't have to pair a `GET()`/`POST()` with `client.newCall(...).awaitSuccess()`.
```kotlin
import keiyoushi.network.get
import keiyoushi.network.post
// Inside the source class the headers are taken from the source automatically:
val response = client.get("$baseUrl/api/popular")
// Anywhere else, pass them explicitly:
val response = client.get(url, headers)
// POST and PUT take a RequestBody:
val response = client.post("$baseUrl/api/search", searchDto.toJsonRequestBody())
```
Every helper accepts either a `String` or an `HttpUrl`. The call is awaited with `awaitSuccess()` by
default, which throws on a non-2xx response; pass `ensureSuccess = false` to get the `Response` back
whatever the status code is.
`get` and `head` also take a `cacheControl` parameter that defaults to a 10 minute `max-age`. Pass
your own `CacheControl` when a response should be cached for longer, or not at all.
##### Custom cookies - `addCookie`
`keiyoushi.network.addCookie` registers cookies on the client builder. It also writes them to the
WebView `CookieManager`, so "Open in WebView" and Cloudflare challenge solving see the same values.
```kotlin
import keiyoushi.network.addCookie
// Inside the source class the cookies apply to the source's own baseUrl host:
override val client = network.client.newBuilder()
.addCookie("player_type" to "hls")
.build()
// For any other domain, pass it as a lambda:
override val client = network.client.newBuilder()
.addCookie({ "cdn.example.org" }, listOf("a" to "1", "b" to "2"))
.build()
// Resolve the values lazily when they depend on a preference or a login:
override val client = network.client.newBuilder()
.addCookie { listOf("session" to preferences.getString(PREF_TOKEN_KEY, "")!!) }
.build()
```
Chain the call once per domain. The first registered domain that matches the request host - exactly
or as a subdomain - is the one applied, and only cookies with the same names are replaced: anything
else already on the request is preserved. That is why you should never set the `Cookie` header by
hand.
##### Filter helpers - `firstInstance` / `firstInstanceOrNull`
Use these instead of `filterIsInstance<T>().first()` / `filterIsInstance<T>().firstOrNull()`.

View File

@@ -1,7 +1,7 @@
package keiyoushi.network
import android.webkit.CookieManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import keiyoushi.utils.firstInstanceOrNull
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
@@ -11,7 +11,7 @@ import okhttp3.Response
/**
* Adds cookies to requests matching the current source [HttpSource.baseUrl].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
fun OkHttpClient.Builder.addCookie(
cookies: List<Pair<String, String>>,
): OkHttpClient.Builder = addCookie({ source.baseUrl.toHttpUrl().host }, cookies)
@@ -19,7 +19,7 @@ fun OkHttpClient.Builder.addCookie(
/**
* Adds a cookie to requests matching the current source [HttpSource.baseUrl].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
fun OkHttpClient.Builder.addCookie(
cookie: Pair<String, String>,
): OkHttpClient.Builder = addCookie({ source.baseUrl.toHttpUrl().host }, cookie)
@@ -27,7 +27,7 @@ fun OkHttpClient.Builder.addCookie(
/**
* Adds dynamically resolved cookies to requests matching the current source [HttpSource.baseUrl].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
fun OkHttpClient.Builder.addCookie(
cookies: () -> List<Pair<String, String>>,
): OkHttpClient.Builder = addCookie({ source.baseUrl.toHttpUrl().host }, cookies)

View File

@@ -1,8 +1,8 @@
package keiyoushi.network
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.CacheControl
import okhttp3.Headers
import okhttp3.HttpUrl
@@ -72,7 +72,7 @@ suspend fun OkHttpClient.get(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.get(
url: HttpUrl,
cacheControl: CacheControl = DEFAULT_CACHE_CONTROL,
@@ -88,7 +88,7 @@ suspend fun OkHttpClient.get(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.get(
url: String,
cacheControl: CacheControl = DEFAULT_CACHE_CONTROL,
@@ -149,7 +149,7 @@ suspend fun OkHttpClient.post(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.post(
url: HttpUrl,
body: RequestBody,
@@ -165,7 +165,7 @@ suspend fun OkHttpClient.post(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.post(
url: String,
body: RequestBody,
@@ -226,7 +226,7 @@ suspend fun OkHttpClient.put(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.put(
url: String,
body: RequestBody,
@@ -242,7 +242,7 @@ suspend fun OkHttpClient.put(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.put(
url: HttpUrl,
body: RequestBody,
@@ -304,7 +304,7 @@ suspend fun OkHttpClient.head(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.head(
url: String,
cacheControl: CacheControl = DEFAULT_CACHE_CONTROL,
@@ -320,7 +320,7 @@ suspend fun OkHttpClient.head(
* @param ensureSuccess If true, throws an exception if the response code is not 2xx.
* @return The HTTP [Response].
*/
context(source: HttpSource)
context(source: AnimeHttpSource)
suspend fun OkHttpClient.head(
url: HttpUrl,
cacheControl: CacheControl = DEFAULT_CACHE_CONTROL,

View File

@@ -4,7 +4,7 @@ plugins {
alias(kei.plugins.multisrc)
}
baseVersionCode = 7
baseVersionCode = 8
dependencies {
implementation(project(":lib:playlistutils"))
}

View File

@@ -20,8 +20,8 @@ import eu.kanade.tachiyomi.multisrc.anikototheme.AnikotoThemeFilters.addQueryPar
import eu.kanade.tachiyomi.multisrc.anikototheme.dto.ResultResponse
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.get
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.network.rateLimit
import keiyoushi.utils.LazyMutable
import keiyoushi.utils.delegate
import keiyoushi.utils.getPreferencesLazy
@@ -31,7 +31,6 @@ import keiyoushi.utils.useAsJsoup
import okhttp3.CacheControl
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
@@ -66,9 +65,9 @@ abstract class AnikotoTheme(
if (value == baseUrl) return
preferences.edit().putString(PREF_DOMAIN_KEY, value).apply()
docHeaders = headersBuilder().build()
client = network.client.newBuilder()
.rateLimit(rateLimit)
.build()
client = buildClient()
playlistClient = buildPlaylistClient()
playlistUtils = PlaylistUtils(playlistClient, headers)
}
private val domainValues = domainEntries.map { "https://$it" }
@@ -91,20 +90,23 @@ abstract class AnikotoTheme(
protected var docHeaders by LazyMutable { headersBuilder().build() }
override var client: OkHttpClient by LazyMutable {
network.client.newBuilder()
.rateLimit(rateLimit)
.build()
}
// Don't eagerly initialize client in multi-src class since subclass overwrite rateLimit will get 0 instead
override var client by LazyMutable { buildClient() }
internal val playlistClient by lazy {
client.newBuilder()
.readTimeout(30, TimeUnit.SECONDS)
.protocols(listOf(Protocol.HTTP_1_1))
.build()
}
// Don't eagerly initialize client in multi-src class since subclass overwrite rateLimit will get 0 instead
// Derived from [client], so both are rebuilt by the [baseUrl] setter on a domain change
internal var playlistClient by LazyMutable { buildPlaylistClient() }
internal val playlistUtils by lazy { PlaylistUtils(playlistClient, headers) }
internal var playlistUtils by LazyMutable { PlaylistUtils(playlistClient, headers) }
private fun buildClient() = network.client.newBuilder()
.rateLimit(rateLimit)
.build()
private fun buildPlaylistClient() = client.newBuilder()
.readTimeout(30, TimeUnit.SECONDS)
.protocols(listOf(Protocol.HTTP_1_1))
.build()
private val extractors by lazy { AnikotoExtractor(this) }

View File

@@ -4,7 +4,7 @@ plugins {
alias(kei.plugins.multisrc)
}
baseVersionCode = 5
baseVersionCode = 6
dependencies {
api(project(":lib:megaupextractor"))

View File

@@ -19,8 +19,8 @@ import eu.kanade.tachiyomi.multisrc.animekaitheme.dto.VideoData
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.network.rateLimit
import keiyoushi.utils.LazyMutable
import keiyoushi.utils.ParsedAnimeHttpLegacySource
import keiyoushi.utils.addListPreference
@@ -45,7 +45,6 @@ import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.hours
abstract class AnimeKaiTheme(
@@ -72,10 +71,10 @@ abstract class AnimeKaiTheme(
protected var docHeaders by LazyMutable { headersBuilder().build() }
// Go back to deprecated rate limit method, as apps e.g. Dantotsu do not support kotlin.time yet
// Don't eagerly initialize client in multi-src class since subclass overwrite rateLimit will get 0 instead
override var client: OkHttpClient by LazyMutable {
network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), permits = rateLimit, period = 1L, unit = TimeUnit.SECONDS)
.rateLimit(rateLimit)
.build()
}
@@ -361,7 +360,7 @@ abstract class AnimeKaiTheme(
protected open fun updateDomainConfig() {
client = network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), permits = rateLimit, period = 1L, unit = TimeUnit.SECONDS)
.rateLimit(rateLimit)
.build()
docHeaders = headersBuilder().build()
megaUpExtractor = MegaUpExtractor(client, docHeaders, context)

View File

@@ -2,7 +2,7 @@ ext {
extName = 'AniZone'
extClass = '.AniZone'
libVersion = 16
extVersionCode = 13
extVersionCode = 14
isNsfw = false
}

View File

@@ -7,7 +7,6 @@ import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import aniyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.animeextension.all.anizone.AniZone.Companion.PREF_TITLE_LANG_KEY
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@@ -21,8 +20,8 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.network.rateLimit
import keiyoushi.utils.firstInstance
import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs

View File

@@ -1,7 +1,7 @@
ext {
extName = 'Anikage'
extClass = '.Anikage'
extVersionCode = 6
extVersionCode = 7
isNsfw = true
}

View File

@@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import keiyoushi.network.rateLimit
import keiyoushi.utils.AnimeHttpLegacySource
import keiyoushi.utils.addListPreference
import keiyoushi.utils.addSwitchPreference
@@ -33,7 +33,6 @@ import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.collections.associate
import kotlin.collections.isNotEmpty
import kotlin.time.Duration.Companion.seconds
class Anikage :
AnimeHttpLegacySource(),
@@ -54,7 +53,7 @@ class Anikage :
.set("Referer", "$baseUrl/")
override val client = network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 3, 1.seconds)
.rateLimit(3)
.build()
private val preferences by getPreferencesLazy()

View File

@@ -1,7 +1,7 @@
ext {
extName = 'AniList'
extClass = '.AniList'
extVersionCode = 12
extVersionCode = 13
}
apply plugin: "kei.plugins.extension.legacy"

View File

@@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import keiyoushi.network.rateLimit
import keiyoushi.utils.AnimeHttpLegacySource
import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs
@@ -25,13 +25,11 @@ import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class AniList :
AnimeHttpLegacySource(),
@@ -48,7 +46,7 @@ class AniList :
override val supportsLatest = true
override val client = network.client.newBuilder()
.rateLimitHost("https://api.jikan.moe".toHttpUrl(), 1, 1L, TimeUnit.SECONDS)
.rateLimit(1) { it.host == "api.tenrai.org" }
.build()
private val json: Json by injectLazy()
@@ -324,7 +322,7 @@ class AniList :
private fun getSingleEpisodeFromMal(malId: Int): List<SEpisode> {
val animeData = client.newCall(
GET("https://api.jikan.moe/v4/anime/$malId", headers),
GET("https://api.tenrai.org/v1/anime/$malId", headers),
).execute().parseAs<JikanAnimeDto>().data
return listOf(
@@ -345,7 +343,7 @@ class AniList :
var page = 1
while (hasNextPage) {
val data = client.newCall(
GET("https://api.jikan.moe/v4/anime/$malId/episodes?page=$page", headers),
GET("https://api.tenrai.org/v1/anime/$malId/episodes?page=$page", headers),
).execute().parseAs<JikanEpisodesDto>()
if (data.pagination.lastPage == 1 && data.data.isEmpty()) {

View File

@@ -8,7 +8,7 @@ import okhttp3.OkHttpClient
class CoverProviders(private val client: OkHttpClient, private val headers: Headers) {
fun getMALCovers(malId: String): List<String> {
return client.newCall(
GET("https://api.jikan.moe/v4/anime/$malId/pictures", headers),
GET("https://api.tenrai.org/v1/anime/$malId/pictures", headers),
).execute().use { response ->
if (!response.isSuccessful) return@use emptyList()

View File

@@ -2,7 +2,7 @@ ext {
extName = 'Miruro.tv'
extClass = '.Miruro'
isNsfw = true
extVersionCode = 10
extVersionCode = 11
}
apply plugin: "kei.plugins.extension.legacy"

View File

@@ -26,7 +26,7 @@ import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import keiyoushi.network.rateLimit
import keiyoushi.utils.AnimeHttpLegacySource
import keiyoushi.utils.LazyMutable
import keiyoushi.utils.addListPreference
@@ -835,6 +835,7 @@ class Miruro :
private val PREF_MIRROR_DEFAULT = DEFAULT_MIRROR_VALUES.first()
private const val PREF_CACHED_MIRRORS_KEY = "cached_mirrors_json"
private const val STATUS_PAGE_API_URL = "https://status.miruro.com/api"
private val STATUS_PAGE_API_HOST = STATUS_PAGE_API_URL.toHttpUrl().host
private val BR_REGEX = Regex("<br\\s*/?>", RegexOption.IGNORE_CASE)
private val CLOSE_P_REGEX = Regex("</p>", RegexOption.IGNORE_CASE)
@@ -845,8 +846,8 @@ class Miruro :
val SUB_TYPE_DISPLAY_ORDER = listOf("sub", "dub", "ssub", "h-sub")
}
private val statusPageClient: OkHttpClient = network.client.newBuilder()
.rateLimitHost("$STATUS_PAGE_API_URL/".toHttpUrl(), permits = 1, period = 2.seconds)
private val statusPageClient = network.client.newBuilder()
.rateLimit(1, 2.seconds) { it.host == STATUS_PAGE_API_HOST }
.build()
// ============================== Pipe Search ==============================

View File

@@ -2,7 +2,7 @@ ext {
extName = 'Re:ANIME'
extClass = '.ReAnime'
libVersion = 16
extVersionCode = 6
extVersionCode = 7
isNsfw = true
}

View File

@@ -23,7 +23,7 @@ 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.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import keiyoushi.network.rateLimit
import keiyoushi.utils.addListPreference
import keiyoushi.utils.get
import keiyoushi.utils.getPreferencesLazy
@@ -40,7 +40,6 @@ import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.putJsonObject
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.nanohttpd.protocols.http.NanoHTTPD
@@ -106,11 +105,9 @@ class ReAnime :
.add("Sec-Fetch-Site", "same-origin")
.build()
override val client: OkHttpClient by lazy {
network.client.newBuilder()
.rateLimit(5)
.build()
}
override val client = network.client.newBuilder()
.rateLimit(5)
.build()
private val playlistUtils by lazy { PlaylistUtils(network.client, headers) }

View File

@@ -1,7 +1,7 @@
ext {
extName = 'XAnime'
extClass = '.XAnime'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}

View File

@@ -27,7 +27,7 @@ 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.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import keiyoushi.network.rateLimit
import keiyoushi.utils.AnimeHttpLegacySource
import keiyoushi.utils.addListPreference
import keiyoushi.utils.addSetPreference
@@ -42,7 +42,6 @@ import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@@ -57,9 +56,9 @@ class XAnime :
override val baseUrl: String get() = preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN)!!
private val cryptoClient: OkHttpClient = client.newBuilder()
.rateLimit(5)
private val cryptoClient = client.newBuilder()
.addInterceptor(Crypto())
.rateLimit(5)
.build()
private val api = Queries(cryptoClient, { baseUrl }, headers)

View File

@@ -1,7 +1,7 @@
ext {
extName = 'Fun Anime TV'
extClass = '.FunAnimeTV'
extVersionCode = 1
extVersionCode = 2
}
apply plugin: "kei.plugins.extension.legacy"

View File

@@ -15,7 +15,7 @@ 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.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import keiyoushi.network.rateLimit
import keiyoushi.utils.AnimeHttpLegacySource
import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs
@@ -30,7 +30,6 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import java.security.MessageDigest
import kotlin.time.Duration.Companion.seconds
class FunAnimeTV :
AnimeHttpLegacySource(),
@@ -54,12 +53,10 @@ class FunAnimeTV :
set("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 16; M2007J20CG Build/BP3A.250905.014)")
}
override val client by lazy {
network.client.newBuilder()
.rateLimit(5, 1.seconds)
.retryOnConnectionFailure(true)
.build()
}
override val client = network.client.newBuilder()
.rateLimit(5)
.retryOnConnectionFailure(true)
.build()
// ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request {