AllAnime -> AllManga: update domain & small refactor (#4576)
* AllAnime -> AllManga: update domain & small refactor * preserve id * potential issue with img quality and cache?
This commit is contained in:
parent
2f4249e842
commit
a7b59aecd4
|
@ -2,7 +2,7 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application>
|
<application>
|
||||||
<activity
|
<activity
|
||||||
android:name=".en.allanime.AllAnimeUrlActivity"
|
android:name=".en.allanime.AllMangaUrlActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:host="allanime.ai"/>
|
<data android:host="allmanga.to"/>
|
||||||
<data android:scheme="https"/>
|
<data android:scheme="https"/>
|
||||||
<data android:pathPattern="/manga/..*"/>
|
<data android:pathPattern="/manga/..*"/>
|
||||||
<data android:pathPattern="/read/..*"/>
|
<data android:pathPattern="/read/..*"/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'AllAnime'
|
extName = 'AllManga'
|
||||||
extClass = '.AllAnime'
|
extClass = '.AllManga'
|
||||||
extVersionCode = 6
|
extVersionCode = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.allanime
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
object AllAnimeHelper {
|
|
||||||
|
|
||||||
val json: Json = Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
explicitNulls = false
|
|
||||||
encodeDefaults = true
|
|
||||||
coerceInputValues = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.parseThumbnailUrl(): String {
|
|
||||||
return if (this.matches(AllAnime.urlRegex)) {
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
"$thumbnail_cdn$this?w=250"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String?.parseStatus(): Int {
|
|
||||||
if (this == null) {
|
|
||||||
return SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
this.contains("releasing", true) -> SManga.ONGOING
|
|
||||||
this.contains("finished", true) -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.titleToSlug() = this.trim()
|
|
||||||
.lowercase(Locale.US)
|
|
||||||
.replace(titleSpecialCharactersRegex, "-")
|
|
||||||
|
|
||||||
fun String.parseDescription(): String {
|
|
||||||
return Jsoup.parse(
|
|
||||||
this.replace("<br>", "br2n"),
|
|
||||||
).text().replace("br2n", "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String?.parseDate(): Long {
|
|
||||||
return runCatching {
|
|
||||||
dateFormat.parse(this!!)!!.time
|
|
||||||
}.getOrDefault(0L)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())
|
|
||||||
|
|
||||||
inline fun <reified T> List<*>.firstInstanceOrNull(): T? =
|
|
||||||
filterIsInstance<T>().firstOrNull()
|
|
||||||
|
|
||||||
inline fun <reified T : Any> T.toJsonRequestBody(): RequestBody =
|
|
||||||
json.encodeToString(this)
|
|
||||||
.toRequestBody(JSON_MEDIA_TYPE)
|
|
||||||
|
|
||||||
fun Headers.Builder.buildApiHeaders(requestBody: RequestBody) = this
|
|
||||||
.add("Content-Length", requestBody.contentLength().toString())
|
|
||||||
.add("Content-Type", requestBody.contentType().toString())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private const val thumbnail_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/"
|
|
||||||
private val titleSpecialCharactersRegex by lazy { Regex("[^a-z\\d]+") }
|
|
||||||
private val dateFormat by lazy {
|
|
||||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
|
||||||
}
|
|
||||||
val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.allanime
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GraphQL<T>(
|
|
||||||
val variables: T,
|
|
||||||
val query: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PopularVariables(
|
|
||||||
val type: String,
|
|
||||||
val size: Int,
|
|
||||||
val dateRange: Int,
|
|
||||||
val page: Int,
|
|
||||||
val allowAdult: Boolean,
|
|
||||||
val allowUnknown: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SearchVariables(
|
|
||||||
val search: SearchPayload,
|
|
||||||
@SerialName("limit") val size: Int,
|
|
||||||
val page: Int,
|
|
||||||
val translationType: String,
|
|
||||||
val countryOrigin: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SearchPayload(
|
|
||||||
val query: String?,
|
|
||||||
val sortBy: String?,
|
|
||||||
val genres: List<String>?,
|
|
||||||
val excludeGenres: List<String>?,
|
|
||||||
val isManga: Boolean,
|
|
||||||
val allowAdult: Boolean,
|
|
||||||
val allowUnknown: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class IDVariables(
|
|
||||||
val id: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ChapterListVariables(
|
|
||||||
val id: String,
|
|
||||||
val chapterNumStart: Float,
|
|
||||||
val chapterNumEnd: Float,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PageListVariables(
|
|
||||||
val id: String,
|
|
||||||
val chapterNum: String,
|
|
||||||
val translationType: String,
|
|
||||||
)
|
|
|
@ -5,10 +5,7 @@ import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.buildApiHeaders
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.firstInstanceOrNull
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseAs
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.toJsonRequestBody
|
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
@ -26,16 +23,18 @@ import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class AllAnime : ConfigurableSource, HttpSource() {
|
class AllManga : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
override val name = "AllAnime"
|
override val name = "AllManga"
|
||||||
|
|
||||||
override val baseUrl = "https://allanime.ai"
|
override val baseUrl = "https://allmanga.to"
|
||||||
|
|
||||||
private val apiUrl = "https://api.allanime.day/api"
|
private val apiUrl = "https://api.allanime.day/api"
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val id = 4709139914729853090
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
private val preferences by lazy {
|
private val preferences by lazy {
|
||||||
|
@ -43,24 +42,6 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor { chain ->
|
|
||||||
val request = chain.request()
|
|
||||||
val frag = request.url.fragment
|
|
||||||
val quality = preferences.imageQuality
|
|
||||||
|
|
||||||
if (frag.isNullOrEmpty() || quality == IMAGE_QUALITY_PREF_DEFAULT) {
|
|
||||||
return@addInterceptor chain.proceed(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
val oldUrl = request.url.toString()
|
|
||||||
val newUrl = oldUrl.replace(imageQualityRegex, "$image_cdn/$1?w=$quality")
|
|
||||||
|
|
||||||
return@addInterceptor chain.proceed(
|
|
||||||
request.newBuilder()
|
|
||||||
.url(newUrl)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.rateLimit(1)
|
.rateLimit(1)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -72,7 +53,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
val payload = GraphQL(
|
val payload = GraphQL(
|
||||||
PopularVariables(
|
PopularVariables(
|
||||||
type = "manga",
|
type = "manga",
|
||||||
size = limit,
|
size = LIMIT,
|
||||||
dateRange = 0,
|
dateRange = 0,
|
||||||
page = page,
|
page = page,
|
||||||
allowAdult = preferences.allowAdult,
|
allowAdult = preferences.allowAdult,
|
||||||
|
@ -83,9 +64,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
val requestBody = payload.toJsonRequestBody()
|
val requestBody = payload.toJsonRequestBody()
|
||||||
|
|
||||||
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
|
|
||||||
return POST(apiUrl, apiHeaders, requestBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
@ -94,7 +73,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
val mangaList = result.data.popular.mangas
|
val mangaList = result.data.popular.mangas
|
||||||
.mapNotNull { it.manga?.toSManga() }
|
.mapNotNull { it.manga?.toSManga() }
|
||||||
|
|
||||||
val hasNextPage = result.data.popular.mangas.size == limit
|
val hasNextPage = result.data.popular.mangas.size == LIMIT
|
||||||
|
|
||||||
return MangasPage(mangaList, hasNextPage)
|
return MangasPage(mangaList, hasNextPage)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +107,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
allowAdult = preferences.allowAdult,
|
allowAdult = preferences.allowAdult,
|
||||||
allowUnknown = false,
|
allowUnknown = false,
|
||||||
),
|
),
|
||||||
size = limit,
|
size = LIMIT,
|
||||||
page = page,
|
page = page,
|
||||||
translationType = "sub",
|
translationType = "sub",
|
||||||
countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL",
|
countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL",
|
||||||
|
@ -138,9 +117,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
val requestBody = payload.toJsonRequestBody()
|
val requestBody = payload.toJsonRequestBody()
|
||||||
|
|
||||||
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
|
|
||||||
return POST(apiUrl, apiHeaders, requestBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
@ -149,7 +126,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
val mangaList = result.data.mangas.edges
|
val mangaList = result.data.mangas.edges
|
||||||
.map(SearchManga::toSManga)
|
.map(SearchManga::toSManga)
|
||||||
|
|
||||||
val hasNextPage = result.data.mangas.edges.size == limit
|
val hasNextPage = result.data.mangas.edges.size == LIMIT
|
||||||
|
|
||||||
return MangasPage(mangaList, hasNextPage)
|
return MangasPage(mangaList, hasNextPage)
|
||||||
}
|
}
|
||||||
|
@ -167,9 +144,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
val requestBody = payload.toJsonRequestBody()
|
val requestBody = payload.toJsonRequestBody()
|
||||||
|
|
||||||
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
|
|
||||||
return POST(apiUrl, apiHeaders, requestBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
@ -205,9 +180,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
val requestBody = payload.toJsonRequestBody()
|
val requestBody = payload.toJsonRequestBody()
|
||||||
|
|
||||||
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
|
|
||||||
return POST(apiUrl, apiHeaders, requestBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
|
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
|
||||||
|
@ -246,9 +219,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
val requestBody = payload.toJsonRequestBody()
|
val requestBody = payload.toJsonRequestBody()
|
||||||
|
|
||||||
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
|
|
||||||
return POST(apiUrl, apiHeaders, requestBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
@ -266,11 +237,24 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
return pages.pictureUrls?.mapIndexed { index, image ->
|
return pages.pictureUrls?.mapIndexed { index, image ->
|
||||||
Page(
|
Page(
|
||||||
index = index,
|
index = index,
|
||||||
imageUrl = "$imageDomain${image.url}#page",
|
imageUrl = "$imageDomain${image.url}",
|
||||||
)
|
)
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun imageRequest(page: Page): Request {
|
||||||
|
val quality = preferences.imageQuality
|
||||||
|
|
||||||
|
if (quality == IMAGE_QUALITY_PREF_DEFAULT) {
|
||||||
|
return super.imageRequest(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
val oldUrl = imageQualityRegex.find(page.imageUrl!!)!!.groupValues[1]
|
||||||
|
val newUrl = "$IMAGE_CDN/$oldUrl?w=$quality"
|
||||||
|
|
||||||
|
return GET(newUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String {
|
override fun imageUrlParse(response: Response): String {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
@ -299,11 +283,11 @@ class AllAnime : ConfigurableSource, HttpSource() {
|
||||||
get() = getString(IMAGE_QUALITY_PREF, IMAGE_QUALITY_PREF_DEFAULT)!!
|
get() = getString(IMAGE_QUALITY_PREF, IMAGE_QUALITY_PREF_DEFAULT)!!
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val limit = 20
|
private const val LIMIT = 20
|
||||||
const val SEARCH_PREFIX = "id:"
|
const val SEARCH_PREFIX = "id:"
|
||||||
val urlRegex = Regex("^https?://.*")
|
val urlRegex = Regex("^https?://.*")
|
||||||
private const val image_cdn = "https://wp.youtube-anime.com"
|
private const val IMAGE_CDN = "https://wp.youtube-anime.com"
|
||||||
private val imageQualityRegex = Regex("^https?://(.*)#.*")
|
private val imageQualityRegex = Regex("^https?://([^#]+)")
|
||||||
|
|
||||||
private const val SHOW_ADULT_PREF = "pref_adult"
|
private const val SHOW_ADULT_PREF = "pref_adult"
|
||||||
private const val SHOW_ADULT_PREF_DEFAULT = false
|
private const val SHOW_ADULT_PREF_DEFAULT = false
|
|
@ -7,7 +7,7 @@ import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class AllAnimeUrlActivity : Activity() {
|
class AllMangaUrlActivity : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val pathSegments = intent?.data?.pathSegments
|
val pathSegments = intent?.data?.pathSegments
|
||||||
|
@ -15,17 +15,17 @@ class AllAnimeUrlActivity : Activity() {
|
||||||
val id = pathSegments[1]
|
val id = pathSegments[1]
|
||||||
val mainIntent = Intent().apply {
|
val mainIntent = Intent().apply {
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
action = "eu.kanade.tachiyomi.SEARCH"
|
||||||
putExtra("query", "${AllAnime.SEARCH_PREFIX}$id")
|
putExtra("query", "${AllManga.SEARCH_PREFIX}$id")
|
||||||
putExtra("filter", packageName)
|
putExtra("filter", packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(mainIntent)
|
startActivity(mainIntent)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
Log.e("AllAnimeUrlActivity", e.toString())
|
Log.e("AllMangaUrlActivity", e.toString())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e("AllAnimeUrlActivity", "could not parse uri from intent $intent")
|
Log.e("AllMangaUrlActivity", "could not parse uri from intent $intent")
|
||||||
}
|
}
|
||||||
|
|
||||||
finish()
|
finish()
|
|
@ -1,10 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.allanime
|
package eu.kanade.tachiyomi.extension.en.allanime
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseDate
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseDescription
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseStatus
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseThumbnailUrl
|
|
||||||
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.titleToSlug
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
|
@ -22,39 +17,39 @@ typealias ApiChapterListResponse = Data<ChapterListData>
|
||||||
typealias ApiPageListResponse = Data<PageListData>
|
typealias ApiPageListResponse = Data<PageListData>
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Data<T>(val data: T)
|
class Data<T>(val data: T)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Edges<T>(val edges: List<T>)
|
class Edges<T>(val edges: List<T>)
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PopularData(
|
class PopularData(
|
||||||
@SerialName("queryPopular") val popular: PopularMangas,
|
@SerialName("queryPopular") val popular: PopularMangas,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PopularMangas(
|
class PopularMangas(
|
||||||
@SerialName("recommendations") val mangas: List<PopularManga>,
|
@SerialName("recommendations") val mangas: List<PopularManga>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PopularManga(
|
class PopularManga(
|
||||||
@SerialName("anyCard") val manga: SearchManga? = null,
|
@SerialName("anyCard") val manga: SearchManga? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SearchData(
|
class SearchData(
|
||||||
val mangas: Edges<SearchManga>,
|
val mangas: Edges<SearchManga>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SearchManga(
|
class SearchManga(
|
||||||
@SerialName("_id") val id: String,
|
@SerialName("_id") val id: String,
|
||||||
val name: String,
|
private val name: String,
|
||||||
val thumbnail: String? = null,
|
private val thumbnail: String? = null,
|
||||||
val englishName: String? = null,
|
private val englishName: String? = null,
|
||||||
) {
|
) {
|
||||||
fun toSManga() = SManga.create().apply {
|
fun toSManga() = SManga.create().apply {
|
||||||
title = englishName ?: name
|
title = englishName ?: name
|
||||||
|
@ -65,22 +60,22 @@ data class SearchManga(
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MangaDetailsData(
|
class MangaDetailsData(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Manga(
|
class Manga(
|
||||||
@SerialName("_id") val id: String,
|
@SerialName("_id") val id: String,
|
||||||
val name: String,
|
private val name: String,
|
||||||
val thumbnail: String? = null,
|
private val thumbnail: String? = null,
|
||||||
val description: String? = null,
|
private val description: String? = null,
|
||||||
val authors: List<String>? = emptyList(),
|
private val authors: List<String>? = emptyList(),
|
||||||
val genres: List<String>? = emptyList(),
|
private val genres: List<String>? = emptyList(),
|
||||||
val tags: List<String>? = emptyList(),
|
private val tags: List<String>? = emptyList(),
|
||||||
val status: String? = null,
|
private val status: String? = null,
|
||||||
val altNames: List<String>? = emptyList(),
|
private val altNames: List<String>? = emptyList(),
|
||||||
val englishName: String? = null,
|
private val englishName: String? = null,
|
||||||
) {
|
) {
|
||||||
fun toSManga() = SManga.create().apply {
|
fun toSManga() = SManga.create().apply {
|
||||||
title = englishName ?: name
|
title = englishName ?: name
|
||||||
|
@ -108,15 +103,15 @@ data class Manga(
|
||||||
|
|
||||||
// chapters details
|
// chapters details
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ChapterListData(
|
class ChapterListData(
|
||||||
@SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(),
|
@SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ChapterData(
|
class ChapterData(
|
||||||
@SerialName("episodeIdNum") val chapterNum: JsonPrimitive,
|
@SerialName("episodeIdNum") val chapterNum: JsonPrimitive,
|
||||||
@SerialName("notes") val title: String? = null,
|
@SerialName("notes") val title: String? = null,
|
||||||
val uploadDates: DateDto? = null,
|
private val uploadDates: DateDto? = null,
|
||||||
) {
|
) {
|
||||||
fun toSChapter(mangaUrl: String) = SChapter.create().apply {
|
fun toSChapter(mangaUrl: String) = SChapter.create().apply {
|
||||||
name = "Chapter $chapterNum"
|
name = "Chapter $chapterNum"
|
||||||
|
@ -133,23 +128,23 @@ data class ChapterData(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DateDto(
|
class DateDto(
|
||||||
val sub: String? = null,
|
val sub: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
// page lsit
|
// page list
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PageListData(
|
class PageListData(
|
||||||
@SerialName("chapterPages") val pageList: Edges<Servers>?,
|
@SerialName("chapterPages") val pageList: Edges<Servers>?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Servers(
|
class Servers(
|
||||||
@SerialName("pictureUrlHead") val serverUrl: String? = null,
|
@SerialName("pictureUrlHead") val serverUrl: String? = null,
|
||||||
val pictureUrls: List<PageUrl>?,
|
val pictureUrls: List<PageUrl>?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PageUrl(
|
class PageUrl(
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
|
@ -0,0 +1,58 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.allanime
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class GraphQL<T>(
|
||||||
|
private val variables: T,
|
||||||
|
private val query: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PopularVariables(
|
||||||
|
private val type: String,
|
||||||
|
private val size: Int,
|
||||||
|
private val dateRange: Int,
|
||||||
|
private val page: Int,
|
||||||
|
private val allowAdult: Boolean,
|
||||||
|
private val allowUnknown: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchVariables(
|
||||||
|
private val search: SearchPayload,
|
||||||
|
private val size: Int,
|
||||||
|
private val page: Int,
|
||||||
|
private val translationType: String,
|
||||||
|
private val countryOrigin: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchPayload(
|
||||||
|
private val query: String?,
|
||||||
|
private val sortBy: String?,
|
||||||
|
private val genres: List<String>?,
|
||||||
|
private val excludeGenres: List<String>?,
|
||||||
|
private val isManga: Boolean,
|
||||||
|
private val allowAdult: Boolean,
|
||||||
|
private val allowUnknown: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class IDVariables(
|
||||||
|
private val id: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterListVariables(
|
||||||
|
private val id: String,
|
||||||
|
private val chapterNumStart: Float,
|
||||||
|
private val chapterNumEnd: Float,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageListVariables(
|
||||||
|
private val id: String,
|
||||||
|
private val chapterNum: String,
|
||||||
|
private val translationType: String,
|
||||||
|
)
|
|
@ -41,14 +41,14 @@ val SEARCH_QUERY: String = buildQuery {
|
||||||
"""
|
"""
|
||||||
query (
|
query (
|
||||||
%search: SearchInput
|
%search: SearchInput
|
||||||
%limit: Int
|
%size: Int
|
||||||
%page: Int
|
%page: Int
|
||||||
%translationType: VaildTranslationTypeMangaEnumType
|
%translationType: VaildTranslationTypeMangaEnumType
|
||||||
%countryOrigin: VaildCountryOriginEnumType
|
%countryOrigin: VaildCountryOriginEnumType
|
||||||
) {
|
) {
|
||||||
mangas(
|
mangas(
|
||||||
search: %search
|
search: %search
|
||||||
limit: %limit
|
limit: %size
|
||||||
page: %page
|
page: %page
|
||||||
translationType: %translationType
|
translationType: %translationType
|
||||||
countryOrigin: %countryOrigin
|
countryOrigin: %countryOrigin
|
|
@ -0,0 +1,72 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.allanime
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
val json: Json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
explicitNulls = false
|
||||||
|
encodeDefaults = true
|
||||||
|
coerceInputValues = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.parseThumbnailUrl(): String {
|
||||||
|
return if (this.matches(AllManga.urlRegex)) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
"$thumbnail_cdn$this?w=250"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String?.parseStatus(): Int {
|
||||||
|
if (this == null) {
|
||||||
|
return SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
this.contains("releasing", true) -> SManga.ONGOING
|
||||||
|
this.contains("finished", true) -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.titleToSlug() = this.trim()
|
||||||
|
.lowercase(Locale.US)
|
||||||
|
.replace(titleSpecialCharactersRegex, "-")
|
||||||
|
|
||||||
|
fun String.parseDescription(): String {
|
||||||
|
return Jsoup.parse(
|
||||||
|
this.replace("<br>", "br2n"),
|
||||||
|
).text().replace("br2n", "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String?.parseDate(): Long {
|
||||||
|
return runCatching {
|
||||||
|
dateFormat.parse(this!!)!!.time
|
||||||
|
}.getOrDefault(0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())
|
||||||
|
|
||||||
|
inline fun <reified T> List<*>.firstInstanceOrNull(): T? =
|
||||||
|
filterIsInstance<T>().firstOrNull()
|
||||||
|
|
||||||
|
inline fun <reified T : Any> T.toJsonRequestBody(): RequestBody =
|
||||||
|
json.encodeToString(this)
|
||||||
|
.toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
private const val thumbnail_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/"
|
||||||
|
private val titleSpecialCharactersRegex = Regex("[^a-z\\d]+")
|
||||||
|
private val dateFormat by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
Loading…
Reference in New Issue