diff --git a/src/en/allanime/AndroidManifest.xml b/src/en/allanime/AndroidManifest.xml new file mode 100644 index 000000000..8ce8c57a6 --- /dev/null +++ b/src/en/allanime/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/en/allanime/build.gradle b/src/en/allanime/build.gradle new file mode 100644 index 000000000..8ba4b84d4 --- /dev/null +++ b/src/en/allanime/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'AllAnime' + pkgNameSuffix = 'en.allanime' + extClass = '.AllAnime' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/allanime/res/mipmap-hdpi/ic_launcher.png b/src/en/allanime/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..949f352cb Binary files /dev/null and b/src/en/allanime/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/allanime/res/mipmap-mdpi/ic_launcher.png b/src/en/allanime/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c0a7751a1 Binary files /dev/null and b/src/en/allanime/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/allanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/allanime/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..12a92f4e7 Binary files /dev/null and b/src/en/allanime/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/allanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/allanime/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..efaa23418 Binary files /dev/null and b/src/en/allanime/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/allanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/allanime/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..810290aab Binary files /dev/null and b/src/en/allanime/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/allanime/res/web_hi_res_512.png b/src/en/allanime/res/web_hi_res_512.png new file mode 100644 index 000000000..dfa23fb46 Binary files /dev/null and b/src/en/allanime/res/web_hi_res_512.png differ diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt new file mode 100644 index 000000000..3b5f40949 --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt @@ -0,0 +1,459 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_EXCLUDE +import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_INCLUDE +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.jsoup.Jsoup +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.util.Locale + +class AllAnime : ConfigurableSource, HttpSource() { + + override val name = "AllAnime" + + override val lang = "en" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences = + Injekt.get().getSharedPreferences("source_$id", 0x0000) + + private val domain = preferences.getString(DOMAIN_PREF, "allanime.to") + + override val baseUrl = "https://$domain" + + private val apiUrl = "https://api.$domain/allanimeapi" + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimitHost(apiUrl.toHttpUrl(), 1) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + /* Popular */ + override fun popularMangaRequest(page: Int): Request { + val showAdult = preferences.getBoolean(SHOW_ADULT_PREF, false) + + val payload = buildJsonObject { + putJsonObject("variables") { + put("type", "manga") + put("size", limit) + put("dateRange", 0) + put("page", page) + put("allowAdult", showAdult) + put("allowUnknown", false) + } + put("query", POPULAR_QUERY) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(apiUrl, newHeaders, body) + } + + override fun popularMangaParse(response: Response): MangasPage { + val result = json.decodeFromString(response.body.string()) + + val titleStyle = preferences.getString(TITLE_PREF, "romaji")!! + + val mangaList = result.data.queryPopular.recommendations + .mapNotNull { it.anyCard } + .map { manga -> + SManga.create().apply { + title = when (titleStyle) { + "romaji" -> manga.name + "eng" -> manga.englishName ?: manga.name + else -> manga.nativeName ?: manga.name + } + url = "/manga/${manga._id}/${manga.name.titleToSlug()}" + thumbnail_url = manga.thumbnail.parseThumbnailUrl() + } + } + + return MangasPage(mangaList, mangaList.size == limit) + } + + /* Latest */ + override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList()) + + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) + + /* Search */ + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (!query.startsWith(SEARCH_PREFIX)) { + return super.fetchSearchManga(page, query, filters) + } + + val url = "/manga/${query.substringAfter(SEARCH_PREFIX)}/" + return fetchMangaDetails(SManga.create().apply { this.url = url }).map { + MangasPage(listOf(it), false) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val showAdult = preferences.getBoolean(SHOW_ADULT_PREF, false) + var country = "ALL" + val includeGenres = mutableListOf() + val excludeGenres = mutableListOf() + + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + filter.state.forEach { genreState -> + when (genreState.state) { + STATE_INCLUDE -> includeGenres.add(genreState.name) + STATE_EXCLUDE -> excludeGenres.add(genreState.name) + } + } + } + is CountryFilter -> { + country = filter.getValue() + } + else -> {} + } + } + + val payload = buildJsonObject { + putJsonObject("variables") { + putJsonObject("search") { + if (includeGenres.isNotEmpty() || excludeGenres.isNotEmpty()) { + put("genres", JsonArray(includeGenres.map { JsonPrimitive(it) })) + put("excludeGenres", JsonArray(excludeGenres.map { JsonPrimitive(it) })) + } + if (query.isNotEmpty()) put("query", query) + put("allowAdult", showAdult) + put("allowUnknown", false) + put("isManga", true) + } + put("limit", limit) + put("page", page) + put("translationType", "sub") + put("countryOrigin", country) + } + put("query", SEARCH_QUERY) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(apiUrl, newHeaders, body) + } + + override fun searchMangaParse(response: Response): MangasPage { + val result = json.decodeFromString(response.body.string()) + + val titleStyle = preferences.getString(TITLE_PREF, "romaji")!! + + val mangaList = result.data.mangas.edges + .map { manga -> + SManga.create().apply { + title = when (titleStyle) { + "romaji" -> manga.name + "eng" -> manga.englishName ?: manga.name + else -> manga.nativeName ?: manga.name + } + url = "/manga/${manga._id}/${manga.name.titleToSlug()}" + thumbnail_url = manga.thumbnail.parseThumbnailUrl() + } + } + + return MangasPage(mangaList, mangaList.size == limit) + } + + override fun getFilterList() = filters + + /* Details */ + override fun mangaDetailsRequest(manga: SManga): Request { + val mangaId = manga.url.split("/")[2] + + val payload = buildJsonObject { + putJsonObject("variables") { + put("_id", mangaId) + } + put("query", DETAILS_QUERY) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(apiUrl, newHeaders, body) + } + + override fun mangaDetailsParse(response: Response): SManga { + val result = json.decodeFromString(response.body.string()) + val manga = result.data.manga + + val titleStyle = preferences.getString(TITLE_PREF, "romaji")!! + + return SManga.create().apply { + title = when (titleStyle) { + "romaji" -> manga.name + "eng" -> manga.englishName ?: manga.name + else -> manga.nativeName ?: manga.name + } + url = "/manga/${manga._id}/${manga.name.titleToSlug()}" + thumbnail_url = manga.thumbnail.parseThumbnailUrl() + description = Jsoup.parse( + manga.description?.replace("
", "br2n") ?: "", + ).text().replace("br2n", "\n") + description += if (manga.altNames != null) { + "\n\nAlternative Names: ${manga.altNames.joinToString { it.trim() }}" + } else { + "" + } + if (manga.authors?.isNotEmpty() == true) { + author = manga.authors.first().trim() + artist = author + } + genre = "${manga.genres?.joinToString { it.trim() }}, ${manga.tags?.joinToString { it.trim() }}" + status = manga.status.parseStatus() + } + } + + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl${manga.url}" + } + + /* Chapters */ + override fun fetchChapterList(manga: SManga): Observable> { + return client.newCall(chapterListRequest(manga)) + .asObservableSuccess() + .map { response -> + chapterListParse(response, manga) + } + } + + override fun chapterListRequest(manga: SManga): Request { + val mangaId = manga.url.split("/")[2] + + val payload = buildJsonObject { + putJsonObject("variables") { + put("_id", mangaId) + } + put("query", CHAPTERS_QUERY) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(apiUrl, newHeaders, body) + } + + private fun chapterListParse(response: Response, manga: SManga): List { + val result = json.decodeFromString(response.body.string()) + + val chapters = result.data.manga.availableChaptersDetail.sub + + val mangaUrl = manga.url.substringAfter("/manga/") + + return chapters?.map { chapter -> + SChapter.create().apply { + name = "Chapter $chapter" + url = "/read/$mangaUrl/chapter-$chapter-sub" + } + } ?: emptyList() + } + + override fun chapterListParse(response: Response): List { + throw UnsupportedOperationException("Not used") + } + + override fun getChapterUrl(chapter: SChapter): String { + return "$baseUrl${chapter.url}" + } + + /* Pages */ + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .map { response -> + pageListParse(response, chapter) + } + } + + override fun pageListRequest(chapter: SChapter): Request { + val chapterUrl = chapter.url.split("/") + val mangaId = chapterUrl[2] + val chapterNo = chapterUrl[4].split("-")[1] + + val payload = buildJsonObject { + putJsonObject("variables") { + put("mangaId", mangaId) + put("translationType", "sub") + put("chapterString", chapterNo) + } + put("query", PAGE_QUERY) + } + + val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(apiUrl, newHeaders, body) + } + + private fun pageListParse(response: Response, chapter: SChapter): List { + val result = json.decodeFromString(response.body.string()) + val pages = result.data.chapterPages?.edges?.get(0) ?: return emptyList() + + val imageDomain = if (!pages.pictureUrlHead.isNullOrEmpty()) { + pages.pictureUrlHead.let { server -> + if (server.matches(urlRegex)) { + server + } else { + "https://$server" + } + } + } else { + // in rare cases, the api doesn't return server url + // for that, we try to parse the frontend html to get it + val chapterUrl = getChapterUrl(chapter) + val frontendRequest = GET(chapterUrl, headers) + val url = client.newCall(frontendRequest).execute().use { frontendResponse -> + val document = frontendResponse.asJsoup() + val script = document.select("script:containsData(window.__NUXT__)").firstOrNull() + imageUrlFromPageRegex.matchEntire(script.toString()) + ?.groupValues + ?.getOrNull(1) + ?.replace("\\u002F", "/") + ?.substringBeforeLast(pages.pictureUrls.first().toString(), "") + } + url?.takeIf { it.isNotEmpty() } ?: return emptyList() + } + + return pages.pictureUrls.mapIndexed { index, image -> + Page( + index = index, + imageUrl = "$imageDomain${image.url}", + ) + } + } + + override fun pageListParse(response: Response): List { + throw UnsupportedOperationException("Not used") + } + + override fun imageUrlParse(response: Response): String { + throw UnsupportedOperationException("Not used") + } + + /* Helpers */ + private fun String.parseThumbnailUrl(): String { + return if (this.matches(urlRegex)) { + this + } else { + "$image_cdn$this?w=250" + } + } + + private 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 + } + } + + private fun String.titleToSlug() = this.trim() + .lowercase(Locale.US) + .replace(titleSpecialCharactersRegex, "-") + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = DOMAIN_PREF + title = "Preferred domain" + entries = arrayOf("allanime.to", "allanime.co") + entryValues = arrayOf("allanime.to", "allanime.co") + setDefaultValue("allanime.to") + summary = "Requires App Restart" + }.let { screen.addPreference(it) } + + ListPreference(screen.context).apply { + key = TITLE_PREF + title = "Preferred Title Style" + entries = arrayOf("Romaji", "English", "Native") + entryValues = arrayOf("romaji", "eng", "native") + setDefaultValue("romaji") + summary = "%s" + }.let { screen.addPreference(it) } + + SwitchPreferenceCompat(screen.context).apply { + key = SHOW_ADULT_PREF + title = "Show Adult Content" + setDefaultValue(false) + }.let { screen.addPreference(it) } + } + + companion object { + private const val limit = 26 + const val SEARCH_PREFIX = "id:" + private const val image_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/" + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + private val urlRegex = Regex("^https?://.*") + private val titleSpecialCharactersRegex = Regex("[^a-z\\d]+") + private val imageUrlFromPageRegex = Regex("selectedPicturesServer:\\[\\{.*?url:\"(.*?)\".*?\\}\\]") + + private const val DOMAIN_PREF = "pref_domain" + private const val TITLE_PREF = "pref_title" + private const val SHOW_ADULT_PREF = "pref_adult" + } +} diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt new file mode 100644 index 000000000..6d04f6ad1 --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt @@ -0,0 +1,127 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +import kotlinx.serialization.Serializable + +@Serializable +data class ApiPopularResponse( + val data: PopularResultData, +) { + @Serializable + data class PopularResultData( + val queryPopular: QueryPopularData, + ) { + @Serializable + data class QueryPopularData( + val recommendations: List, + ) { + @Serializable + data class Recommendation( + val anyCard: Card? = null, + ) { + @Serializable + data class Card( + val _id: String, + val name: String, + val thumbnail: String, + val englishName: String? = null, + val nativeName: String? = null, + ) + } + } + } +} + +@Serializable +data class ApiSearchResponse( + val data: SearchResultData, +) { + @Serializable + data class SearchResultData( + val mangas: SearchResultMangas, + ) { + @Serializable + data class SearchResultMangas( + val edges: List, + ) { + @Serializable + data class SearchResultEdge( + val _id: String, + val name: String, + val thumbnail: String, + val englishName: String? = null, + val nativeName: String? = null, + ) + } + } +} + +@Serializable +data class ApiMangaDetailsResponse( + val data: MangaDetailsData, +) { + @Serializable + data class MangaDetailsData( + val manga: MangaDetails, + ) { + @Serializable + data class MangaDetails( + val _id: String, + val name: String, + val thumbnail: String, + val description: String?, + val authors: List?, + val genres: List?, + val tags: List?, + val status: String?, + val altNames: List?, + val englishName: String? = null, + val nativeName: String? = null, + ) + } +} + +@Serializable +data class ApiChapterListResponse( + val data: ChapterListData, +) { + @Serializable + data class ChapterListData( + val manga: ChapterList, + ) { + @Serializable + data class ChapterList( + val availableChaptersDetail: AvailableChapters, + ) { + @Serializable + data class AvailableChapters( + val sub: List? = null, + ) + } + } +} + +@Serializable +data class ApiPageListResponse( + val data: PageListData, +) { + @Serializable + data class PageListData( + val chapterPages: PageList?, + ) { + @Serializable + data class PageList( + val edges: List?, + ) { + @Serializable + data class Servers( + val pictureUrlHead: String? = null, + val pictureUrls: List, + ) { + @Serializable + data class PageUrl( + val url: String, + ) + } + } + } +} diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt new file mode 100644 index 000000000..fd5d453c6 --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt @@ -0,0 +1,97 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList + +internal class Genre(name: String) : Filter.TriState(name) + +internal class CountryFilter(name: String, private val countries: List>) : + Filter.Select(name, countries.map { it.first }.toTypedArray()) { + fun getValue() = countries[state].second +} + +internal class GenreFilter(title: String, genres: List) : + Filter.Group(title, genres) + +private val genreList: List = listOf( + Genre("4 Koma"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Cars"), + Genre("Comedy"), + Genre("Cooking"), + Genre("Crossdressing"), + Genre("Dementia"), + Genre("Demons"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Game"), + Genre("Gender Bender"), + Genre("Gyaru"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Isekai"), + Genre("Josei"), + Genre("Kids"), + Genre("Loli"), + Genre("Magic"), + Genre("Manhua"), + Genre("Manhwa"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Medical"), + Genre("Military"), + Genre("Monster Girls"), + Genre("Music"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Parody"), + Genre("Police"), + Genre("Post Apocalyptic"), + Genre("Psychological"), + Genre("Reincarnation"), + Genre("Reverse Harem"), + Genre("Romance"), + Genre("Samurai"), + Genre("School"), + Genre("Sci-Fi"), + Genre("Seinen"), + Genre("Shota"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Space"), + Genre("Sports"), + Genre("Super Power"), + Genre("Supernatural"), + Genre("Suspense"), + Genre("Thriller"), + Genre("Tragedy"), + Genre("Unknown"), + Genre("Vampire"), + Genre("Webtoons"), + Genre("Yaoi"), + Genre("Youkai"), + Genre("Yuri"), + Genre("Zombies"), +) + +private val countryList: List> = listOf( + Pair("All", "ALL"), + Pair("Japan", "JP"), + Pair("China", "CN"), + Pair("Korea", "KR"), +) + +val filters = FilterList( + CountryFilter("Countries", countryList), + GenreFilter("Genres", genreList), +) diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt new file mode 100644 index 000000000..afc07c7e6 --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt @@ -0,0 +1,122 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +fun buildQuery(queryAction: () -> String): String { + return queryAction() + .trimIndent() + .replace("%", "$") +} + +val POPULAR_QUERY: String = buildQuery { + """ + query( + %type: VaildPopularTypeEnumType! + %size: Int! + %page: Int + %dateRange: Int + %allowAdult: Boolean + %allowUnknown: Boolean + ) { + queryPopular( + type: %type + size: %size + dateRange: %dateRange + page: %page + allowAdult: %allowAdult + allowUnknown: %allowUnknown + ) { + recommendations { + anyCard { + _id + name + thumbnail + englishName + nativeName + } + } + } + } + """ +} + +val SEARCH_QUERY: String = buildQuery { + """ + query( + %search: SearchInput + %limit: Int + %page: Int + %translationType: VaildTranslationTypeMangaEnumType + %countryOrigin: VaildCountryOriginEnumType + ) { + mangas( + search: %search + limit: %limit + page: %page + translationType: %translationType + countryOrigin: %countryOrigin + ) { + edges { + _id + name + thumbnail + englishName + nativeName + } + } + } + """ +} + +val DETAILS_QUERY: String = buildQuery { + """ + query (%_id: String!) { + manga( + _id: %_id + ) { + _id + name + thumbnail + description + authors + genres + tags + status + altNames + englishName + nativeName + } + } + """ +} + +val CHAPTERS_QUERY: String = buildQuery { + """ + query (%_id: String!) { + manga( + _id: %_id + ) { + availableChaptersDetail + } + } + """ +} + +val PAGE_QUERY: String = buildQuery { + """ + query( + %mangaId: String!, + %translationType: VaildTranslationTypeMangaEnumType!, + %chapterString: String! + ) { + chapterPages( + mangaId: %mangaId + translationType: %translationType + chapterString: %chapterString + ) { + edges { + pictureUrls + pictureUrlHead + } + } + } + """ +} diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeUrlActivity.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeUrlActivity.kt new file mode 100644 index 000000000..7c844a846 --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeUrlActivity.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class AllAnimeUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val id = pathSegments[1] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${AllAnime.SEARCH_PREFIX}$id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("AllAnimeUrlActivity", e.toString()) + } + } else { + Log.e("AllAnimeUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +}