diff --git a/src/all/hitomi/build.gradle b/src/all/hitomi/build.gradle index 108ea0e09..df804d52f 100644 --- a/src/all/hitomi/build.gradle +++ b/src/all/hitomi/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Hitomi.la' pkgNameSuffix = 'all.hitomi' extClass = '.HitomiFactory' - extVersionCode = 5 + extVersionCode = 6 libVersion = '1.2' containsNsfw = true } diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt index c4c2b926e..257705ee8 100644 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt +++ b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt @@ -2,10 +2,6 @@ package eu.kanade.tachiyomi.extension.all.hitomi import android.app.Application import android.content.SharedPreferences -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.ConfigurableSource @@ -16,6 +12,10 @@ 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.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -24,6 +24,7 @@ import rx.Single import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.util.Locale import androidx.preference.CheckBoxPreference as AndroidXCheckBoxPreference import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen @@ -41,6 +42,8 @@ open class Hitomi(override val lang: String, private val nozomiLang: String) : H override val baseUrl = BASE_URL + private val json: Json by injectLazy() + // Popular override fun fetchPopularManga(page: Int): Observable { @@ -271,21 +274,25 @@ open class Hitomi(override val lang: String, private val nozomiLang: String) : H return GET("$LTN_BASE_URL/galleries/${hlIdFromUrl(chapter.url)}.js") } - private val jsonParser = JsonParser() - override fun pageListParse(response: Response): List { - val str = response.body!!.string() - val json = jsonParser.parse(str.removePrefix("var galleryinfo = ")) - return json["files"].array.mapIndexed { i, jsonElement -> - val hash = jsonElement["hash"].string - val ext = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) jsonElement["name"].string.split('.').last() else "webp" - val path = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) "images" else "webp" + val jsonRaw = response.body!!.string().removePrefix("var galleryinfo = ") + val jsonResult = json.parseToJsonElement(jsonRaw).jsonObject + + return jsonResult["files"]!!.jsonArray.mapIndexed { i, jsonEl -> + val jsonObj = jsonEl.jsonObject + val hash = jsonObj["hash"]!!.jsonPrimitive.content + val hasWebp = jsonObj["haswebp"]!!.jsonPrimitive.content == "0" + val hasAvif = jsonObj["hasavif"]!!.jsonPrimitive.content == "0" + val ext = if (hasWebp || !hitomiAlwaysWebp()) + jsonObj["name"]!!.jsonPrimitive.content.substringAfterLast(".") else "webp" + val path = if (hasWebp || !hitomiAlwaysWebp()) + "images" else "webp" val hashPath1 = hash.takeLast(1) val hashPath2 = hash.takeLast(3).take(2) // https://ltn.hitomi.la/reader.js // function make_image_element() - val secondSubdomain = if (jsonElement["haswebp"].string == "0" && jsonElement["hasavif"].string == "0") "b" else "a" + val secondSubdomain = if (hasWebp && hasAvif) "b" else "a" Page(i, "", "https://${firstSubdomainFromGalleryId(hashPath2)}$secondSubdomain.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext") } diff --git a/src/en/homeheroscans/build.gradle b/src/en/homeheroscans/build.gradle index d4f093142..98209a05d 100644 --- a/src/en/homeheroscans/build.gradle +++ b/src/en/homeheroscans/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Home Hero Scans' pkgNameSuffix = "en.homeheroscans" extClass = '.HomeHeroScans' - extVersionCode = 3 + extVersionCode = 4 libVersion = '1.2' } diff --git a/src/en/homeheroscans/src/eu/kanade/tachiyomi/extension/en/homeheroscans/HomeHeroScans.kt b/src/en/homeheroscans/src/eu/kanade/tachiyomi/extension/en/homeheroscans/HomeHeroScans.kt index 4880098a6..87fbe3c81 100644 --- a/src/en/homeheroscans/src/eu/kanade/tachiyomi/extension/en/homeheroscans/HomeHeroScans.kt +++ b/src/en/homeheroscans/src/eu/kanade/tachiyomi/extension/en/homeheroscans/HomeHeroScans.kt @@ -1,29 +1,36 @@ package eu.kanade.tachiyomi.extension.en.homeheroscans -import com.github.salomonbrys.kotson.forEach -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST 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 kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import rx.Observable +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat +import java.util.Locale open class HomeHeroScans : HttpSource() { - override val lang = "en" + final override val name = "Home Hero Scans" + + override val lang = "en" + final override val baseUrl = "https://hhs.vercel.app" + final override val supportsLatest = false + private val json: Json by injectLazy() + // { seriesId |---> chapter |---> numPages } private val chapterNumberCache: MutableMap> = mutableMapOf() @@ -42,38 +49,39 @@ open class HomeHeroScans : HttpSource() { } val memoizedFetchPopularManga = memoizeObservable { page: Int -> super.fetchPopularManga(page) } - // reduce number of times we call their api, user can force a call to api by relaunching the app - override fun fetchPopularManga(page: Int) = memoizedFetchPopularManga(page) - override fun popularMangaRequest(page: Int) = GET("$baseUrl/series.json", headers) - override fun popularMangaParse(response: Response): MangasPage { - val res = JsonParser.parseString(response.body?.string()).asJsonObject - val manga = mutableListOf() - res.forEach { s, jsonElement -> - val data = jsonElement.asJsonObject - fun get(k: String) = data[k]?.asString - manga.add( - SManga.create().apply { - artist = get("artist") - author = get("author") - description = get("description") - genre = get("genre") - title = get("title")!! - thumbnail_url = "$baseUrl${get("cover")}" - url = "/series?series=$s" - status = SManga.ONGOING // isn't reported - } - ) + // Reduce number of times we call their api, user can force a call to api by relaunching the app + override fun fetchPopularManga(page: Int) = memoizedFetchPopularManga(page) + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/series.json", headers) + + override fun popularMangaParse(response: Response): MangasPage { + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject + + val mangaList = jsonResult.entries.map { entry -> + val jsonObj = entry.value.jsonObject + + SManga.create().apply { + artist = jsonObj["artist"]!!.jsonPrimitive.content + author = jsonObj["author"]!!.jsonPrimitive.content + description = jsonObj["description"]!!.jsonPrimitive.content + genre = jsonObj["genre"]!!.jsonPrimitive.content + title = jsonObj["title"]!!.jsonPrimitive.content + thumbnail_url = baseUrl + jsonObj["cover"]!!.jsonPrimitive.content + url = "/series?series=" + entry.key + status = SManga.ONGOING + } } - return MangasPage(manga, false) + + return MangasPage(mangaList, hasNextPage = false) } - // latest + // Latest override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used") + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used") - // search - + // Search private fun getMangaId(s: String): String? { return s.toHttpUrlOrNull()?.let { url -> // allow for trailing slash @@ -89,34 +97,43 @@ open class HomeHeroScans : HttpSource() { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { if (!query.startsWith(URL_SEARCH_PREFIX)) - // site doesn't have a search, so just return the popular page + // Site doesn't have a search, so just return the popular page return fetchPopularManga(page) return getMangaId(query.substringAfter(URL_SEARCH_PREFIX))?.let { id -> fetchBySeriesId(id).map { MangasPage(it, false) } } ?: Observable.just(MangasPage(emptyList(), false)) } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + throw UnsupportedOperationException("Not used") + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not used") - // chapter list (is paginated), + // Chapter list (is paginated) override fun chapterListParse(response: Response): List { - return JsonParser.parseString(response.body?.string()!!).asJsonObject["data"].asJsonArray.map { - val chapterData = it.asJsonObject["data"].asJsonObject - fun get(k: String) = chapterData[k].asString - if (chapterNumberCache[get("series")] == null) - chapterNumberCache[get("series")] = mutableMapOf() - chapterNumberCache[get("series")]!![get("chapter")] = get("numPages").toInt() - SChapter.create().apply { - url = "/chapter?series=${get("series")}&ch=${get("chapter")}" + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject - name = "Ch. ${get("chapter")} ${get("title")}" + return jsonResult["data"]!!.jsonArray + .map { jsonEl -> + val jsonObj = jsonEl.jsonObject + val chapterData = jsonObj["data"]!!.jsonObject + val series = chapterData["series"]!!.jsonPrimitive.content + val chapter = chapterData["chapter"]!!.jsonPrimitive.content - date_upload = SimpleDateFormat("MM/dd/yyyy").parse(get("date")).time + if (chapterNumberCache[series] == null) { + chapterNumberCache[series] = mutableMapOf() + } - chapter_number = get("chapter").toFloat() + chapterNumberCache[series]!![chapter] = chapterData["numPages"]!!.jsonPrimitive.content.toInt() + + SChapter.create().apply { + name = "Ch. $chapter ${chapterData["title"]!!.jsonPrimitive.content}" + chapter_number = chapter.toFloatOrNull() ?: -1f + date_upload = DATE_FORMATTER.parse(chapterData["date"]!!.jsonPrimitive.content)?.time ?: 0L + url = "/chapter?series=$series&ch=$chapter" + } } - }.sortedByDescending { it.chapter_number } + .sortedByDescending { it.chapter_number } } override fun chapterListRequest(manga: SManga): Request { @@ -134,7 +151,8 @@ open class HomeHeroScans : HttpSource() { } ?: Observable.just(manga) override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used") - // default implementation of mangaDetailsRequest has to exist for webview to work + + // Default implementation of mangaDetailsRequest has to exist for webview to work // override fun mangaDetailsRequest(manga: SManga) = throw UnsupportedOperationException("Not used") // Pages @@ -146,9 +164,9 @@ open class HomeHeroScans : HttpSource() { return if (chapterPages() != null) { Observable.just(chapterPages()!!) } else { - // has side effect of setting numPages in cache + // Has side effect of setting numPages in cache fetchChapterList( - // super hacky, url is wrong but has query parameter we need + // Super hacky, url is wrong but has query parameter we need SManga.create().apply { this.url = chapter.url } ).map { chapterPages() @@ -161,10 +179,14 @@ open class HomeHeroScans : HttpSource() { } override fun pageListParse(response: Response) = throw UnsupportedOperationException("Not used") + override fun pageListRequest(chapter: SChapter) = throw UnsupportedOperationException("Not Used") - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") + + override fun imageUrlParse(response: Response): String = "" companion object { + private val DATE_FORMATTER = SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH) + const val URL_SEARCH_PREFIX = "url:" } } diff --git a/src/en/honkaiimpact3/build.gradle b/src/en/honkaiimpact3/build.gradle index bc356d0ef..6b068e7ac 100644 --- a/src/en/honkaiimpact3/build.gradle +++ b/src/en/honkaiimpact3/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'HonkaiImpact3' pkgNameSuffix = 'en.honkaiimpact' extClass = '.Honkaiimpact' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt b/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt index dab4a863d..4939158f1 100644 --- a/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt +++ b/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt @@ -1,34 +1,39 @@ package eu.kanade.tachiyomi.extension.en.honkaiimpact -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList 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.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.float +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit +// Info - Based of BH3 +// This is the english version of the site class Honkaiimpact : ParsedHttpSource() { - // Info - Based of BH3 - // This is the english version of the site override val name = "Honkai Impact 3rd" + override val baseUrl = "https://manga.honkaiimpact3.com" + override val lang = "en" + override val supportsLatest = false + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) @@ -36,25 +41,33 @@ class Honkaiimpact : ParsedHttpSource() { .followRedirects(true) .build() + private val json: Json by injectLazy() + // Popular override fun popularMangaSelector() = "a[href*=book]" override fun popularMangaNextPageSelector(): String? = null + override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers) + override fun popularMangaFromElement(element: Element) = mangaFromElement(element) // Latest override fun latestUpdatesSelector() = throw Exception("Not Used") override fun latestUpdatesNextPageSelector(): String? = null + override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") + override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) // Search override fun searchMangaSelector() = throw Exception("Not Used") override fun searchMangaNextPageSelector(): String? = null + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") + override fun searchMangaFromElement(element: Element) = mangaFromElement(element) private fun mangaFromElement(element: Element): SManga { @@ -78,22 +91,20 @@ class Honkaiimpact : ParsedHttpSource() { override fun chapterListSelector() = throw Exception("Not Used") override fun chapterFromElement(element: Element) = throw Exception("Not Used") + override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers) + override fun chapterListParse(response: Response): List { - val jsondata = response.body!!.string() - val json = JsonParser().parse(jsondata).asJsonArray - val chapters = mutableListOf() - json.forEach { - chapters.add(createChapter(it)) - } - return chapters + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray + + return jsonResult.map { jsonEl -> createChapter(jsonEl.jsonObject) } } - private fun createChapter(json: JsonElement) = SChapter.create().apply { - name = json["title"].string - url = "/book/${json["bookid"].int}/${json["chapterid"].int}" - date_upload = parseDate(json["timestamp"].string) - chapter_number = json["chapterid"].float + private fun createChapter(jsonObj: JsonObject) = SChapter.create().apply { + name = jsonObj["title"]!!.jsonPrimitive.content + url = "/book/${jsonObj["bookid"]!!.jsonPrimitive.int}/${jsonObj["chapterid"]!!.jsonPrimitive.int}" + date_upload = parseDate(jsonObj["timestamp"]!!.jsonPrimitive.content) + chapter_number = jsonObj["chapterid"]!!.jsonPrimitive.float } private fun parseDate(date: String): Long { @@ -101,13 +112,12 @@ class Honkaiimpact : ParsedHttpSource() { } // Manga Pages - override fun pageListParse(response: Response): List = mutableListOf().apply { - val body = response.asJsoup() - body.select("img.lazy.comic_img")?.forEach { - add(Page(size, "", it.attr("data-original"))) + + override fun pageListParse(document: Document): List { + return document.select("img.lazy.comic_img").mapIndexed { i, el -> + Page(i, "", el.attr("data-original")) } } - override fun pageListParse(document: Document) = throw Exception("Not Used") - override fun imageUrlParse(document: Document) = throw Exception("Not Used") + override fun imageUrlParse(document: Document) = "" } diff --git a/src/en/mangadog/build.gradle b/src/en/mangadog/build.gradle index a684883f5..a1cc0ef09 100644 --- a/src/en/mangadog/build.gradle +++ b/src/en/mangadog/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'MangaDog' pkgNameSuffix = 'en.mangadog' extClass = '.Mangadog' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt b/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt index 0ad823e19..5e182e828 100644 --- a/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt +++ b/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt @@ -1,10 +1,6 @@ package eu.kanade.tachiyomi.extension.en.mangadog import android.net.Uri -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -13,23 +9,39 @@ 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.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.float +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale class Mangadog : HttpSource() { override val name = "MangaDog" + override val baseUrl = "https://mangadog.club" + private val cdn = "https://cdn.mangadog.club" + override val lang = "en" + override val supportsLatest = true + override val client: OkHttpClient = network.cloudflareClient + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int) = GET("$baseUrl/index/classification/search_test?page=$page&state=all&demographic=all&genre=all", headers) + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/index/latestupdate/getUpdateResult?page=$page", headers) + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val uri = Uri.parse("$baseUrl/index/keywordsearch/index").buildUpon() .appendQueryParameter("query", query) @@ -37,61 +49,49 @@ class Mangadog : HttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - // val page = response.request.url.queryParameterValues("page").toString().toInt() - val jsonData = response.body!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"]["data"] - val mangas = mutableListOf() - for (i in 0 until data.asJsonArray.size()) { - mangas.add(popularMangaFromjson(data[i])) + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject + + val mangaList = jsonResult["data"]!!.jsonObject["data"]!!.jsonArray.map { jsonEl -> + popularMangaFromJson(jsonEl.jsonObject) } - val hasNextPage = true // page < results["data"]["pageNum"].int - return MangasPage(mangas, hasNextPage) + return MangasPage(mangaList, hasNextPage = true) } - private fun popularMangaFromjson(json: JsonElement): SManga { - val manga = SManga.create() - manga.title = json["name"].string.trim() - manga.thumbnail_url = cdn + json["image"].string.replace("\\/", "/") - val searchname = json["search_name"].string - val id = json["id"].string - manga.url = "/detail/$searchname/$id.html" - return manga + private fun popularMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { + title = jsonObj["name"]!!.jsonPrimitive.content.trim() + thumbnail_url = cdn + jsonObj["image"]!!.jsonPrimitive.content.replace("\\/", "/") + + val searchName = jsonObj["search_name"]!!.jsonPrimitive.content + val id = jsonObj["id"]!!.jsonPrimitive.content + url = "/detail/$searchName/$id.html" } override fun latestUpdatesParse(response: Response): MangasPage { - val jsonData = response.body!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"] - val mangas = mutableListOf() - for (i in 0 until data.asJsonArray.size()) { - mangas.add(popularMangaFromjson(data[i])) + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject + + val mangaList = jsonResult["data"]!!.jsonArray.map { jsonEl -> + popularMangaFromJson(jsonEl.jsonObject) } - val hasNextPage = true // data.asJsonArray.size()>18 - return MangasPage(mangas, hasNextPage) + return MangasPage(mangaList, hasNextPage = true) } override fun searchMangaParse(response: Response): MangasPage { - val jsonData = response.body!!.string() - val results = JsonParser().parse(jsonData) - val data = results["suggestions"] - val mangas = mutableListOf() - for (i in 0 until 1) { - mangas.add(searchMangaFromjson(data[i])) + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject + + val mangaList = jsonResult["suggestions"]!!.jsonArray.map { jsonEl -> + searchMangaFromJson(jsonEl.jsonObject) } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) + return MangasPage(mangaList, hasNextPage = false) } - private fun searchMangaFromjson(json: JsonElement): SManga { - val manga = SManga.create() - manga.title = json["value"].string.trim() - val data = json["data"].string.replace("\\/", "/") - manga.url = "/detail/$data.html" - return manga + private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply{ + title = jsonObj["value"]!!.jsonPrimitive.content.trim() + + val dataValue = jsonObj["data"]!!.jsonPrimitive.content.replace("\\/", "/") + url = "/detail/$dataValue.html" } override fun chapterListRequest(manga: SManga): Request { @@ -100,57 +100,51 @@ class Mangadog : HttpSource() { } override fun chapterListParse(response: Response): List { - val jsonData = response.body!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"]["data"] - val chapters = mutableListOf() - for (i in 0 until data.asJsonArray.size()) { - chapters.add(chapterFromjson(data[i])) + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject + + return jsonResult["data"]!!.jsonObject["data"]!!.jsonArray.map { jsonEl -> + chapterFromJson(jsonEl.jsonObject) } - return chapters } - private fun chapterFromjson(json: JsonElement): SChapter { - val chapter = SChapter.create() - val searchname = json["search_name"].string - val id = json["comic_id"].string - chapter.url = "/read/read/$searchname/$id.html" // The url should include the manga name but it doesn't seem to matter - chapter.name = json["name"].string.trim() - chapter.chapter_number = json["obj_id"].asFloat - chapter.date_upload = parseDate(json["create_date"].string) - return chapter + private fun chapterFromJson(jsonObj: JsonObject): SChapter = SChapter.create().apply { + // The url should include the manga name but it doesn't seem to matter + val searchname = jsonObj["search_name"]!!.jsonPrimitive.content + val id = jsonObj["comic_id"]!!.jsonPrimitive.content + url = "/read/read/$searchname/$id.html" + + name = jsonObj["name"]!!.jsonPrimitive.content.trim() + chapter_number = jsonObj["obj_id"]!!.jsonPrimitive.float + date_upload = parseDate(jsonObj["create_date"]!!.jsonPrimitive.content) } private fun parseDate(date: String): Long { return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(date)?.time ?: 0L } - override fun mangaDetailsParse(response: Response): SManga { + override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply{ val document = response.asJsoup() - val manga = SManga.create() - manga.thumbnail_url = document.select("img.detail-post-img").attr("abs:src") - manga.description = document.select("h2.fs15 + p").text().trim() - manga.author = document.select("a[href*=artist]").text() - manga.artist = document.select("a[href*=artist]").text() - val glist = document.select("div.col-sm-10.col-xs-9.text-left.toe.mlr0.text-left-m a[href*=genre]").map { it.text().substringAfter(",").capitalize() } - manga.genre = glist.joinToString(", ") - manga.status = when (document.select("span.label.label-success").first().text()) { + + thumbnail_url = document.select("img.detail-post-img").attr("abs:src") + description = document.select("h2.fs15 + p").text().trim() + author = document.select("a[href*=artist]").text() + artist = document.select("a[href*=artist]").text() + genre = document.select("div.col-sm-10.col-xs-9.text-left.toe.mlr0.text-left-m a[href*=genre]") + .joinToString { it.text().substringAfter(",").capitalize(Locale.ROOT) } + status = when (document.select("span.label.label-success").first().text()) { "update" -> SManga.ONGOING "finished" -> SManga.COMPLETED else -> SManga.UNKNOWN } - return manga } override fun pageListParse(response: Response): List { - val body = response.asJsoup() - val pages = mutableListOf() - val elements = body.select("img[data-src]") - for (i in 0 until elements.size) { - pages.add(Page(i, "", elements[i].select("img").attr("data-src"))) + val document = response.asJsoup() + + return document.select("img[data-src]").mapIndexed { i, el -> + Page(i, "", el.select("img").attr("data-src")) } - return pages } - override fun imageUrlParse(response: Response) = throw Exception("Not used") + override fun imageUrlParse(response: Response) = "" } diff --git a/src/en/mangahasu/build.gradle b/src/en/mangahasu/build.gradle index 7b5ca851d..949eed887 100644 --- a/src/en/mangahasu/build.gradle +++ b/src/en/mangahasu/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Mangahasu' pkgNameSuffix = 'en.mangahasu' extClass = '.Mangahasu' - extVersionCode = 11 + extVersionCode = 12 libVersion = '1.2' } diff --git a/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt b/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt index d45048966..342e428ed 100644 --- a/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt +++ b/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt @@ -1,11 +1,6 @@ package eu.kanade.tachiyomi.extension.en.mangahasu import android.util.Base64 -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList @@ -13,12 +8,18 @@ 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.ParsedHttpSource +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -34,9 +35,10 @@ class Mangahasu : ParsedHttpSource() { override val client: OkHttpClient = network.cloudflareClient - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } + private val json: Json by injectLazy() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Referer", baseUrl) override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/directory.html?page=$page", headers) @@ -143,34 +145,40 @@ class Mangahasu : ParsedHttpSource() { } override fun pageListParse(document: Document): List { - // Grab All Pages from site // Some images are place holders on new chapters. - val pages = mutableListOf().apply { - document.select("div.img img").forEach { - val pageNumber = it.attr("class").substringAfter("page").toInt() - add(Page(pageNumber, "", it.attr("src"))) + val pageList = document.select("div.img img") + .mapIndexed { i, el -> + val pageNumber = el.attr("class").substringAfter("page").toInt() + Page(pageNumber, "", el.attr("src")) } - } + .toMutableList() // Some images are not yet loaded onto Mangahasu's image server. // Decode temporary URLs and replace placeholder images. - val lstDUrls = - document.select("script:containsData(lstDUrls)").html().substringAfter("lstDUrls") - .substringAfter("\"").substringBefore("\"") - if (lstDUrls != "W10=") { // Base64 = [] or empty file - val decoded = String(Base64.decode(lstDUrls, Base64.DEFAULT)) - val json = JsonParser().parse(decoded).array - json.forEach { - val pageNumber = it["page"].int - pages.removeAll { page -> page.index == pageNumber } - pages.add(Page(pageNumber, "", it["url"].string)) + val lstDUrls = document.select("script:containsData(lstDUrls)") + .html() + .substringAfter("lstDUrls") + .substringAfter("\"") + .substringBefore("\"") + + // Base64 = [] or empty file + if (lstDUrls != "W10=") { + val jsonRaw = String(Base64.decode(lstDUrls, Base64.DEFAULT)) + val jsonArr = json.parseToJsonElement(jsonRaw).jsonArray + + jsonArr.forEach { jsonEl -> + val jsonObj = jsonEl.jsonObject + val pageNumber = jsonObj["page"]!!.jsonPrimitive.int + + pageList.removeAll { page -> page.index == pageNumber } + pageList.add(Page(pageNumber, "", jsonObj["url"]!!.jsonPrimitive.content)) } } - pages.sortBy { page -> page.index } - return pages + + return pageList.sortedBy { page -> page.index } } override fun imageUrlParse(document: Document) = "" diff --git a/src/es/kumanga/build.gradle b/src/es/kumanga/build.gradle index d14eb5876..8c654a7bb 100755 --- a/src/es/kumanga/build.gradle +++ b/src/es/kumanga/build.gradle @@ -1,17 +1,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Kumanga' pkgNameSuffix = 'es.kumanga' extClass = '.Kumanga' - extVersionCode = 6 + extVersionCode = 7 libVersion = '1.2' } -dependencies { - compileOnly 'com.google.code.gson:gson:2.8.5' - compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0' -} - apply from: "$rootDir/common.gradle" diff --git a/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt b/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt index a3d0053aa..cd169397c 100755 --- a/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt +++ b/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt @@ -1,12 +1,7 @@ package eu.kanade.tachiyomi.extension.es.kumanga import android.util.Base64 -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser +import com.github.salomonbrys.kotson.jsonObject import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.Filter @@ -17,18 +12,33 @@ 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.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale import kotlin.math.roundToInt class Kumanga : HttpSource() { + override val name = "Kumanga" + + override val baseUrl = "https://www.kumanga.com" + + override val lang = "es" + + override val supportsLatest = false + override val client: OkHttpClient = network.cloudflareClient .newBuilder() .followRedirects(true) @@ -45,13 +55,7 @@ class Kumanga : HttpSource() { } .build() - override val name = "Kumanga" - - override val baseUrl = "https://www.kumanga.com" - - override val lang = "es" - - override val supportsLatest = false + private val json: Json by injectLazy() override fun headersBuilder(): Headers.Builder = Headers.Builder() .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0") @@ -72,8 +76,10 @@ class Kumanga : HttpSource() { private fun getKumangaToken(): String { val body = client.newCall(GET("$baseUrl/mangalist?&page=1", headers)).execute().asJsoup() - var dt = body.select("#searchinput").attr("dt").toString() - var kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt)).replace("=", "k").toLowerCase() + val dt = body.select("#searchinput").attr("dt").toString() + val kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt)) + .replace("=", "k") + .toLowerCase(Locale.ROOT) kumangaToken = body.select("div.input-group [type=hidden]").attr(kumangaTokenKey) return kumangaToken } @@ -82,40 +88,28 @@ class Kumanga : HttpSource() { private fun getMangaUrl(mangaId: String, mangaSlug: String, page: Int) = "/manga/$mangaId/p/$page/$mangaSlug#cl" - private fun parseMangaFromJson(json: JsonElement) = SManga.create().apply { - title = json["name"].string - description = json["description"].string.replace("\\", "") - url = getMangaUrl(json["id"].string, json["slug"].string, 1) - thumbnail_url = getMangaCover(json["id"].string) - - val genresArray = json["categories"].array - genre = genresArray.joinToString { jsonObject -> - parseGenresFromJson(jsonObject) - } + private fun parseMangaFromJson(jsonObj: JsonObject) = SManga.create().apply { + title = jsonObj["name"]!!.jsonPrimitive.content + description = jsonObj["description"]!!.jsonPrimitive.content.replace("\\", "") + url = getMangaUrl(jsonObj["id"]!!.jsonPrimitive.content, jsonObj["slug"]!!.jsonPrimitive.content, 1) + thumbnail_url = getMangaCover(jsonObj["id"]!!.jsonPrimitive.content) + genre = jsonObj["categories"]!!.jsonArray + .joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } } - private fun parseJson(json: String): JsonElement { - return JsonParser().parse(json) - } - - private fun parseGenresFromJson(json: JsonElement) = json["name"].string - override fun popularMangaRequest(page: Int): Request { return POST("$baseUrl/backend/ajax/searchengine.php?page=$page&perPage=10&keywords=&retrieveCategories=true&retrieveAuthors=false&contentType=manga&token=$kumangaToken", headers) } override fun popularMangaParse(response: Response): MangasPage { - val res = response.body!!.string() - val json = parseJson(res) - val data = json["contents"].array - val retrievedCount = json["retrievedCount"].int - val hasNextPage = retrievedCount == 10 + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject - val mangas = data.map { jsonObject -> - parseMangaFromJson(jsonObject) - } + val mangaList = jsonResult["contents"]!!.jsonArray + .map { jsonEl -> parseMangaFromJson(jsonEl.jsonObject) } - return MangasPage(mangas, hasNextPage) + val hasNextPage = jsonResult["retrievedCount"]!!.jsonPrimitive.int == 10 + + return MangasPage(mangaList, hasNextPage) } override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") @@ -174,20 +168,22 @@ class Kumanga : HttpSource() { } else throw Exception("No fue posible obtener los capítulos") } - override fun pageListParse(response: Response): List = mutableListOf().apply { + override fun pageListParse(response: Response): List { val document = response.asJsoup() - var imagesJsonListStr = document.select("script:containsData(var pUrl=)").firstOrNull()?.data() + val imagesJsonRaw = document.select("script:containsData(var pUrl=)").firstOrNull() + ?.data() ?.substringAfter("var pUrl=") ?.substringBefore(";") + ?.let { decodeBase64(decodeBase64(it).reversed().dropLast(10).drop(10)) } ?: throw Exception("imagesJsonListStr null") - imagesJsonListStr = decodeBase64(decodeBase64(imagesJsonListStr).reversed().dropLast(10).drop(10)) - val imagesJsonList = parseJson(imagesJsonListStr).array - imagesJsonList.forEach { - val fakeImageUrl = it["imgURL"].string.replace("\\", "") - val imageUrl = baseUrl + fakeImageUrl + val jsonResult = json.parseToJsonElement(imagesJsonRaw).jsonArray - add(Page(size, "", imageUrl)) + return jsonResult.mapIndexed { i, jsonEl -> + val jsonObj = jsonEl.jsonObject + val imagePath = jsonObj["imgURL"]!!.jsonPrimitive.content.replace("\\", "") + + Page(i, "", baseUrl + imagePath) } } diff --git a/src/es/mangamx/build.gradle b/src/es/mangamx/build.gradle index d73ae5f31..10acd78dd 100644 --- a/src/es/mangamx/build.gradle +++ b/src/es/mangamx/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'MangaMx' pkgNameSuffix = 'es.mangamx' extClass = '.MangaMx' - extVersionCode = 10 + extVersionCode = 11 libVersion = '1.2' } diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt index 0f4ce86c7..e22e4540a 100644 --- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt +++ b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt @@ -4,10 +4,6 @@ import android.app.Application import android.content.SharedPreferences import android.net.Uri import android.util.Base64 -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.ConfigurableSource @@ -19,6 +15,11 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.FormBody import okhttp3.Request import okhttp3.Response @@ -26,6 +27,7 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.nio.charset.Charset import java.text.SimpleDateFormat import java.util.Locale @@ -44,6 +46,8 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { private var csrfToken = "" + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int) = GET( "$baseUrl/directorio?genero=false" + "&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", @@ -154,20 +158,22 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { return MangasPage(mangas, hasNextPage) } else { - val body = response.body!!.string() - if (body == "[]") throw Exception("Término de búsqueda demasiado corto") - val json = JsonParser().parse(body)["mangas"].asJsonArray + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray - val mangas = json.map { jsonElement -> searchMangaFromJson(jsonElement) } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) + if (jsonResult.isEmpty()) { + throw Exception("Término de búsqueda demasiado corto") + } + + val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } + + return MangasPage(mangaList, hasNextPage = false) } } - private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { - title = jsonElement["nombre"].string - setUrlWithoutDomain(jsonElement["url"].string) - thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover") + private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { + title = jsonObj["nombre"]!!.jsonPrimitive.content + thumbnail_url = jsonObj["img"]!!.jsonPrimitive.content.replace("/thumb", "/cover") + setUrlWithoutDomain(jsonObj["url"]!!.jsonPrimitive.content) } override fun mangaDetailsParse(document: Document): SManga { diff --git a/src/fr/japanread/build.gradle b/src/fr/japanread/build.gradle index 96780723e..8ad0cf902 100644 --- a/src/fr/japanread/build.gradle +++ b/src/fr/japanread/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Japanread' pkgNameSuffix = 'fr.japanread' extClass = '.Japanread' - extVersionCode = 7 + extVersionCode = 8 libVersion = '1.2' containsNsfw = true } diff --git a/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt b/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt index a5756df67..6d6a0a364 100644 --- a/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt +++ b/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt @@ -1,8 +1,6 @@ package eu.kanade.tachiyomi.extension.fr.japanread import android.net.Uri -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.Filter @@ -12,11 +10,16 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.injectLazy import java.util.Calendar class Japanread : ParsedHttpSource() { @@ -29,6 +32,8 @@ class Japanread : ParsedHttpSource() { override val supportsLatest = true + private val json: Json by injectLazy() + // Generic (used by popular/latest/search) private fun mangaListFromElement(element: Element): SManga { return SManga.create().apply { @@ -224,20 +229,19 @@ class Japanread : ParsedHttpSource() { override fun pageListParse(document: Document): List { val chapterId = document.select("meta[data-chapter-id]").attr("data-chapter-id") - val apiResponse = client.newCall(GET("$baseUrl/api/?id=$chapterId&type=chapter", apiHeaders())).execute() + val apiRequest = GET("$baseUrl/api/?id=$chapterId&type=chapter", apiHeaders()) + val apiResponse = client.newCall(apiRequest).execute() - val jsonData = apiResponse.body!!.string() - val json = JsonParser.parseString(jsonData).asJsonObject + val jsonResult = json.parseToJsonElement(apiResponse.body!!.string()).jsonObject - val baseImagesUrl = json["baseImagesUrl"].string + val baseImagesUrl = jsonResult["baseImagesUrl"]!!.jsonPrimitive.content - return json["page_array"].asJsonArray.mapIndexed { idx, it -> - val imgUrl = "$baseUrl$baseImagesUrl/${it.asString}" - Page(idx, baseUrl, imgUrl) + return jsonResult["page_array"]!!.jsonArray.mapIndexed { i, jsonEl -> + Page(i, baseUrl, "$baseUrl$baseImagesUrl/${jsonEl.jsonPrimitive.content}") } } - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") + override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { return GET(page.imageUrl!!, headers) diff --git a/src/fr/japscan/build.gradle b/src/fr/japscan/build.gradle index bc1b95171..9ea09c005 100644 --- a/src/fr/japscan/build.gradle +++ b/src/fr/japscan/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Japscan' pkgNameSuffix = 'fr.japscan' extClass = '.Japscan' - extVersionCode = 28 + extVersionCode = 29 libVersion = '1.2' } diff --git a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt b/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt index e3502c41b..3d27bd256 100644 --- a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt +++ b/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt @@ -18,13 +18,6 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.ConfigurableSource @@ -36,6 +29,11 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.FormBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -46,6 +44,7 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.text.ParseException @@ -66,6 +65,8 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { override val supportsLatest = true + private val json: Json by injectLazy() + private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } @@ -173,7 +174,9 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { } override fun popularMangaNextPageSelector(): String? = null + override fun popularMangaSelector() = "#top_mangas_week li > span" + override fun popularMangaFromElement(element: Element): SManga { val manga = SManga.create() element.select("a").first().let { @@ -201,10 +204,10 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { } override fun latestUpdatesNextPageSelector(): String? = null - override fun latestUpdatesSelector() = "#chapters > div > h3.text-truncate" - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - private val gson = Gson() + override fun latestUpdatesSelector() = "#chapters > div > h3.text-truncate" + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) // Search override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { @@ -227,20 +230,22 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { .build() try { - return client.newCall(POST("$baseUrl/live-search/", searchHeaders, formBody)).execute().use { response -> - if (!response.isSuccessful) throw Exception("Unexpected code $response") + val searchRequest = POST("$baseUrl/live-search/", searchHeaders, formBody) + val searchResponse = client.newCall(searchRequest).execute() - val jsonObject = gson.fromJson(response.body!!.string()) - - if (jsonObject.asJsonArray.size() === 0) { - Log.d("japscan", "Search not returning anything, using duckduckgo") - - throw Exception("No data") - } - - return response.request + if (!searchResponse.isSuccessful) { + throw Exception("Unexpected code ${searchResponse.code}") } - } finally { + + val jsonResult = json.parseToJsonElement(searchResponse.body!!.string()).jsonArray + + if (jsonResult.isEmpty()) { + Log.d("japscan", "Search not returning anything, using duckduckgo") + throw Exception("No data") + } + + return searchRequest + } catch (e: Exception) { // Fallback to duckduckgo if the search does not return any result val uri = Uri.parse("https://duckduckgo.com/lite/").buildUpon() .appendQueryParameter("q", "$query site:$baseUrl/manga/") @@ -250,42 +255,30 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { } } - override fun searchMangaNextPageSelector(): String? = "li.page-item:last-child:not(li.active),.next_form .navbutton" + override fun searchMangaNextPageSelector(): String = "li.page-item:last-child:not(li.active),.next_form .navbutton" + override fun searchMangaSelector(): String = "div.card div.p-2, a.result-link" + override fun searchMangaParse(response: Response): MangasPage { if ("live-search" in response.request.url.toString()) { - val body = response.body!!.string() - val json = JsonParser().parse(body).asJsonArray - val mangas = json.map { jsonElement -> - searchMangaFromJson(jsonElement) - } + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray - val hasNextPage = false + val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } - return MangasPage(mangas, hasNextPage) - } else { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) + return MangasPage(mangaList, hasNextPage = false) } + + return super.searchMangaParse(response) } override fun searchMangaFromElement(element: Element): SManga { - if (element.attr("class") == "result-link") { - return SManga.create().apply { + return if (element.attr("class") == "result-link") { + SManga.create().apply { title = element.text().substringAfter(" ").substringBefore(" | JapScan") setUrlWithoutDomain(element.attr("abs:href")) } } else { - return SManga.create().apply { + SManga.create().apply { thumbnail_url = element.select("img").attr("abs:src") element.select("p a").let { title = it.text() @@ -295,9 +288,9 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { } } - private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { - title = jsonElement["name"].string - url = jsonElement["url"].string + private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { + title = jsonObj["name"]!!.jsonPrimitive.content + url = jsonObj["url"]!!.jsonPrimitive.content } override fun mangaDetailsParse(document: Document): SManga { @@ -365,7 +358,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() { val checkNew = ArrayList(pagecount) var maxIter = document.getElementsByTag("option").size var isSinglePage = false - if ((zjs.toLowerCase().split("new image").size - 1) == 1) { + if ((zjs.toLowerCase(Locale.ROOT).split("new image").size - 1) == 1) { isSinglePage = true maxIter = 1 } diff --git a/src/fr/scanmanga/build.gradle b/src/fr/scanmanga/build.gradle index 90bf91c0d..bda7164d3 100644 --- a/src/fr/scanmanga/build.gradle +++ b/src/fr/scanmanga/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Scan-Manga' pkgNameSuffix = 'fr.scanmanga' extClass = '.ScanManga' - extVersionCode = 4 + extVersionCode = 5 libVersion = '1.2' containsNsfw = true } diff --git a/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt b/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt index e94c6711e..af61a8ed9 100644 --- a/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt +++ b/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt @@ -1,9 +1,5 @@ package eu.kanade.tachiyomi.extension.fr.scanmanga -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.FilterList @@ -13,6 +9,10 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request @@ -21,6 +21,7 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.parser.Parser import rx.Observable +import uy.kohesive.injekt.injectLazy import kotlin.random.Random class ScanManga : ParsedHttpSource() { @@ -44,7 +45,7 @@ class ScanManga : ParsedHttpSource() { chain.proceed(newReq) }.build()!! - private val gson = Gson() + private val json: Json by injectLazy() override fun headersBuilder(): Headers.Builder = super.headersBuilder() .add("Accept-Language", "fr-FR") @@ -93,7 +94,7 @@ class ScanManga : ParsedHttpSource() { override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - fun shuffle(s: String?): String? { + private fun shuffle(s: String?): String { val result = StringBuffer(s!!) var n = result.length while (n > 1) { @@ -106,10 +107,11 @@ class ScanManga : ParsedHttpSource() { } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchHeaders = headersBuilder().apply { - add("Referer", "$baseUrl/scanlation/liste_series.html") - add("x-requested-with", "XMLHttpRequest") - }.build() + val searchHeaders = headersBuilder() + .add("Referer", "$baseUrl/scanlation/liste_series.html") + .add("x-requested-with", "XMLHttpRequest") + .build() + return GET("$baseUrl/scanlation/scan.data.json", searchHeaders) } @@ -126,58 +128,58 @@ class ScanManga : ParsedHttpSource() { } private fun parseMangaFromJson(response: Response): MangasPage { - val jsonData = response.body!!.string()!! - if (jsonData == "") { - return MangasPage(listOf(), false) + val jsonRaw = response.body!!.string() + + if (jsonRaw.isEmpty()) { + return MangasPage(emptyList(), hasNextPage = false) } - val jsonObject = JsonParser().parse(jsonData).asJsonObject + val jsonObj = json.parseToJsonElement(jsonRaw).jsonObject - val mangas = jsonObject.keySet() - .map { key -> - // "95","%24100-is-Too-Cheap","0","3","One Shot","","2 avril 2010","","335","178","4010","" - SManga.create().apply { - url = "/" + jsonObject[key][0].string + "/" + jsonObject[key][1].string + ".html" - title = Parser.unescapeEntities(key, false) - genre = jsonObject[key][2].string.let { - when { - it.contains("0") -> "Shōnen" - it.contains("1") -> "Shōjo" - it.contains("2") -> "Seinen" - it.contains("3") -> "Josei" - else -> null - } - } - status = jsonObject[key][3].string.let { - when { - it.contains("0") -> SManga.ONGOING // En cours - it.contains("1") -> SManga.ONGOING // En pause - it.contains("2") -> SManga.COMPLETED // Terminé - it.contains("3") -> SManga.COMPLETED // One shot - else -> SManga.UNKNOWN - } + val mangaList = jsonObj.entries.map { entry -> + SManga.create().apply { + title = Parser.unescapeEntities(entry.key, false) + genre = entry.value.jsonArray[2].jsonPrimitive.content.let { + when { + it.contains("0") -> "Shōnen" + it.contains("1") -> "Shōjo" + it.contains("2") -> "Seinen" + it.contains("3") -> "Josei" + else -> null } } + status = entry.value.jsonArray[3].jsonPrimitive.content.let { + when { + it.contains("0") -> SManga.ONGOING // En cours + it.contains("1") -> SManga.ONGOING // En pause + it.contains("2") -> SManga.COMPLETED // Terminé + it.contains("3") -> SManga.COMPLETED // One shot + else -> SManga.UNKNOWN + } + } + url = "/" + entry.value.jsonArray[0].jsonPrimitive.content + "/" + + entry.value.jsonArray[1].jsonPrimitive.content + ".html" } + } - return MangasPage(mangas, false) + return MangasPage(mangaList, hasNextPage = false) } override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") // Details - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("h2[itemprop=\"name\"]").text() - author = document.select("li[itemprop=\"author\"] a").joinToString { it.text() } - description = document.select("p[itemprop=\"description\"]").text() - thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src") - } + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + title = document.select("h2[itemprop=\"name\"]").text() + author = document.select("li[itemprop=\"author\"] a").joinToString { it.text() } + description = document.select("p[itemprop=\"description\"]").text() + thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src") } // Chapters override fun chapterListSelector() = throw Exception("Not used") + override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") + override fun chapterListParse(response: Response): List { val document = response.asJsoup() @@ -207,9 +209,10 @@ class ScanManga : ParsedHttpSource() { override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") override fun imageRequest(page: Page): Request { - val imgHeaders = headersBuilder().apply { - add("Referer", page.url) - }.build() + val imgHeaders = headersBuilder() + .add("Referer", page.url) + .build() + return GET(page.imageUrl!!, imgHeaders) } } diff --git a/src/it/digitalteam/build.gradle b/src/it/digitalteam/build.gradle index e4c949305..3f159e520 100644 --- a/src/it/digitalteam/build.gradle +++ b/src/it/digitalteam/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'DigitalTeam' pkgNameSuffix = 'it.digitalteam' extClass = '.DigitalTeam' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' containsNsfw = false } diff --git a/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt b/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt index b109f6920..53d8cd722 100644 --- a/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt +++ b/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt @@ -1,7 +1,5 @@ package eu.kanade.tachiyomi.extension.it.digitalteam -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.FilterList @@ -10,12 +8,18 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.FormBody import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale @@ -23,117 +27,152 @@ import java.util.Locale class DigitalTeam : ParsedHttpSource() { override val name = "DigitalTeam" + override val baseUrl = "https://dgtread.com" + override val lang = "it" + override val supportsLatest = false + override val client: OkHttpClient = network.cloudflareClient + private val json: Json by injectLazy() + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/reader/series", headers) } - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("La ricerca è momentaneamente disabilitata.") - // LIST SELECTOR + override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + throw Exception("La ricerca è momentaneamente disabilitata.") + + // LIST SELECTOR override fun popularMangaSelector() = "ul li.manga_block" + override fun latestUpdatesSelector() = popularMangaSelector() + override fun searchMangaSelector() = popularMangaSelector() - // ELEMENT - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").attr("src") - manga.setUrlWithoutDomain(element.select(".manga_title a").first().attr("href")) - manga.title = element.select(".manga_title a").text() - return manga + // ELEMENT + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.select(".manga_title a").text() + thumbnail_url = element.select("img").attr("src") + setUrlWithoutDomain(element.select(".manga_title a").first().attr("href")) } override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used") + override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not Used") - // NEXT SELECTOR + // NEXT SELECTOR // Not needed override fun popularMangaNextPageSelector(): String? = null - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - // //////////////// - override fun mangaDetailsParse(document: Document): SManga { + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply{ val infoElement = document.select("#manga_left") - val manga = SManga.create() - manga.author = infoElement.select(".info_name:contains(Autore)").next()?.text() - manga.artist = infoElement.select(".info_name:contains(Artista)").next()?.text() - manga.genre = infoElement.select(".info_name:contains(Genere)").next()?.text() - manga.status = parseStatus(infoElement.select(".info_name:contains(Status)").next().text()) - manga.description = document.select("div.plot")?.text() - manga.thumbnail_url = infoElement.select(".cover img").attr("src") - return manga + + author = infoElement.select(".info_name:contains(Autore)").next()?.text() + artist = infoElement.select(".info_name:contains(Artista)").next()?.text() + genre = infoElement.select(".info_name:contains(Genere)").next()?.text() + status = parseStatus(infoElement.select(".info_name:contains(Status)").next().text()) + description = document.select("div.plot")?.text() + thumbnail_url = infoElement.select(".cover img").attr("src") } private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("in corso") -> SManga.ONGOING - element.toLowerCase().contains("completo") -> SManga.COMPLETED + element.toLowerCase(Locale.ROOT).contains("in corso") -> SManga.ONGOING + element.toLowerCase(Locale.ROOT).contains("completo") -> SManga.COMPLETED else -> SManga.UNKNOWN } override fun chapterListSelector() = ".chapter_list ul li" - override fun chapterFromElement(element: Element): SChapter { + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select(".ch_bottom").first()?.text()?.replace("Pubblicato il ", "")?.let { - try { - SimpleDateFormat("dd-MM-yyyy", Locale.ITALY).parse(it).time - } catch (e: ParseException) { - SimpleDateFormat("H", Locale.ITALY).parse(it).time - } - } ?: 0 - return chapter + + setUrlWithoutDomain(urlElement.attr("href")) + name = urlElement.text() + date_upload = element.select(".ch_bottom").first()?.text() + ?.replace("Pubblicato il ", "") + ?.let { + try { + DATE_FORMAT_FULL.parse(it)?.time + } catch (e: ParseException) { + DATE_FORMAT_SIMPLE.parse(it)?.time + } + } ?: 0 } - protected fun getXhrPages(script_content: String, title: String): String { - val xhrHeaders = headersBuilder().add("Content-Type: application/x-www-form-urlencoded; charset=UTF-8") + private fun getXhrPages(script_content: String, title: String): String { + val infoManga = script_content.substringAfter("m='").substringBefore("'") + val infoChapter = script_content.substringAfter("ch='").substringBefore("'") + val infoChSub = script_content.substringAfter("chs='").substringBefore("'") + + val formBody = FormBody.Builder() + .add("info[manga]", infoManga) + .add("info[chapter]", infoChapter) + .add("info[ch_sub]", infoChSub) + .add("info[title]", title) .build() - // This can be improved, i don't know how to do it with Regex - var infomanga = script_content.substringAfter("m='").substringBefore("'") - var infochapter = script_content.substringAfter("ch='").substringBefore("'") - var infoch_sub = script_content.substringAfter("chs='").substringBefore("'") - val body = RequestBody.create(null, "info[manga]=$infomanga&info[chapter]=$infochapter&info[ch_sub]=$infoch_sub&info[title]=$title") - return client.newCall(POST("$baseUrl/reader/c_i", xhrHeaders, body)) - .execute() - .asJsoup().select("body").text() - .replace("\\", "").removeSurrounding("\"") + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .build() + + val request = POST("$baseUrl/reader/c_i", xhrHeaders, formBody) + val response = client.newCall(request).execute() + + return response.asJsoup() + .select("body") + .text() + .replace("\\", "") + .removeSurrounding("\"") } override fun pageListParse(document: Document): List { - val pages = mutableListOf() - val script_content = document.body()!!.toString().substringAfter("current_page=").substringBefore(";") + val scriptContent = document.body().toString() + .substringAfter("current_page=") + .substringBefore(";") val title = document.select("title").first().text() - val imagesJsonList = JsonParser().parse(getXhrPages(script_content, title)).asJsonArray - val image_url = imagesJsonList.get(2).string - val images_name = imagesJsonList.get(1).asJsonArray - val images_data = imagesJsonList.get(0).asJsonArray - images_name.forEachIndexed { index, imagename -> - val imageUrl = - "$baseUrl/reader$image_url" + - "${images_data.get(index).asJsonObject.get("name").string}" + - "${imagename.string}" + - "${images_data.get(index).asJsonObject.get("ex").string}" - pages.add(Page(index, "", imageUrl)) + + val xhrPages = getXhrPages(scriptContent, title) + val jsonResult = json.parseToJsonElement(xhrPages).jsonArray + + val imageData = jsonResult[0].jsonArray + val imagePath = jsonResult[2].jsonPrimitive.content + + return jsonResult[1].jsonArray.mapIndexed { i, jsonEl -> + val imageUrl = "$baseUrl/reader$imagePath" + + imageData[i].jsonObject["name"]!!.jsonPrimitive.content + + jsonEl.jsonPrimitive.content + + imageData[i].jsonObject["ex"]!!.jsonPrimitive.content + + Page(i, "", imageUrl) } - return pages } override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() + val imgHeader = Headers.Builder() + .add("User-Agent", USER_AGENT) + .add("Referer", baseUrl) + .build() + return GET(page.imageUrl!!, imgHeader) } + + companion object { + private const val USER_AGENT = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) " + + "AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30" + + private val DATE_FORMAT_FULL = SimpleDateFormat("dd-MM-yyyy", Locale.ITALY) + private val DATE_FORMAT_SIMPLE = SimpleDateFormat("H", Locale.ITALY) + } } diff --git a/src/ru/mangahub/build.gradle b/src/ru/mangahub/build.gradle index 39b03b97b..180a21e42 100644 --- a/src/ru/mangahub/build.gradle +++ b/src/ru/mangahub/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Mangahub' pkgNameSuffix = 'ru.mangahub' extClass = '.Mangahub' - extVersionCode = 9 + extVersionCode = 10 libVersion = '1.2' } diff --git a/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt b/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt index a0a14ed35..ee07e20a2 100644 --- a/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt +++ b/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.extension.ru.mangahub -import com.google.gson.JsonParser import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList @@ -8,11 +7,16 @@ 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.ParsedHttpSource +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -26,12 +30,11 @@ open class Mangahub : ParsedHttpSource() { override val supportsLatest = true - private val rateLimitInterceptor = RateLimitInterceptor(2) - - private val jsonParser = JsonParser() - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() + .addNetworkInterceptor(RateLimitInterceptor(2)) + .build() + + private val json: Json by injectLazy() override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/explore?filter[sort]=rating&filter[dateStart][left_number]=1900&filter[dateStart][right_number]=2099&page=$page", headers) @@ -114,25 +117,25 @@ open class Mangahub : ParsedHttpSource() { } override fun pageListParse(document: Document): List { - val chapInfo = document.select("reader").attr("data-store").replace(""", "\"").replace("\\/", "/") - val chapter = jsonParser.parse(chapInfo).asJsonObject - val scans = chapter["scans"].asJsonArray + val chapInfo = document.select("reader") + .attr("data-store") + .replace(""", "\"") + .replace("\\/", "/") + val chapter = json.parseToJsonElement(chapInfo).jsonObject - val pages = mutableListOf() - scans.mapIndexed { i, page -> - pages.add(Page(i, "", "https:${page.asJsonObject.get("src").asString}")) + return chapter["scans"]!!.jsonArray.mapIndexed { i, jsonEl -> + Page(i, "", "https:" + jsonEl.jsonObject["src"]!!.jsonPrimitive.content) } - - return pages } override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() + val imgHeader = Headers.Builder() + .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + .add("Referer", baseUrl) + .build() + return GET(page.imageUrl!!, imgHeader) } } diff --git a/src/zh/bh3/build.gradle b/src/zh/bh3/build.gradle index c66e7b954..70ce1bb22 100644 --- a/src/zh/bh3/build.gradle +++ b/src/zh/bh3/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'BH3' pkgNameSuffix = 'zh.bh3' extClass = '.BH3' - extVersionCode = 2 + extVersionCode = 3 libVersion = '1.2' } diff --git a/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt b/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt index 851cebf8b..ae7391ac9 100644 --- a/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt +++ b/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt @@ -1,22 +1,23 @@ package eu.kanade.tachiyomi.extension.zh.bh3 -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList 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.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.float +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -24,35 +25,48 @@ import java.util.concurrent.TimeUnit class BH3 : ParsedHttpSource() { override val name = "《崩坏3》IP站" + override val baseUrl = "https://comic.bh3.com" + override val lang = "zh" + override val supportsLatest = false + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) .retryOnConnectionFailure(true) .followRedirects(true) - .build()!! + .build() + + private val json: Json by injectLazy() override fun popularMangaSelector() = "a[href*=book]" - override fun latestUpdatesSelector() = throw Exception("Not Used") - override fun searchMangaSelector() = throw Exception("Not Used") - override fun chapterListSelector() = throw Exception("Not Used") - override fun popularMangaNextPageSelector() = "none" - override fun latestUpdatesNextPageSelector() = "none" - override fun searchMangaNextPageSelector() = "none" + override fun latestUpdatesSelector() = "" + + override fun searchMangaSelector() = "" + + override fun chapterListSelector() = "" + + override fun popularMangaNextPageSelector(): String? = null + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun searchMangaNextPageSelector(): String? = null override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers) + override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") - // override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - // override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers) override fun popularMangaFromElement(element: Element) = mangaFromElement(element) + override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) + override fun searchMangaFromElement(element: Element) = mangaFromElement(element) private fun mangaFromElement(element: Element): SManga { @@ -66,41 +80,33 @@ class BH3 : ParsedHttpSource() { override fun chapterFromElement(element: Element) = throw Exception("Not Used") override fun chapterListParse(response: Response): List { - val jsondata = response.body!!.string() - val json = JsonParser().parse(jsondata).asJsonArray - val chapters = mutableListOf() - json.forEach { - chapters.add(createChapter(it)) - } - return chapters + val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray + + return jsonResult.map { jsonEl -> createChapter(jsonEl.jsonObject) } } - private fun createChapter(json: JsonElement) = SChapter.create().apply { - name = json["title"].string - url = "/book/${json["bookid"].int}/${json["chapterid"].int}" - date_upload = parseDate(json["timestamp"].string) - chapter_number = json["chapterid"].float + private fun createChapter(jsonObj: JsonObject) = SChapter.create().apply { + name = jsonObj["title"]!!.jsonPrimitive.content + url = "/book/${jsonObj["bookid"]!!.jsonPrimitive.int}/${jsonObj["chapterid"]!!.jsonPrimitive.int}" + date_upload = parseDate(jsonObj["timestamp"]!!.jsonPrimitive.content) + chapter_number = jsonObj["chapterid"]!!.jsonPrimitive.float } private fun parseDate(date: String): Long { return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0L } - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = document.select("img.cover").attr("abs:src") - manga.description = document.select("div.detail_info1").text().trim() - manga.title = document.select("div.title").text().trim() - return manga + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + thumbnail_url = document.select("img.cover").attr("abs:src") + description = document.select("div.detail_info1").text().trim() + title = document.select("div.title").text().trim() } - override fun pageListParse(response: Response): List = mutableListOf().apply { - val body = response.asJsoup() - body.select("img.lazy.comic_img")?.forEach { - add(Page(size, "", it.attr("data-original"))) + override fun pageListParse(document: Document): List { + return document.select("img.lazy.comic_img").mapIndexed { i, el -> + Page(i, "", el.attr("data-original")) } } - override fun pageListParse(document: Document) = throw Exception("Not Used") - override fun imageUrlParse(document: Document) = throw Exception("Not Used") + override fun imageUrlParse(document: Document) = "" } diff --git a/src/zh/qimiaomh/build.gradle b/src/zh/qimiaomh/build.gradle index 5e9983ee8..a79b8f609 100644 --- a/src/zh/qimiaomh/build.gradle +++ b/src/zh/qimiaomh/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'Qimiaomh' pkgNameSuffix = 'zh.qimiaomh' extClass = '.Qimiaomh' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt b/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt index c1f4b09c5..09409b82d 100644 --- a/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt +++ b/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt @@ -1,40 +1,53 @@ package eu.kanade.tachiyomi.extension.zh.qimiaomh -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList 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.ParsedHttpSource +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale import java.util.Random import java.util.concurrent.TimeUnit class Qimiaomh : ParsedHttpSource() { + override val name: String = "奇妙漫画" + override val lang: String = "zh" + override val baseUrl: String = "https://www.qimiaomh.com" + override val supportsLatest: Boolean = true + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) .retryOnConnectionFailure(true) .followRedirects(true) - .build()!! + .build() + + private val json: Json by injectLazy() // Popular override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/list-1------hits--$page.html", headers) } + override fun popularMangaNextPageSelector(): String? = "a:contains(下一页)" + override fun popularMangaSelector(): String = "div.classification" + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { url = element.select("a").first().attr("href") thumbnail_url = element.select("img.lazyload").attr("abs:data-src") @@ -45,8 +58,11 @@ class Qimiaomh : ParsedHttpSource() { override fun latestUpdatesRequest(page: Int): Request { return GET("$baseUrl/list-1------updatetime--$page.html", headers) } + override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() + override fun latestUpdatesSelector(): String = popularMangaSelector() + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) // Search @@ -54,8 +70,11 @@ class Qimiaomh : ParsedHttpSource() { throw Exception("不管用 (T_T)") // Todo Filters } + override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() + override fun searchMangaSelector(): String = popularMangaSelector() + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) // Details @@ -78,31 +97,31 @@ class Qimiaomh : ParsedHttpSource() { // Chapters override fun chapterListSelector(): String = "div.comic-content-list ul.comic-content-c" + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { url = element.select("a").first().attr("href") name = element.select("li.tit").text().trim() } + private fun parseDate(date: String): Long { return SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L } // Pages - override fun pageListParse(document: Document): List = mutableListOf().apply { + override fun pageListParse(document: Document): List { val script = document.select("script:containsData(var did =)").html() val did = script.substringAfter("var did = ").substringBefore(";") val sid = script.substringAfter("var sid = ").substringBefore(";") + val url = "$baseUrl/Action/Play/AjaxLoadImgUrl?did=$did&sid=$sid&tmp=${Random().nextFloat()}" - val body = client.newCall(GET(url, headers)).execute().body!!.string() - val json = JsonParser().parse(body).asJsonObject - val images = json["listImg"].asJsonArray - images.forEachIndexed { index, jsonElement -> - add(Page(index, "", jsonElement.string)) + val response = client.newCall(GET(url, headers)).execute() + val result = json.parseToJsonElement(response.body!!.string()).jsonObject + + return result["listImg"]!!.jsonArray.mapIndexed { i, jsonEl -> + Page(i, "", jsonEl.jsonPrimitive.content) } } - override fun imageUrlParse(document: Document): String { - throw Exception("Not Used") - } - // Not Used + override fun imageUrlParse(document: Document): String = "" }