diff --git a/src/en/inkr/build.gradle b/src/en/inkr/build.gradle deleted file mode 100644 index 70ad0f0f1..000000000 --- a/src/en/inkr/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'INKR' - pkgNameSuffix = 'en.inkr' - extClass = '.INKR' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/inkr/res/mipmap-hdpi/ic_launcher.png b/src/en/inkr/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 43925d0cf..000000000 Binary files a/src/en/inkr/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/inkr/res/mipmap-mdpi/ic_launcher.png b/src/en/inkr/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5ce837cbe..000000000 Binary files a/src/en/inkr/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/inkr/res/mipmap-xhdpi/ic_launcher.png b/src/en/inkr/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6b530ea4e..000000000 Binary files a/src/en/inkr/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/inkr/res/mipmap-xxhdpi/ic_launcher.png b/src/en/inkr/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0e0f3a788..000000000 Binary files a/src/en/inkr/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/inkr/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/inkr/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e5999d8fd..000000000 Binary files a/src/en/inkr/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/inkr/res/web_hi_res_512.png b/src/en/inkr/res/web_hi_res_512.png deleted file mode 100644 index ed5b8330a..000000000 Binary files a/src/en/inkr/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/INKR.kt b/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/INKR.kt deleted file mode 100644 index 0415eecc2..000000000 --- a/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/INKR.kt +++ /dev/null @@ -1,353 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.inkr - -import com.github.salomonbrys.kotson.jsonObject -import com.github.salomonbrys.kotson.set -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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 java.util.ArrayList -import kotlin.experimental.and -import kotlin.experimental.xor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import okhttp3.ResponseBody -import org.json.JSONObject -import rx.Observable - -/** - * INKR source - same old MR code, though - */ - -class INKR : HttpSource() { - - override val name = "INKR" - - override val baseUrl = "https://mangarock.com" - - private val apiUrl = "https://api.mangarockhd.com/query/android500" - - override val lang = "en" - - override val supportsLatest = true - - // Handles the page decoding - override val client: OkHttpClient = super.client.newBuilder().addInterceptor(fun(chain): Response { - val url = chain.request().url().toString() - val response = chain.proceed(chain.request()) - if (!url.endsWith(".mri")) return response - - val decoded: ByteArray = decodeMri(response) - val mediaType = MediaType.parse("image/webp") - val rb = ResponseBody.create(mediaType, decoded) - return response.newBuilder().body(rb).build() - }).build() - - override fun latestUpdatesRequest(page: Int) = GET("$apiUrl/mrs_latest") - - override fun latestUpdatesParse(response: Response): MangasPage { - val res = response.body()!!.string() - val list = getMangaListFromJson(res) - return getMangasPageFromJsonList(list) - } - - override fun popularMangaRequest(page: Int): Request { - val jsonType = MediaType.parse("application/jsonType; charset=utf-8") - - val body = RequestBody.create(jsonType, jsonObject( - "status" to "all" - ).toString()) - - return POST("$apiUrl/mrs_filter", headers, body) - } - - override fun popularMangaParse(response: Response) = searchMangaParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val jsonType = MediaType.parse("application/jsonType; charset=utf-8") - - // Filter - if (query.isBlank()) { - var status = "" - var orderBy = "" - val genres = jsonObject() - filters.forEach { filter -> - when (filter) { - is StatusFilter -> { - status = when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "completed" - Filter.TriState.STATE_EXCLUDE -> "ongoing" - else -> "all" - } - } - is SortBy -> { - orderBy = filter.toUriPart() - } - is GenreList -> { - filter.state - .filter { it.state != Filter.TriState.STATE_IGNORE } - .forEach { genres[it.id] = it.state == Filter.TriState.STATE_INCLUDE } - } - } - } - - val body = RequestBody.create(jsonType, jsonObject( - "status" to status, - "genres" to genres, - "order" to orderBy - ).toString()) - return POST("$apiUrl/mrs_filter", headers, body) - } - - // Regular search - val body = RequestBody.create(jsonType, jsonObject( - "type" to "series", - "keywords" to query - ).toString()) - return POST("$apiUrl/mrs_search", headers, body) - } - - override fun searchMangaParse(response: Response): MangasPage { - val idArray = JSONObject(response.body()!!.string()).getJSONArray("data") - - val jsonType = MediaType.parse("application/jsonType; charset=utf-8") - val body = RequestBody.create(jsonType, idArray.toString()) - val metaRes = client.newCall(POST("https://api.mangarockhd.com/meta", headers, body)).execute().body()!!.string() - - val res = JSONObject(metaRes).getJSONObject("data") - val mangas = ArrayList(res.length()) - for (i in 0 until idArray.length()) { - val id = idArray.get(i).toString() - mangas.add(parseMangaJson(res.getJSONObject(id))) - } - return MangasPage(mangas, false) - } - - private fun getMangaListFromJson(json: String): List { - val arr = JSONObject(json).getJSONArray("data") - val mangaJson = ArrayList(arr.length()) - for (i in 0 until arr.length()) { - mangaJson.add(arr.getJSONObject(i)) - } - return mangaJson - } - - private fun getMangasPageFromJsonList(arr: List): MangasPage { - val mangas = ArrayList(arr.size) - for (obj in arr) { - mangas.add(parseMangaJson(obj)) - } - return MangasPage(mangas, false) - } - - private fun parseMangaJson(obj: JSONObject): SManga { - return SManga.create().apply { - setUrlWithoutDomain("/manga/${obj.getString("oid")}") - title = obj.getString("name") - thumbnail_url = obj.getString("thumbnail") - status = if (obj.getBoolean("completed")) SManga.COMPLETED else SManga.ONGOING - } - } - private fun sortByRank(arr: List): List { - return arr.sortedBy { it.getInt("rank") } - } - - // Avoid directly overriding mangaDetailsRequest so that "Open in browser" action uses the - // "real" URL - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(getMangaApiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - // Always returns the "real" URL for the "Open in browser" action - override fun mangaDetailsRequest(manga: SManga): Request { - // Handle older entries with API URL ("/info?oid=mrs-series-...") - if (manga.url.startsWith("/info")) { - val oid = manga.url.substringAfterLast("=") - return GET("$baseUrl/manga/$oid", headers) - } - - return super.mangaDetailsRequest(manga) - } - - override fun chapterListRequest(manga: SManga) = getMangaApiRequest(manga) - - private fun getMangaApiRequest(manga: SManga): Request { - // Handle older entries with API URL ("/info?oid=mrs-series-...") - if (manga.url.startsWith("/info")) { - return GET("$apiUrl${manga.url}&Country=", headers) - } - - val oid = manga.url.substringAfterLast("/") - return GET("$apiUrl/info?oid=$oid&Country=", headers) - } - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val obj = JSONObject(response.body()!!.string()).getJSONObject("data") - - title = obj.getString("name") - description = obj.getString("description") - - if (obj.isNull("authors")) { - artist = "" - author = "" - } else { - val people = obj.getJSONArray("authors") - val authors = ArrayList() - val artists = ArrayList() - for (i in 0 until people.length()) { - val person = people.getJSONObject(i) - when (person.getString("role")) { - "art" -> artists.add(person.getString("name")) - "story" -> authors.add(person.getString("name")) - } - } - artist = artists.sorted().joinToString(", ") - author = authors.sorted().joinToString(", ") - } - - val categories = obj.getJSONArray("rich_categories") - val genres = ArrayList(categories.length()) - for (i in 0 until categories.length()) { - genres.add(categories.getJSONObject(i).getString("name")) - } - genre = genres.sorted().joinToString(", ") - - status = if (obj.getBoolean("completed")) SManga.COMPLETED else SManga.ONGOING - thumbnail_url = obj.getString("thumbnail") - } - - override fun chapterListParse(response: Response): List { - val body = response.body()!!.string() - - if (body == "Manga is licensed") { - throw Exception("Manga has been removed from INKR, please migrate to another source") - } - - val obj = JSONObject(body).getJSONObject("data") - val chapters = ArrayList() - val arr = obj.getJSONArray("chapters") - // Iterate backwards to match website's sorting - for (i in arr.length() - 1 downTo 0) { - val chapter = arr.getJSONObject(i) - chapters.add(SChapter.create().apply { - name = chapter.getString("name") - date_upload = chapter.getString("updatedAt").toLong() * 1000 - url = "/pagesv3?oid=${chapter.getString("oid")}" - }) - } - return chapters - } - - override fun pageListRequest(chapter: SChapter) = GET(apiUrl + chapter.url, headers) - - override fun pageListParse(response: Response): List { - val obj = JSONObject(response.body()!!.string()).getJSONArray("data") - val pages = ArrayList() - for (i in 0 until obj.length()) { - pages.add(Page(i, "", obj.getJSONObject(i).getString("url"))) - } - return pages - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") - - // See drawWebpToCanvas function in the site's client.js file - // Extracted code: https://jsfiddle.net/6h2sLcs4/30/ - private fun decodeMri(response: Response): ByteArray { - val data = response.body()!!.bytes() - - // Decode file if it starts with "E" (space when XOR-ed later) - if (data[0] != 69.toByte()) return data - - // Reconstruct WEBP header - // Doc: https://developers.google.com/speed/webp/docs/riff_container#webp_file_header - val buffer = ByteArray(data.size + 15) - val size = data.size + 7 - buffer[0] = 82 // R - buffer[1] = 73 // I - buffer[2] = 70 // F - buffer[3] = 70 // F - buffer[4] = (255.toByte() and size.toByte()) - buffer[5] = (size ushr 8).toByte() and 255.toByte() - buffer[6] = (size ushr 16).toByte() and 255.toByte() - buffer[7] = (size ushr 24).toByte() and 255.toByte() - buffer[8] = 87 // W - buffer[9] = 69 // E - buffer[10] = 66 // B - buffer[11] = 80 // P - buffer[12] = 86 // V - buffer[13] = 80 // P - buffer[14] = 56 // 8 - - // Decrypt file content using XOR cipher with 101 as the key - val cipherKey = 101.toByte() - for (r in data.indices) { - buffer[r + 15] = cipherKey xor data[r] - } - - return buffer - } - - private class StatusFilter : Filter.TriState("Completed") - - private class SortBy : UriPartFilter("Sort by", arrayOf( - Pair("Name", "name"), - Pair("Rank", "rank") - )) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - // Search and filter don't work at the same time - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - StatusFilter(), - SortBy(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll('._2DMqI .mdl-checkbox')].map(n => `Genre("${n.querySelector('.mdl-checkbox__label').innerText}", "${n.querySelector('input').dataset.oid}")`).sort().join(',\n') - // on https://mangarock.com/manga - private fun getGenreList() = listOf( - Genre("Action", "mrs-genre-304068"), - Genre("Adventure", "mrs-genre-304087"), - Genre("Comedy", "mrs-genre-304069"), - Genre("Drama", "mrs-genre-304177"), - Genre("Ecchi", "mrs-genre-304074"), - Genre("Fantasy", "mrs-genre-304089"), - Genre("Historical", "mrs-genre-304306"), - Genre("Horror", "mrs-genre-304259"), - Genre("Magic", "mrs-genre-304090"), - Genre("Martial Arts", "mrs-genre-304072"), - Genre("Mecha", "mrs-genre-304245"), - Genre("Military", "mrs-genre-304091"), - Genre("Music", "mrs-genre-304589"), - Genre("Psychological", "mrs-genre-304176"), - Genre("Romance", "mrs-genre-304073"), - Genre("School Life", "mrs-genre-304076"), - Genre("Shounen Ai", "mrs-genre-304307"), - Genre("Slice of Life", "mrs-genre-304195"), - Genre("Sports", "mrs-genre-304367"), - Genre("Supernatural", "mrs-genre-304067"), - Genre("Vampire", "mrs-genre-304765") - ) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -}