diff --git a/src/en/inkr/AndroidManifest.xml b/src/en/inkr/AndroidManifest.xml deleted file mode 100644 index b4571bfa8..000000000 --- a/src/en/inkr/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/en/inkr/build.gradle b/src/en/inkr/build.gradle deleted file mode 100644 index 2319c4ac1..000000000 --- a/src/en/inkr/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'INKR' - pkgNameSuffix = 'en.inkr' - extClass = '.Inkr' - extVersionCode = 4 - isNsfw = true -} - -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 161a7729c..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 6a0af18a1..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 0bd16ca94..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 9ec914d7f..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 e13b5e95c..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 611839749..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 da581630f..000000000 --- a/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/Inkr.kt +++ /dev/null @@ -1,333 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.inkr - -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 -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.add -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Inkr : HttpSource() { - - override val name = "INKR" - - override val baseUrl = "https://inkr.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(::buildIdIntercept) - .rateLimit(2, 1, TimeUnit.SECONDS) - .build() - - private val json: Json by injectLazy() - - private var buildId: String? = null - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .build() - - return GET("$baseUrl/_next/data/buildId/index.json", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.pageProps == null) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val comicList = result.pageProps.topCharts!!.topTrending.map(::popularMangaFromObject) - - return MangasPage(comicList, hasNextPage = false) - } - - private fun popularMangaFromObject(comic: InkrComic) = SManga.create().apply { - title = comic.name - thumbnail_url = comic.thumbnailURL - url = "/${comic.oid}" - } - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.pageProps == null) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val comicList = result.pageProps.latestUpdateDetails.map(::latestMangaFromObject) - - return MangasPage(comicList, hasNextPage = false) - } - - private fun latestMangaFromObject(comic: InkrComic) = popularMangaFromObject(comic) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val payload = buildJsonObject { put("query", query) } - - val requestBody = payload.toString().toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Cf-Ipcountry", "VN") - .add("Content-Length", requestBody.contentLength().toString()) - .add("Content-Type", requestBody.contentType().toString()) - .add("Ikc-Platform", "android") - .build() - - return POST("$ICQ_API_URL/title/search", newHeaders, requestBody) - } - - private fun searchDetailsRequest(oids: List): Request { - val payload = buildJsonObject { - putJsonArray("fields") { - add("oid") - add("name") - add("thumbnailURL") - } - put("oids", json.encodeToJsonElement(oids)) - put("url", "$ICD_API_URL/content_json") - } - - val requestBody = payload.toString().toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Cf-Ipcountry", "VN") - .add("Content-Length", requestBody.contentLength().toString()) - .add("Content-Type", requestBody.contentType().toString()) - .add("Ikc-Platform", "android") - .build() - - return POST("$ICD_API_URL/content_json", newHeaders, requestBody) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.data == null) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val searchResults = result.data.title.take(SEARCH_LIMIT) - - val detailsRequest = searchDetailsRequest(searchResults) - val detailsResponse = client.newCall(detailsRequest).execute() - val detailsJson = detailsResponse.body!!.string() - val detailsResult = json.decodeFromString>>(detailsJson) - - if (detailsResult.data == null) { - return MangasPage(emptyList(), hasNextPage = false) - } - - // Use the searchResults to iterate to keep the result order. - val comicList = searchResults.map { oid -> - searchMangaFromObject(detailsResult.data[oid]!!) - } - - return MangasPage(comicList, hasNextPage = false) - } - - private fun searchMangaFromObject(comic: InkrComic) = popularMangaFromObject(comic) - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(mangaDetailsApiRequest(manga.url)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun mangaDetailsApiRequest(mangaUrl: String): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .build() - - val comicId = mangaUrl.substringAfterLast("/") - - return GET("$baseUrl/_next/data/buildId/$comicId.json", newHeaders) - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val newHeaders = headersBuilder() - .removeAll("Accept") - .build() - - return GET(baseUrl + manga.url, newHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.pageProps == null) { - throw Exception(COULD_NOT_PARSE_RESPONSE) - } - - val comic = result.pageProps.titleInfo!! - - title = comic.name - author = comic.creators - .filter { it.role == "story" } - .joinToString(", ") { it.name } - artist = comic.creators - .filter { it.role == "art" } - .joinToString(", ") { it.name } - description = comic.summary.joinToString("\n\n") - .plus(if (comic.extras?.containsKey("Copyright") == true) "\n\n${comic.extras["Copyright"]}" else "") - genre = comic.listGenre?.jsonArray?.joinToString(", ") { it.jsonPrimitive.content } - status = comic.releaseStatus.toStatus() - thumbnail_url = comic.thumbnailURL - } - - override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga.url) - - override fun chapterListParse(response: Response): List { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.pageProps == null) { - throw Exception(COULD_NOT_PARSE_RESPONSE) - } - - val comic = result.pageProps.titleInfo!! - - if (comic.webPreviewingPages.isEmpty()) { - return emptyList() - } - - val previewChapter = SChapter.create().apply { - name = "Preview" - scanlator = comic.creators.firstOrNull { it.role == "publisher" }?.name - date_upload = comic.firstChapterFirstPublishedDate.toDate() - url = "/${comic.oid}" - } - - return listOf(previewChapter) - } - - override fun pageListRequest(chapter: SChapter): Request = mangaDetailsApiRequest(chapter.url) - - override fun pageListParse(response: Response): List { - val result = json.decodeFromString>(response.body!!.string()) - - if (result.pageProps == null) { - throw Exception(COULD_NOT_PARSE_RESPONSE) - } - - val comic = result.pageProps.titleInfo!! - val referer = "$baseUrl/" - - return comic.webPreviewingPages - .mapIndexed { i, page -> Page(i, referer, page.url) } - } - - override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun buildIdIntercept(chain: Interceptor.Chain): Response { - if (chain.request().url.toString().contains("/buildId/").not()) { - return chain.proceed(chain.request()) - } - - if (buildId == null) { - val buildIdRequest = GET(baseUrl, headers) - val buildIdResponse = chain.proceed(buildIdRequest) - val document = buildIdResponse.asJsoup() - - val nextData = document.select("script#__NEXT_DATA__") - .firstOrNull()?.data() ?: throw IOException(COULD_NOT_FIND_BUILD_ID) - val nextJson = json.parseToJsonElement(nextData).jsonObject - - buildId = nextJson["buildId"]!!.jsonPrimitive.content - - buildIdResponse.close() - } - - val newRequestUrl = chain.request().url.toString() - .replace("buildId", buildId!!) - .toHttpUrl() - - val newRequest = chain.request().newBuilder() - .url(newRequestUrl) - .build() - - return chain.proceed(newRequest) - } - - private fun String.toDate(): Long { - return runCatching { DATE_FORMATTER.parse(substringBefore("T"))?.time } - .getOrNull() ?: 0L - } - - private fun String.toStatus(): Int = when (this) { - "ongoing", "hold" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - companion object { - private const val ICQ_API_URL = "https://icq-api.inkr.com/v1" - private const val ICD_API_URL = "https://icd-api.inkr.com/v1" - - private const val ACCEPT_JSON = "application/json, text/plain, */*" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" - - private val JSON_MEDIA_TYPE = "application/json; charset=UTF-8".toMediaType() - - private const val COULD_NOT_FIND_BUILD_ID = "Could not find the API token." - private const val COULD_NOT_PARSE_RESPONSE = "Could not parse the API response." - - private const val SEARCH_LIMIT = 30 - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - } -} diff --git a/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/InkrDto.kt b/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/InkrDto.kt deleted file mode 100644 index 88ccac24a..000000000 --- a/src/en/inkr/src/eu/kanade/tachiyomi/extension/en/inkr/InkrDto.kt +++ /dev/null @@ -1,61 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.inkr - -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - -@Serializable -data class NextJsWrapper( - val pageProps: T? = null -) - -@Serializable -data class InkrResult( - val code: Int = -1, - val data: T? = null -) - -@Serializable -data class InkrHome( - val latestUpdateDetails: List = emptyList(), - val topCharts: InkrHomeCharts? = null -) - -@Serializable -data class InkrHomeCharts( - val topTrending: List = emptyList() -) - -@Serializable -data class InkrSearch( - val title: List = emptyList() -) - -@Serializable -data class InkrTitleInfo( - val titleInfo: InkrComic? = null -) - -@Serializable -data class InkrComic( - val creators: List = emptyList(), - val extras: Map? = emptyMap(), - val firstChapterFirstPublishedDate: String = "", - val listGenre: JsonElement? = null, - val name: String = "", - val oid: String = "", - val releaseStatus: String = "", - val summary: List = emptyList(), - val thumbnailURL: String = "", - val webPreviewingPages: List = emptyList() -) - -@Serializable -data class InkrPerson( - val name: String = "", - val role: String = "" -) - -@Serializable -data class InkrPage( - val url: String = "" -)