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:
AwkwardPeak7 2024-08-13 06:11:40 +05:00 committed by Draff
parent 2f4249e842
commit a7b59aecd4
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
11 changed files with 202 additions and 233 deletions

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".en.allanime.AllAnimeUrlActivity"
android:name=".en.allanime.AllMangaUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
@ -12,7 +12,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="allanime.ai"/>
<data android:host="allmanga.to"/>
<data android:scheme="https"/>
<data android:pathPattern="/manga/..*"/>
<data android:pathPattern="/read/..*"/>

View File

@ -1,7 +1,7 @@
ext {
extName = 'AllAnime'
extClass = '.AllAnime'
extVersionCode = 6
extName = 'AllManga'
extClass = '.AllManga'
extVersionCode = 7
}
apply from: "$rootDir/common.gradle"

View File

@ -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()
}

View File

@ -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,
)

View File

@ -5,10 +5,7 @@ import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.buildApiHeaders
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.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
@ -26,16 +23,18 @@ import rx.Observable
import uy.kohesive.injekt.Injekt
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"
override val lang = "en"
override val id = 4709139914729853090
override val supportsLatest = true
private val preferences by lazy {
@ -43,24 +42,6 @@ class AllAnime : ConfigurableSource, HttpSource() {
}
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)
.build()
@ -72,7 +53,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val payload = GraphQL(
PopularVariables(
type = "manga",
size = limit,
size = LIMIT,
dateRange = 0,
page = page,
allowAdult = preferences.allowAdult,
@ -83,9 +64,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val requestBody = payload.toJsonRequestBody()
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
return POST(apiUrl, apiHeaders, requestBody)
return POST(apiUrl, headers, requestBody)
}
override fun popularMangaParse(response: Response): MangasPage {
@ -94,7 +73,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val mangaList = result.data.popular.mangas
.mapNotNull { it.manga?.toSManga() }
val hasNextPage = result.data.popular.mangas.size == limit
val hasNextPage = result.data.popular.mangas.size == LIMIT
return MangasPage(mangaList, hasNextPage)
}
@ -128,7 +107,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
allowAdult = preferences.allowAdult,
allowUnknown = false,
),
size = limit,
size = LIMIT,
page = page,
translationType = "sub",
countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL",
@ -138,9 +117,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val requestBody = payload.toJsonRequestBody()
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
return POST(apiUrl, apiHeaders, requestBody)
return POST(apiUrl, headers, requestBody)
}
override fun searchMangaParse(response: Response): MangasPage {
@ -149,7 +126,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val mangaList = result.data.mangas.edges
.map(SearchManga::toSManga)
val hasNextPage = result.data.mangas.edges.size == limit
val hasNextPage = result.data.mangas.edges.size == LIMIT
return MangasPage(mangaList, hasNextPage)
}
@ -167,9 +144,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val requestBody = payload.toJsonRequestBody()
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
return POST(apiUrl, apiHeaders, requestBody)
return POST(apiUrl, headers, requestBody)
}
override fun mangaDetailsParse(response: Response): SManga {
@ -205,9 +180,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val requestBody = payload.toJsonRequestBody()
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
return POST(apiUrl, apiHeaders, requestBody)
return POST(apiUrl, headers, requestBody)
}
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
@ -246,9 +219,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
val requestBody = payload.toJsonRequestBody()
val apiHeaders = headersBuilder().buildApiHeaders(requestBody)
return POST(apiUrl, apiHeaders, requestBody)
return POST(apiUrl, headers, requestBody)
}
override fun pageListParse(response: Response): List<Page> {
@ -266,11 +237,24 @@ class AllAnime : ConfigurableSource, HttpSource() {
return pages.pictureUrls?.mapIndexed { index, image ->
Page(
index = index,
imageUrl = "$imageDomain${image.url}#page",
imageUrl = "$imageDomain${image.url}",
)
} ?: 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 {
throw UnsupportedOperationException()
}
@ -299,11 +283,11 @@ class AllAnime : ConfigurableSource, HttpSource() {
get() = getString(IMAGE_QUALITY_PREF, IMAGE_QUALITY_PREF_DEFAULT)!!
companion object {
private const val limit = 20
private const val LIMIT = 20
const val SEARCH_PREFIX = "id:"
val urlRegex = Regex("^https?://.*")
private const val image_cdn = "https://wp.youtube-anime.com"
private val imageQualityRegex = Regex("^https?://(.*)#.*")
private const val IMAGE_CDN = "https://wp.youtube-anime.com"
private val imageQualityRegex = Regex("^https?://([^#]+)")
private const val SHOW_ADULT_PREF = "pref_adult"
private const val SHOW_ADULT_PREF_DEFAULT = false

View File

@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class AllAnimeUrlActivity : Activity() {
class AllMangaUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
@ -15,17 +15,17 @@ class AllAnimeUrlActivity : Activity() {
val id = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${AllAnime.SEARCH_PREFIX}$id")
putExtra("query", "${AllManga.SEARCH_PREFIX}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("AllAnimeUrlActivity", e.toString())
Log.e("AllMangaUrlActivity", e.toString())
}
} else {
Log.e("AllAnimeUrlActivity", "could not parse uri from intent $intent")
Log.e("AllMangaUrlActivity", "could not parse uri from intent $intent")
}
finish()

View File

@ -1,10 +1,5 @@
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.SManga
import kotlinx.serialization.SerialName
@ -22,39 +17,39 @@ typealias ApiChapterListResponse = Data<ChapterListData>
typealias ApiPageListResponse = Data<PageListData>
@Serializable
data class Data<T>(val data: T)
class Data<T>(val data: T)
@Serializable
data class Edges<T>(val edges: List<T>)
class Edges<T>(val edges: List<T>)
// Popular
@Serializable
data class PopularData(
class PopularData(
@SerialName("queryPopular") val popular: PopularMangas,
)
@Serializable
data class PopularMangas(
class PopularMangas(
@SerialName("recommendations") val mangas: List<PopularManga>,
)
@Serializable
data class PopularManga(
class PopularManga(
@SerialName("anyCard") val manga: SearchManga? = null,
)
// Search
@Serializable
data class SearchData(
class SearchData(
val mangas: Edges<SearchManga>,
)
@Serializable
data class SearchManga(
class SearchManga(
@SerialName("_id") val id: String,
val name: String,
val thumbnail: String? = null,
val englishName: String? = null,
private val name: String,
private val thumbnail: String? = null,
private val englishName: String? = null,
) {
fun toSManga() = SManga.create().apply {
title = englishName ?: name
@ -65,22 +60,22 @@ data class SearchManga(
// Details
@Serializable
data class MangaDetailsData(
class MangaDetailsData(
val manga: Manga,
)
@Serializable
data class Manga(
class Manga(
@SerialName("_id") val id: String,
val name: String,
val thumbnail: String? = null,
val description: String? = null,
val authors: List<String>? = emptyList(),
val genres: List<String>? = emptyList(),
val tags: List<String>? = emptyList(),
val status: String? = null,
val altNames: List<String>? = emptyList(),
val englishName: String? = null,
private val name: String,
private val thumbnail: String? = null,
private val description: String? = null,
private val authors: List<String>? = emptyList(),
private val genres: List<String>? = emptyList(),
private val tags: List<String>? = emptyList(),
private val status: String? = null,
private val altNames: List<String>? = emptyList(),
private val englishName: String? = null,
) {
fun toSManga() = SManga.create().apply {
title = englishName ?: name
@ -108,15 +103,15 @@ data class Manga(
// chapters details
@Serializable
data class ChapterListData(
class ChapterListData(
@SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(),
)
@Serializable
data class ChapterData(
class ChapterData(
@SerialName("episodeIdNum") val chapterNum: JsonPrimitive,
@SerialName("notes") val title: String? = null,
val uploadDates: DateDto? = null,
private val uploadDates: DateDto? = null,
) {
fun toSChapter(mangaUrl: String) = SChapter.create().apply {
name = "Chapter $chapterNum"
@ -133,23 +128,23 @@ data class ChapterData(
}
@Serializable
data class DateDto(
class DateDto(
val sub: String? = null,
)
// page lsit
// page list
@Serializable
data class PageListData(
class PageListData(
@SerialName("chapterPages") val pageList: Edges<Servers>?,
)
@Serializable
data class Servers(
class Servers(
@SerialName("pictureUrlHead") val serverUrl: String? = null,
val pictureUrls: List<PageUrl>?,
)
@Serializable
data class PageUrl(
class PageUrl(
val url: String,
)

View File

@ -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,
)

View File

@ -41,14 +41,14 @@ val SEARCH_QUERY: String = buildQuery {
"""
query (
%search: SearchInput
%limit: Int
%size: Int
%page: Int
%translationType: VaildTranslationTypeMangaEnumType
%countryOrigin: VaildCountryOriginEnumType
) {
mangas(
search: %search
limit: %limit
limit: %size
page: %page
translationType: %translationType
countryOrigin: %countryOrigin

View File

@ -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()