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)
+ }
+}