Replace JsonParser with kotlinx.serialization in some extensions (#7620)

* Replace JsonParser with kotlinx.serialization.

* Remove wildcard import.

* Remove more usages of JsonParser.
This commit is contained in:
Alessandro Jean 2021-06-14 07:20:05 -03:00 committed by GitHub
parent 71986a1c6e
commit 3f91c5f75e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 638 additions and 519 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Hitomi.la' extName = 'Hitomi.la'
pkgNameSuffix = 'all.hitomi' pkgNameSuffix = 'all.hitomi'
extClass = '.HitomiFactory' extClass = '.HitomiFactory'
extVersionCode = 5 extVersionCode = 6
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -2,10 +2,6 @@ package eu.kanade.tachiyomi.extension.all.hitomi
import android.app.Application import android.app.Application
import android.content.SharedPreferences 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.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource 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.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -24,6 +24,7 @@ import rx.Single
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Locale import java.util.Locale
import androidx.preference.CheckBoxPreference as AndroidXCheckBoxPreference import androidx.preference.CheckBoxPreference as AndroidXCheckBoxPreference
import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen 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 override val baseUrl = BASE_URL
private val json: Json by injectLazy()
// Popular // Popular
override fun fetchPopularManga(page: Int): Observable<MangasPage> { override fun fetchPopularManga(page: Int): Observable<MangasPage> {
@ -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") return GET("$LTN_BASE_URL/galleries/${hlIdFromUrl(chapter.url)}.js")
} }
private val jsonParser = JsonParser()
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val str = response.body!!.string() val jsonRaw = response.body!!.string().removePrefix("var galleryinfo = ")
val json = jsonParser.parse(str.removePrefix("var galleryinfo = ")) val jsonResult = json.parseToJsonElement(jsonRaw).jsonObject
return json["files"].array.mapIndexed { i, jsonElement ->
val hash = jsonElement["hash"].string return jsonResult["files"]!!.jsonArray.mapIndexed { i, jsonEl ->
val ext = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) jsonElement["name"].string.split('.').last() else "webp" val jsonObj = jsonEl.jsonObject
val path = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) "images" else "webp" 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 hashPath1 = hash.takeLast(1)
val hashPath2 = hash.takeLast(3).take(2) val hashPath2 = hash.takeLast(3).take(2)
// https://ltn.hitomi.la/reader.js // https://ltn.hitomi.la/reader.js
// function make_image_element() // 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") Page(i, "", "https://${firstSubdomainFromGalleryId(hashPath2)}$secondSubdomain.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext")
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Home Hero Scans' extName = 'Home Hero Scans'
pkgNameSuffix = "en.homeheroscans" pkgNameSuffix = "en.homeheroscans"
extClass = '.HomeHeroScans' extClass = '.HomeHeroScans'
extVersionCode = 3 extVersionCode = 4
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,29 +1,36 @@
package eu.kanade.tachiyomi.extension.en.homeheroscans 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.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource 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.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale
open class HomeHeroScans : HttpSource() { open class HomeHeroScans : HttpSource() {
override val lang = "en"
final override val name = "Home Hero Scans" final override val name = "Home Hero Scans"
override val lang = "en"
final override val baseUrl = "https://hhs.vercel.app" final override val baseUrl = "https://hhs.vercel.app"
final override val supportsLatest = false final override val supportsLatest = false
private val json: Json by injectLazy()
// { seriesId |---> chapter |---> numPages } // { seriesId |---> chapter |---> numPages }
private val chapterNumberCache: MutableMap<String, MutableMap<String, Int>> = mutableMapOf() private val chapterNumberCache: MutableMap<String, MutableMap<String, Int>> = mutableMapOf()
@ -42,38 +49,39 @@ open class HomeHeroScans : HttpSource() {
} }
val memoizedFetchPopularManga = memoizeObservable { page: Int -> super.fetchPopularManga(page) } 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<SManga>()
res.forEach { s, jsonElement ->
val data = jsonElement.asJsonObject
fun get(k: String) = data[k]?.asString
manga.add( // Reduce number of times we call their api, user can force a call to api by relaunching the app
SManga.create().apply { override fun fetchPopularManga(page: Int) = memoizedFetchPopularManga(page)
artist = get("artist")
author = get("author") override fun popularMangaRequest(page: Int) = GET("$baseUrl/series.json", headers)
description = get("description")
genre = get("genre") override fun popularMangaParse(response: Response): MangasPage {
title = get("title")!! val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
thumbnail_url = "$baseUrl${get("cover")}"
url = "/series?series=$s" val mangaList = jsonResult.entries.map { entry ->
status = SManga.ONGOING // isn't reported 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 latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used")
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used") override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used")
// search // Search
private fun getMangaId(s: String): String? { private fun getMangaId(s: String): String? {
return s.toHttpUrlOrNull()?.let { url -> return s.toHttpUrlOrNull()?.let { url ->
// allow for trailing slash // allow for trailing slash
@ -89,34 +97,43 @@ open class HomeHeroScans : HttpSource() {
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (!query.startsWith(URL_SEARCH_PREFIX)) 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 fetchPopularManga(page)
return getMangaId(query.substringAfter(URL_SEARCH_PREFIX))?.let { id -> return getMangaId(query.substringAfter(URL_SEARCH_PREFIX))?.let { id ->
fetchBySeriesId(id).map { MangasPage(it, false) } fetchBySeriesId(id).map { MangasPage(it, false) }
} ?: Observable.just(MangasPage(emptyList(), 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") override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not used")
// chapter list (is paginated), // Chapter list (is paginated)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
return JsonParser.parseString(response.body?.string()!!).asJsonObject["data"].asJsonArray.map { val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
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")}"
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 { override fun chapterListRequest(manga: SManga): Request {
@ -134,7 +151,8 @@ open class HomeHeroScans : HttpSource() {
} ?: Observable.just(manga) } ?: Observable.just(manga)
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used") 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") // override fun mangaDetailsRequest(manga: SManga) = throw UnsupportedOperationException("Not used")
// Pages // Pages
@ -146,9 +164,9 @@ open class HomeHeroScans : HttpSource() {
return if (chapterPages() != null) { return if (chapterPages() != null) {
Observable.just(chapterPages()!!) Observable.just(chapterPages()!!)
} else { } else {
// has side effect of setting numPages in cache // Has side effect of setting numPages in cache
fetchChapterList( 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 } SManga.create().apply { this.url = chapter.url }
).map { ).map {
chapterPages() chapterPages()
@ -161,10 +179,14 @@ open class HomeHeroScans : HttpSource() {
} }
override fun pageListParse(response: Response) = throw UnsupportedOperationException("Not used") override fun pageListParse(response: Response) = throw UnsupportedOperationException("Not used")
override fun pageListRequest(chapter: SChapter) = 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 { companion object {
private val DATE_FORMATTER = SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH)
const val URL_SEARCH_PREFIX = "url:" const val URL_SEARCH_PREFIX = "url:"
} }
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'HonkaiImpact3' extName = 'HonkaiImpact3'
pkgNameSuffix = 'en.honkaiimpact' pkgNameSuffix = 'en.honkaiimpact'
extClass = '.Honkaiimpact' extClass = '.Honkaiimpact'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,34 +1,39 @@
package eu.kanade.tachiyomi.extension.en.honkaiimpact 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.network.GET
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.OkHttpClient
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
// Info - Based of BH3
// This is the english version of the site
class Honkaiimpact : ParsedHttpSource() { class Honkaiimpact : ParsedHttpSource() {
// Info - Based of BH3
// This is the english version of the site
override val name = "Honkai Impact 3rd" override val name = "Honkai Impact 3rd"
override val baseUrl = "https://manga.honkaiimpact3.com" override val baseUrl = "https://manga.honkaiimpact3.com"
override val lang = "en" override val lang = "en"
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES) .connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES)
@ -36,25 +41,33 @@ class Honkaiimpact : ParsedHttpSource() {
.followRedirects(true) .followRedirects(true)
.build() .build()
private val json: Json by injectLazy()
// Popular // Popular
override fun popularMangaSelector() = "a[href*=book]" override fun popularMangaSelector() = "a[href*=book]"
override fun popularMangaNextPageSelector(): String? = null override fun popularMangaNextPageSelector(): String? = null
override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers) override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers)
override fun popularMangaFromElement(element: Element) = mangaFromElement(element) override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
// Latest // Latest
override fun latestUpdatesSelector() = throw Exception("Not Used") override fun latestUpdatesSelector() = throw Exception("Not Used")
override fun latestUpdatesNextPageSelector(): String? = null override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used")
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
// Search // Search
override fun searchMangaSelector() = throw Exception("Not Used") override fun searchMangaSelector() = throw Exception("Not Used")
override fun searchMangaNextPageSelector(): String? = null override fun searchMangaNextPageSelector(): String? = null
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search")
override fun searchMangaFromElement(element: Element) = mangaFromElement(element) override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
private fun mangaFromElement(element: Element): SManga { private fun mangaFromElement(element: Element): SManga {
@ -78,22 +91,20 @@ class Honkaiimpact : ParsedHttpSource() {
override fun chapterListSelector() = throw Exception("Not Used") override fun chapterListSelector() = throw Exception("Not Used")
override fun chapterFromElement(element: Element) = 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 chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val jsondata = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray
val json = JsonParser().parse(jsondata).asJsonArray
val chapters = mutableListOf<SChapter>() return jsonResult.map { jsonEl -> createChapter(jsonEl.jsonObject) }
json.forEach {
chapters.add(createChapter(it))
}
return chapters
} }
private fun createChapter(json: JsonElement) = SChapter.create().apply { private fun createChapter(jsonObj: JsonObject) = SChapter.create().apply {
name = json["title"].string name = jsonObj["title"]!!.jsonPrimitive.content
url = "/book/${json["bookid"].int}/${json["chapterid"].int}" url = "/book/${jsonObj["bookid"]!!.jsonPrimitive.int}/${jsonObj["chapterid"]!!.jsonPrimitive.int}"
date_upload = parseDate(json["timestamp"].string) date_upload = parseDate(jsonObj["timestamp"]!!.jsonPrimitive.content)
chapter_number = json["chapterid"].float chapter_number = jsonObj["chapterid"]!!.jsonPrimitive.float
} }
private fun parseDate(date: String): Long { private fun parseDate(date: String): Long {
@ -101,13 +112,12 @@ class Honkaiimpact : ParsedHttpSource() {
} }
// Manga Pages // Manga Pages
override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply {
val body = response.asJsoup() override fun pageListParse(document: Document): List<Page> {
body.select("img.lazy.comic_img")?.forEach { return document.select("img.lazy.comic_img").mapIndexed { i, el ->
add(Page(size, "", it.attr("data-original"))) Page(i, "", el.attr("data-original"))
} }
} }
override fun pageListParse(document: Document) = throw Exception("Not Used") override fun imageUrlParse(document: Document) = ""
override fun imageUrlParse(document: Document) = throw Exception("Not Used")
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'MangaDog' extName = 'MangaDog'
pkgNameSuffix = 'en.mangadog' pkgNameSuffix = 'en.mangadog'
extClass = '.Mangadog' extClass = '.Mangadog'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,10 +1,6 @@
package eu.kanade.tachiyomi.extension.en.mangadog package eu.kanade.tachiyomi.extension.en.mangadog
import android.net.Uri 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.network.GET
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage 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.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class Mangadog : HttpSource() { class Mangadog : HttpSource() {
override val name = "MangaDog" override val name = "MangaDog"
override val baseUrl = "https://mangadog.club" override val baseUrl = "https://mangadog.club"
private val cdn = "https://cdn.mangadog.club" private val cdn = "https://cdn.mangadog.club"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient 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 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 latestUpdatesRequest(page: Int) = GET("$baseUrl/index/latestupdate/getUpdateResult?page=$page", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse("$baseUrl/index/keywordsearch/index").buildUpon() val uri = Uri.parse("$baseUrl/index/keywordsearch/index").buildUpon()
.appendQueryParameter("query", query) .appendQueryParameter("query", query)
@ -37,61 +49,49 @@ class Mangadog : HttpSource() {
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
// val page = response.request.url.queryParameterValues("page").toString().toInt() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
val jsonData = response.body!!.string()
val results = JsonParser().parse(jsonData) val mangaList = jsonResult["data"]!!.jsonObject["data"]!!.jsonArray.map { jsonEl ->
val data = results["data"]["data"] popularMangaFromJson(jsonEl.jsonObject)
val mangas = mutableListOf<SManga>()
for (i in 0 until data.asJsonArray.size()) {
mangas.add(popularMangaFromjson(data[i]))
} }
val hasNextPage = true // page < results["data"]["pageNum"].int return MangasPage(mangaList, hasNextPage = true)
return MangasPage(mangas, hasNextPage)
} }
private fun popularMangaFromjson(json: JsonElement): SManga { private fun popularMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply {
val manga = SManga.create() title = jsonObj["name"]!!.jsonPrimitive.content.trim()
manga.title = json["name"].string.trim() thumbnail_url = cdn + jsonObj["image"]!!.jsonPrimitive.content.replace("\\/", "/")
manga.thumbnail_url = cdn + json["image"].string.replace("\\/", "/")
val searchname = json["search_name"].string val searchName = jsonObj["search_name"]!!.jsonPrimitive.content
val id = json["id"].string val id = jsonObj["id"]!!.jsonPrimitive.content
manga.url = "/detail/$searchname/$id.html" url = "/detail/$searchName/$id.html"
return manga
} }
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val jsonData = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
val results = JsonParser().parse(jsonData)
val data = results["data"] val mangaList = jsonResult["data"]!!.jsonArray.map { jsonEl ->
val mangas = mutableListOf<SManga>() popularMangaFromJson(jsonEl.jsonObject)
for (i in 0 until data.asJsonArray.size()) {
mangas.add(popularMangaFromjson(data[i]))
} }
val hasNextPage = true // data.asJsonArray.size()>18 return MangasPage(mangaList, hasNextPage = true)
return MangasPage(mangas, hasNextPage)
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val jsonData = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
val results = JsonParser().parse(jsonData)
val data = results["suggestions"] val mangaList = jsonResult["suggestions"]!!.jsonArray.map { jsonEl ->
val mangas = mutableListOf<SManga>() searchMangaFromJson(jsonEl.jsonObject)
for (i in 0 until 1) {
mangas.add(searchMangaFromjson(data[i]))
} }
val hasNextPage = false return MangasPage(mangaList, hasNextPage = false)
return MangasPage(mangas, hasNextPage)
} }
private fun searchMangaFromjson(json: JsonElement): SManga { private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply{
val manga = SManga.create() title = jsonObj["value"]!!.jsonPrimitive.content.trim()
manga.title = json["value"].string.trim()
val data = json["data"].string.replace("\\/", "/") val dataValue = jsonObj["data"]!!.jsonPrimitive.content.replace("\\/", "/")
manga.url = "/detail/$data.html" url = "/detail/$dataValue.html"
return manga
} }
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
@ -100,57 +100,51 @@ class Mangadog : HttpSource() {
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val jsonData = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
val results = JsonParser().parse(jsonData)
val data = results["data"]["data"] return jsonResult["data"]!!.jsonObject["data"]!!.jsonArray.map { jsonEl ->
val chapters = mutableListOf<SChapter>() chapterFromJson(jsonEl.jsonObject)
for (i in 0 until data.asJsonArray.size()) {
chapters.add(chapterFromjson(data[i]))
} }
return chapters
} }
private fun chapterFromjson(json: JsonElement): SChapter { private fun chapterFromJson(jsonObj: JsonObject): SChapter = SChapter.create().apply {
val chapter = SChapter.create() // The url should include the manga name but it doesn't seem to matter
val searchname = json["search_name"].string val searchname = jsonObj["search_name"]!!.jsonPrimitive.content
val id = json["comic_id"].string val id = jsonObj["comic_id"]!!.jsonPrimitive.content
chapter.url = "/read/read/$searchname/$id.html" // The url should include the manga name but it doesn't seem to matter url = "/read/read/$searchname/$id.html"
chapter.name = json["name"].string.trim()
chapter.chapter_number = json["obj_id"].asFloat name = jsonObj["name"]!!.jsonPrimitive.content.trim()
chapter.date_upload = parseDate(json["create_date"].string) chapter_number = jsonObj["obj_id"]!!.jsonPrimitive.float
return chapter date_upload = parseDate(jsonObj["create_date"]!!.jsonPrimitive.content)
} }
private fun parseDate(date: String): Long { private fun parseDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(date)?.time ?: 0L 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 document = response.asJsoup()
val manga = SManga.create()
manga.thumbnail_url = document.select("img.detail-post-img").attr("abs:src") thumbnail_url = document.select("img.detail-post-img").attr("abs:src")
manga.description = document.select("h2.fs15 + p").text().trim() description = document.select("h2.fs15 + p").text().trim()
manga.author = document.select("a[href*=artist]").text() author = document.select("a[href*=artist]").text()
manga.artist = document.select("a[href*=artist]").text() 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() } genre = document.select("div.col-sm-10.col-xs-9.text-left.toe.mlr0.text-left-m a[href*=genre]")
manga.genre = glist.joinToString(", ") .joinToString { it.text().substringAfter(",").capitalize(Locale.ROOT) }
manga.status = when (document.select("span.label.label-success").first().text()) { status = when (document.select("span.label.label-success").first().text()) {
"update" -> SManga.ONGOING "update" -> SManga.ONGOING
"finished" -> SManga.COMPLETED "finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
return manga
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val body = response.asJsoup() val document = response.asJsoup()
val pages = mutableListOf<Page>()
val elements = body.select("img[data-src]") return document.select("img[data-src]").mapIndexed { i, el ->
for (i in 0 until elements.size) { Page(i, "", el.select("img").attr("data-src"))
pages.add(Page(i, "", elements[i].select("img").attr("data-src")))
} }
return pages
} }
override fun imageUrlParse(response: Response) = throw Exception("Not used") override fun imageUrlParse(response: Response) = ""
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Mangahasu' extName = 'Mangahasu'
pkgNameSuffix = 'en.mangahasu' pkgNameSuffix = 'en.mangahasu'
extClass = '.Mangahasu' extClass = '.Mangahasu'
extVersionCode = 11 extVersionCode = 12
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,11 +1,6 @@
package eu.kanade.tachiyomi.extension.en.mangahasu package eu.kanade.tachiyomi.extension.en.mangahasu
import android.util.Base64 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.network.GET
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList 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.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -34,9 +35,10 @@ class Mangahasu : ParsedHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
override fun headersBuilder(): Headers.Builder { private val json: Json by injectLazy()
return super.headersBuilder().add("Referer", baseUrl)
} override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", baseUrl)
override fun popularMangaRequest(page: Int): Request = override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/directory.html?page=$page", headers) GET("$baseUrl/directory.html?page=$page", headers)
@ -143,34 +145,40 @@ class Mangahasu : ParsedHttpSource() {
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
// Grab All Pages from site // Grab All Pages from site
// Some images are place holders on new chapters. // Some images are place holders on new chapters.
val pages = mutableListOf<Page>().apply { val pageList = document.select("div.img img")
document.select("div.img img").forEach { .mapIndexed { i, el ->
val pageNumber = it.attr("class").substringAfter("page").toInt() val pageNumber = el.attr("class").substringAfter("page").toInt()
add(Page(pageNumber, "", it.attr("src"))) Page(pageNumber, "", el.attr("src"))
} }
} .toMutableList()
// Some images are not yet loaded onto Mangahasu's image server. // Some images are not yet loaded onto Mangahasu's image server.
// Decode temporary URLs and replace placeholder images. // Decode temporary URLs and replace placeholder images.
val lstDUrls = val lstDUrls = document.select("script:containsData(lstDUrls)")
document.select("script:containsData(lstDUrls)").html().substringAfter("lstDUrls") .html()
.substringAfter("\"").substringBefore("\"") .substringAfter("lstDUrls")
if (lstDUrls != "W10=") { // Base64 = [] or empty file .substringAfter("\"")
val decoded = String(Base64.decode(lstDUrls, Base64.DEFAULT)) .substringBefore("\"")
val json = JsonParser().parse(decoded).array
json.forEach { // Base64 = [] or empty file
val pageNumber = it["page"].int if (lstDUrls != "W10=") {
pages.removeAll { page -> page.index == pageNumber } val jsonRaw = String(Base64.decode(lstDUrls, Base64.DEFAULT))
pages.add(Page(pageNumber, "", it["url"].string)) 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) = "" override fun imageUrlParse(document: Document) = ""

View File

@ -1,17 +1,13 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Kumanga' extName = 'Kumanga'
pkgNameSuffix = 'es.kumanga' pkgNameSuffix = 'es.kumanga'
extClass = '.Kumanga' extClass = '.Kumanga'
extVersionCode = 6 extVersionCode = 7
libVersion = '1.2' 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" apply from: "$rootDir/common.gradle"

View File

@ -1,12 +1,7 @@
package eu.kanade.tachiyomi.extension.es.kumanga package eu.kanade.tachiyomi.extension.es.kumanga
import android.util.Base64 import android.util.Base64
import com.github.salomonbrys.kotson.array import com.github.salomonbrys.kotson.jsonObject
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.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter 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.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
class Kumanga : HttpSource() { 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 override val client: OkHttpClient = network.cloudflareClient
.newBuilder() .newBuilder()
.followRedirects(true) .followRedirects(true)
@ -45,13 +55,7 @@ class Kumanga : HttpSource() {
} }
.build() .build()
override val name = "Kumanga" private val json: Json by injectLazy()
override val baseUrl = "https://www.kumanga.com"
override val lang = "es"
override val supportsLatest = false
override fun headersBuilder(): Headers.Builder = Headers.Builder() 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") .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 { private fun getKumangaToken(): String {
val body = client.newCall(GET("$baseUrl/mangalist?&page=1", headers)).execute().asJsoup() val body = client.newCall(GET("$baseUrl/mangalist?&page=1", headers)).execute().asJsoup()
var dt = body.select("#searchinput").attr("dt").toString() val dt = body.select("#searchinput").attr("dt").toString()
var kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt)).replace("=", "k").toLowerCase() val kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt))
.replace("=", "k")
.toLowerCase(Locale.ROOT)
kumangaToken = body.select("div.input-group [type=hidden]").attr(kumangaTokenKey) kumangaToken = body.select("div.input-group [type=hidden]").attr(kumangaTokenKey)
return kumangaToken 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 getMangaUrl(mangaId: String, mangaSlug: String, page: Int) = "/manga/$mangaId/p/$page/$mangaSlug#cl"
private fun parseMangaFromJson(json: JsonElement) = SManga.create().apply { private fun parseMangaFromJson(jsonObj: JsonObject) = SManga.create().apply {
title = json["name"].string title = jsonObj["name"]!!.jsonPrimitive.content
description = json["description"].string.replace("\\", "") description = jsonObj["description"]!!.jsonPrimitive.content.replace("\\", "")
url = getMangaUrl(json["id"].string, json["slug"].string, 1) url = getMangaUrl(jsonObj["id"]!!.jsonPrimitive.content, jsonObj["slug"]!!.jsonPrimitive.content, 1)
thumbnail_url = getMangaCover(json["id"].string) thumbnail_url = getMangaCover(jsonObj["id"]!!.jsonPrimitive.content)
genre = jsonObj["categories"]!!.jsonArray
val genresArray = json["categories"].array .joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }
genre = genresArray.joinToString { jsonObject ->
parseGenresFromJson(jsonObject)
}
} }
private fun parseJson(json: String): JsonElement {
return JsonParser().parse(json)
}
private fun parseGenresFromJson(json: JsonElement) = json["name"].string
override fun popularMangaRequest(page: Int): Request { 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) 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 { override fun popularMangaParse(response: Response): MangasPage {
val res = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonObject
val json = parseJson(res)
val data = json["contents"].array
val retrievedCount = json["retrievedCount"].int
val hasNextPage = retrievedCount == 10
val mangas = data.map { jsonObject -> val mangaList = jsonResult["contents"]!!.jsonArray
parseMangaFromJson(jsonObject) .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") 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") } else throw Exception("No fue posible obtener los capítulos")
} }
override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup() 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=") ?.substringAfter("var pUrl=")
?.substringBefore(";") ?.substringBefore(";")
?.let { decodeBase64(decodeBase64(it).reversed().dropLast(10).drop(10)) }
?: throw Exception("imagesJsonListStr null") ?: throw Exception("imagesJsonListStr null")
imagesJsonListStr = decodeBase64(decodeBase64(imagesJsonListStr).reversed().dropLast(10).drop(10))
val imagesJsonList = parseJson(imagesJsonListStr).array
imagesJsonList.forEach { val jsonResult = json.parseToJsonElement(imagesJsonRaw).jsonArray
val fakeImageUrl = it["imgURL"].string.replace("\\", "")
val imageUrl = baseUrl + fakeImageUrl
add(Page(size, "", imageUrl)) return jsonResult.mapIndexed { i, jsonEl ->
val jsonObj = jsonEl.jsonObject
val imagePath = jsonObj["imgURL"]!!.jsonPrimitive.content.replace("\\", "")
Page(i, "", baseUrl + imagePath)
} }
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'MangaMx' extName = 'MangaMx'
pkgNameSuffix = 'es.mangamx' pkgNameSuffix = 'es.mangamx'
extClass = '.MangaMx' extClass = '.MangaMx'
extVersionCode = 10 extVersionCode = 11
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -4,10 +4,6 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.util.Base64 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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource 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.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -26,6 +27,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -44,6 +46,8 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() {
private var csrfToken = "" private var csrfToken = ""
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int) = GET( override fun popularMangaRequest(page: Int) = GET(
"$baseUrl/directorio?genero=false" + "$baseUrl/directorio?genero=false" +
"&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", "&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) return MangasPage(mangas, hasNextPage)
} else { } else {
val body = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray
if (body == "[]") throw Exception("Término de búsqueda demasiado corto")
val json = JsonParser().parse(body)["mangas"].asJsonArray
val mangas = json.map { jsonElement -> searchMangaFromJson(jsonElement) } if (jsonResult.isEmpty()) {
val hasNextPage = false throw Exception("Término de búsqueda demasiado corto")
return MangasPage(mangas, hasNextPage) }
val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) }
return MangasPage(mangaList, hasNextPage = false)
} }
} }
private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply {
title = jsonElement["nombre"].string title = jsonObj["nombre"]!!.jsonPrimitive.content
setUrlWithoutDomain(jsonElement["url"].string) thumbnail_url = jsonObj["img"]!!.jsonPrimitive.content.replace("/thumb", "/cover")
thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover") setUrlWithoutDomain(jsonObj["url"]!!.jsonPrimitive.content)
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Japanread' extName = 'Japanread'
pkgNameSuffix = 'fr.japanread' pkgNameSuffix = 'fr.japanread'
extClass = '.Japanread' extClass = '.Japanread'
extVersionCode = 7 extVersionCode = 8
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.extension.fr.japanread package eu.kanade.tachiyomi.extension.fr.japanread
import android.net.Uri 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.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter 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.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.util.Calendar import java.util.Calendar
class Japanread : ParsedHttpSource() { class Japanread : ParsedHttpSource() {
@ -29,6 +32,8 @@ class Japanread : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val json: Json by injectLazy()
// Generic (used by popular/latest/search) // Generic (used by popular/latest/search)
private fun mangaListFromElement(element: Element): SManga { private fun mangaListFromElement(element: Element): SManga {
return SManga.create().apply { return SManga.create().apply {
@ -224,20 +229,19 @@ class Japanread : ParsedHttpSource() {
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val chapterId = document.select("meta[data-chapter-id]").attr("data-chapter-id") 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 jsonResult = json.parseToJsonElement(apiResponse.body!!.string()).jsonObject
val json = JsonParser.parseString(jsonData).asJsonObject
val baseImagesUrl = json["baseImagesUrl"].string val baseImagesUrl = jsonResult["baseImagesUrl"]!!.jsonPrimitive.content
return json["page_array"].asJsonArray.mapIndexed { idx, it -> return jsonResult["page_array"]!!.jsonArray.mapIndexed { i, jsonEl ->
val imgUrl = "$baseUrl$baseImagesUrl/${it.asString}" Page(i, baseUrl, "$baseUrl$baseImagesUrl/${jsonEl.jsonPrimitive.content}")
Page(idx, baseUrl, imgUrl)
} }
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers) return GET(page.imageUrl!!, headers)

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Japscan' extName = 'Japscan'
pkgNameSuffix = 'fr.japscan' pkgNameSuffix = 'fr.japscan'
extClass = '.Japscan' extClass = '.Japscan'
extVersionCode = 28 extVersionCode = 29
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -18,13 +18,6 @@ import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient 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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource 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.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -46,6 +44,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.text.ParseException import java.text.ParseException
@ -66,6 +65,8 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
@ -173,7 +174,9 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
} }
override fun popularMangaNextPageSelector(): String? = null override fun popularMangaNextPageSelector(): String? = null
override fun popularMangaSelector() = "#top_mangas_week li > span" override fun popularMangaSelector() = "#top_mangas_week li > span"
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a").first().let { element.select("a").first().let {
@ -201,10 +204,10 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
} }
override fun latestUpdatesNextPageSelector(): String? = null 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 // Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
@ -227,20 +230,22 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
.build() .build()
try { try {
return client.newCall(POST("$baseUrl/live-search/", searchHeaders, formBody)).execute().use { response -> val searchRequest = POST("$baseUrl/live-search/", searchHeaders, formBody)
if (!response.isSuccessful) throw Exception("Unexpected code $response") val searchResponse = client.newCall(searchRequest).execute()
val jsonObject = gson.fromJson<JsonObject>(response.body!!.string()) if (!searchResponse.isSuccessful) {
throw Exception("Unexpected code ${searchResponse.code}")
if (jsonObject.asJsonArray.size() === 0) {
Log.d("japscan", "Search not returning anything, using duckduckgo")
throw Exception("No data")
}
return response.request
} }
} 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 // Fallback to duckduckgo if the search does not return any result
val uri = Uri.parse("https://duckduckgo.com/lite/").buildUpon() val uri = Uri.parse("https://duckduckgo.com/lite/").buildUpon()
.appendQueryParameter("q", "$query site:$baseUrl/manga/") .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 searchMangaSelector(): String = "div.card div.p-2, a.result-link"
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
if ("live-search" in response.request.url.toString()) { if ("live-search" in response.request.url.toString()) {
val body = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray
val json = JsonParser().parse(body).asJsonArray
val mangas = json.map { jsonElement ->
searchMangaFromJson(jsonElement)
}
val hasNextPage = false val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) }
return MangasPage(mangas, hasNextPage) return MangasPage(mangaList, hasNextPage = false)
} 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 super.searchMangaParse(response)
} }
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
if (element.attr("class") == "result-link") { return if (element.attr("class") == "result-link") {
return SManga.create().apply { SManga.create().apply {
title = element.text().substringAfter(" ").substringBefore(" | JapScan") title = element.text().substringAfter(" ").substringBefore(" | JapScan")
setUrlWithoutDomain(element.attr("abs:href")) setUrlWithoutDomain(element.attr("abs:href"))
} }
} else { } else {
return SManga.create().apply { SManga.create().apply {
thumbnail_url = element.select("img").attr("abs:src") thumbnail_url = element.select("img").attr("abs:src")
element.select("p a").let { element.select("p a").let {
title = it.text() title = it.text()
@ -295,9 +288,9 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
} }
} }
private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply {
title = jsonElement["name"].string title = jsonObj["name"]!!.jsonPrimitive.content
url = jsonElement["url"].string url = jsonObj["url"]!!.jsonPrimitive.content
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
@ -365,7 +358,7 @@ class Japscan : ConfigurableSource, ParsedHttpSource() {
val checkNew = ArrayList<String>(pagecount) val checkNew = ArrayList<String>(pagecount)
var maxIter = document.getElementsByTag("option").size var maxIter = document.getElementsByTag("option").size
var isSinglePage = false 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 isSinglePage = true
maxIter = 1 maxIter = 1
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Scan-Manga' extName = 'Scan-Manga'
pkgNameSuffix = 'fr.scanmanga' pkgNameSuffix = 'fr.scanmanga'
extClass = '.ScanManga' extClass = '.ScanManga'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.extension.fr.scanmanga 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.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList 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.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -21,6 +21,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
import kotlin.random.Random import kotlin.random.Random
class ScanManga : ParsedHttpSource() { class ScanManga : ParsedHttpSource() {
@ -44,7 +45,7 @@ class ScanManga : ParsedHttpSource() {
chain.proceed(newReq) chain.proceed(newReq)
}.build()!! }.build()!!
private val gson = Gson() private val json: Json by injectLazy()
override fun headersBuilder(): Headers.Builder = super.headersBuilder() override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept-Language", "fr-FR") .add("Accept-Language", "fr-FR")
@ -93,7 +94,7 @@ class ScanManga : ParsedHttpSource() {
override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response)
fun shuffle(s: String?): String? { private fun shuffle(s: String?): String {
val result = StringBuffer(s!!) val result = StringBuffer(s!!)
var n = result.length var n = result.length
while (n > 1) { while (n > 1) {
@ -106,10 +107,11 @@ class ScanManga : ParsedHttpSource() {
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val searchHeaders = headersBuilder().apply { val searchHeaders = headersBuilder()
add("Referer", "$baseUrl/scanlation/liste_series.html") .add("Referer", "$baseUrl/scanlation/liste_series.html")
add("x-requested-with", "XMLHttpRequest") .add("x-requested-with", "XMLHttpRequest")
}.build() .build()
return GET("$baseUrl/scanlation/scan.data.json", searchHeaders) return GET("$baseUrl/scanlation/scan.data.json", searchHeaders)
} }
@ -126,58 +128,58 @@ class ScanManga : ParsedHttpSource() {
} }
private fun parseMangaFromJson(response: Response): MangasPage { private fun parseMangaFromJson(response: Response): MangasPage {
val jsonData = response.body!!.string()!! val jsonRaw = response.body!!.string()
if (jsonData == "") {
return MangasPage(listOf<SManga>(), false) if (jsonRaw.isEmpty()) {
return MangasPage(emptyList(), hasNextPage = false)
} }
val jsonObject = JsonParser().parse(jsonData).asJsonObject val jsonObj = json.parseToJsonElement(jsonRaw).jsonObject
val mangas = jsonObject.keySet() val mangaList = jsonObj.entries.map { entry ->
.map { key -> SManga.create().apply {
// "95","%24100-is-Too-Cheap","0","3","One Shot","","2 avril 2010","","335","178","4010","" title = Parser.unescapeEntities(entry.key, false)
SManga.create().apply { genre = entry.value.jsonArray[2].jsonPrimitive.content.let {
url = "/" + jsonObject[key][0].string + "/" + jsonObject[key][1].string + ".html" when {
title = Parser.unescapeEntities(key, false) it.contains("0") -> "Shōnen"
genre = jsonObject[key][2].string.let { it.contains("1") -> "Shōjo"
when { it.contains("2") -> "Seinen"
it.contains("0") -> "Shōnen" it.contains("3") -> "Josei"
it.contains("1") -> "Shōjo" else -> null
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
}
} }
} }
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") override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
// Details // Details
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
return SManga.create().apply { title = document.select("h2[itemprop=\"name\"]").text()
title = document.select("h2[itemprop=\"name\"]").text() author = document.select("li[itemprop=\"author\"] a").joinToString { it.text() }
author = document.select("li[itemprop=\"author\"] a").joinToString { it.text() } description = document.select("p[itemprop=\"description\"]").text()
description = document.select("p[itemprop=\"description\"]").text() thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src")
thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src")
}
} }
// Chapters // Chapters
override fun chapterListSelector() = throw Exception("Not used") override fun chapterListSelector() = throw Exception("Not used")
override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used")
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()
@ -207,9 +209,10 @@ class ScanManga : ParsedHttpSource() {
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
val imgHeaders = headersBuilder().apply { val imgHeaders = headersBuilder()
add("Referer", page.url) .add("Referer", page.url)
}.build() .build()
return GET(page.imageUrl!!, imgHeaders) return GET(page.imageUrl!!, imgHeaders)
} }
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'DigitalTeam' extName = 'DigitalTeam'
pkgNameSuffix = 'it.digitalteam' pkgNameSuffix = 'it.digitalteam'
extClass = '.DigitalTeam' extClass = '.DigitalTeam'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
containsNsfw = false containsNsfw = false
} }

View File

@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.extension.it.digitalteam 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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.FilterList 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.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -23,117 +27,152 @@ import java.util.Locale
class DigitalTeam : ParsedHttpSource() { class DigitalTeam : ParsedHttpSource() {
override val name = "DigitalTeam" override val name = "DigitalTeam"
override val baseUrl = "https://dgtread.com" override val baseUrl = "https://dgtread.com"
override val lang = "it" override val lang = "it"
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/reader/series", headers) 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 popularMangaSelector() = "ul li.manga_block"
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
// ELEMENT // ELEMENT
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
val manga = SManga.create() title = element.select(".manga_title a").text()
manga.thumbnail_url = element.select("img").attr("src") thumbnail_url = element.select("img").attr("src")
manga.setUrlWithoutDomain(element.select(".manga_title a").first().attr("href")) setUrlWithoutDomain(element.select(".manga_title a").first().attr("href"))
manga.title = element.select(".manga_title a").text()
return manga
} }
override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used") override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used")
override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not Used") override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not Used")
// NEXT SELECTOR // NEXT SELECTOR
// Not needed // Not needed
override fun popularMangaNextPageSelector(): String? = null 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 infoElement = document.select("#manga_left")
val manga = SManga.create()
manga.author = infoElement.select(".info_name:contains(Autore)").next()?.text() author = infoElement.select(".info_name:contains(Autore)").next()?.text()
manga.artist = infoElement.select(".info_name:contains(Artista)").next()?.text() artist = infoElement.select(".info_name:contains(Artista)").next()?.text()
manga.genre = infoElement.select(".info_name:contains(Genere)").next()?.text() genre = infoElement.select(".info_name:contains(Genere)").next()?.text()
manga.status = parseStatus(infoElement.select(".info_name:contains(Status)").next().text()) status = parseStatus(infoElement.select(".info_name:contains(Status)").next().text())
manga.description = document.select("div.plot")?.text() description = document.select("div.plot")?.text()
manga.thumbnail_url = infoElement.select(".cover img").attr("src") thumbnail_url = infoElement.select(".cover img").attr("src")
return manga
} }
private fun parseStatus(element: String): Int = when { private fun parseStatus(element: String): Int = when {
element.toLowerCase().contains("in corso") -> SManga.ONGOING element.toLowerCase(Locale.ROOT).contains("in corso") -> SManga.ONGOING
element.toLowerCase().contains("completo") -> SManga.COMPLETED element.toLowerCase(Locale.ROOT).contains("completo") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = ".chapter_list ul li" 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 urlElement = element.select("a").first()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() name = urlElement.text()
chapter.date_upload = element.select(".ch_bottom").first()?.text()?.replace("Pubblicato il ", "")?.let { date_upload = element.select(".ch_bottom").first()?.text()
try { ?.replace("Pubblicato il ", "")
SimpleDateFormat("dd-MM-yyyy", Locale.ITALY).parse(it).time ?.let {
} catch (e: ParseException) { try {
SimpleDateFormat("H", Locale.ITALY).parse(it).time DATE_FORMAT_FULL.parse(it)?.time
} } catch (e: ParseException) {
} ?: 0 DATE_FORMAT_SIMPLE.parse(it)?.time
return chapter }
} ?: 0
} }
protected fun getXhrPages(script_content: String, title: String): String { private fun getXhrPages(script_content: String, title: String): String {
val xhrHeaders = headersBuilder().add("Content-Type: application/x-www-form-urlencoded; charset=UTF-8") 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() .build()
// This can be improved, i don't know how to do it with Regex val xhrHeaders = headersBuilder()
var infomanga = script_content.substringAfter("m='").substringBefore("'") .add("Content-Length", formBody.contentLength().toString())
var infochapter = script_content.substringAfter("ch='").substringBefore("'") .add("Content-Type", formBody.contentType().toString())
var infoch_sub = script_content.substringAfter("chs='").substringBefore("'") .build()
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)) val request = POST("$baseUrl/reader/c_i", xhrHeaders, formBody)
.execute() val response = client.newCall(request).execute()
.asJsoup().select("body").text()
.replace("\\", "").removeSurrounding("\"") return response.asJsoup()
.select("body")
.text()
.replace("\\", "")
.removeSurrounding("\"")
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val scriptContent = document.body().toString()
val script_content = document.body()!!.toString().substringAfter("current_page=").substringBefore(";") .substringAfter("current_page=")
.substringBefore(";")
val title = document.select("title").first().text() val title = document.select("title").first().text()
val imagesJsonList = JsonParser().parse(getXhrPages(script_content, title)).asJsonArray
val image_url = imagesJsonList.get(2).string val xhrPages = getXhrPages(scriptContent, title)
val images_name = imagesJsonList.get(1).asJsonArray val jsonResult = json.parseToJsonElement(xhrPages).jsonArray
val images_data = imagesJsonList.get(0).asJsonArray
images_name.forEachIndexed { index, imagename -> val imageData = jsonResult[0].jsonArray
val imageUrl = val imagePath = jsonResult[2].jsonPrimitive.content
"$baseUrl/reader$image_url" +
"${images_data.get(index).asJsonObject.get("name").string}" + return jsonResult[1].jsonArray.mapIndexed { i, jsonEl ->
"${imagename.string}" + val imageUrl = "$baseUrl/reader$imagePath" +
"${images_data.get(index).asJsonObject.get("ex").string}" imageData[i].jsonObject["name"]!!.jsonPrimitive.content +
pages.add(Page(index, "", imageUrl)) jsonEl.jsonPrimitive.content +
imageData[i].jsonObject["ex"]!!.jsonPrimitive.content
Page(i, "", imageUrl)
} }
return pages
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
val imgHeader = Headers.Builder().apply { val imgHeader = Headers.Builder()
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("User-Agent", USER_AGENT)
add("Referer", baseUrl) .add("Referer", baseUrl)
}.build() .build()
return GET(page.imageUrl!!, imgHeader) 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)
}
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Mangahub' extName = 'Mangahub'
pkgNameSuffix = 'ru.mangahub' pkgNameSuffix = 'ru.mangahub'
extClass = '.Mangahub' extClass = '.Mangahub'
extVersionCode = 9 extVersionCode = 10
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.ru.mangahub package eu.kanade.tachiyomi.extension.ru.mangahub
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList 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.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -26,12 +30,11 @@ open class Mangahub : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val rateLimitInterceptor = RateLimitInterceptor(2)
private val jsonParser = JsonParser()
override val client: OkHttpClient = network.client.newBuilder() 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 = 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) 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<Page> { override fun pageListParse(document: Document): List<Page> {
val chapInfo = document.select("reader").attr("data-store").replace("&quot;", "\"").replace("\\/", "/") val chapInfo = document.select("reader")
val chapter = jsonParser.parse(chapInfo).asJsonObject .attr("data-store")
val scans = chapter["scans"].asJsonArray .replace("&quot;", "\"")
.replace("\\/", "/")
val chapter = json.parseToJsonElement(chapInfo).jsonObject
val pages = mutableListOf<Page>() return chapter["scans"]!!.jsonArray.mapIndexed { i, jsonEl ->
scans.mapIndexed { i, page -> Page(i, "", "https:" + jsonEl.jsonObject["src"]!!.jsonPrimitive.content)
pages.add(Page(i, "", "https:${page.asJsonObject.get("src").asString}"))
} }
return pages
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
val imgHeader = Headers.Builder().apply { val imgHeader = Headers.Builder()
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
add("Referer", baseUrl) .add("Referer", baseUrl)
}.build() .build()
return GET(page.imageUrl!!, imgHeader) return GET(page.imageUrl!!, imgHeader)
} }
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'BH3' extName = 'BH3'
pkgNameSuffix = 'zh.bh3' pkgNameSuffix = 'zh.bh3'
extClass = '.BH3' extClass = '.BH3'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,22 +1,23 @@
package eu.kanade.tachiyomi.extension.zh.bh3 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.network.GET
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.OkHttpClient
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -24,35 +25,48 @@ import java.util.concurrent.TimeUnit
class BH3 : ParsedHttpSource() { class BH3 : ParsedHttpSource() {
override val name = "《崩坏3》IP站" override val name = "《崩坏3》IP站"
override val baseUrl = "https://comic.bh3.com" override val baseUrl = "https://comic.bh3.com"
override val lang = "zh" override val lang = "zh"
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES) .connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true) .retryOnConnectionFailure(true)
.followRedirects(true) .followRedirects(true)
.build()!! .build()
private val json: Json by injectLazy()
override fun popularMangaSelector() = "a[href*=book]" 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 latestUpdatesSelector() = ""
override fun latestUpdatesNextPageSelector() = "none"
override fun searchMangaNextPageSelector() = "none" 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 popularMangaRequest(page: Int) = GET("$baseUrl/book", headers)
override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") 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 chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers)
override fun popularMangaFromElement(element: Element) = mangaFromElement(element) override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
override fun searchMangaFromElement(element: Element) = mangaFromElement(element) override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
private fun mangaFromElement(element: Element): SManga { private fun mangaFromElement(element: Element): SManga {
@ -66,41 +80,33 @@ class BH3 : ParsedHttpSource() {
override fun chapterFromElement(element: Element) = throw Exception("Not Used") override fun chapterFromElement(element: Element) = throw Exception("Not Used")
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val jsondata = response.body!!.string() val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray
val json = JsonParser().parse(jsondata).asJsonArray
val chapters = mutableListOf<SChapter>() return jsonResult.map { jsonEl -> createChapter(jsonEl.jsonObject) }
json.forEach {
chapters.add(createChapter(it))
}
return chapters
} }
private fun createChapter(json: JsonElement) = SChapter.create().apply { private fun createChapter(jsonObj: JsonObject) = SChapter.create().apply {
name = json["title"].string name = jsonObj["title"]!!.jsonPrimitive.content
url = "/book/${json["bookid"].int}/${json["chapterid"].int}" url = "/book/${jsonObj["bookid"]!!.jsonPrimitive.int}/${jsonObj["chapterid"]!!.jsonPrimitive.int}"
date_upload = parseDate(json["timestamp"].string) date_upload = parseDate(jsonObj["timestamp"]!!.jsonPrimitive.content)
chapter_number = json["chapterid"].float chapter_number = jsonObj["chapterid"]!!.jsonPrimitive.float
} }
private fun parseDate(date: String): Long { private fun parseDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0L return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0L
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
val manga = SManga.create() thumbnail_url = document.select("img.cover").attr("abs:src")
manga.thumbnail_url = document.select("img.cover").attr("abs:src") description = document.select("div.detail_info1").text().trim()
manga.description = document.select("div.detail_info1").text().trim() title = document.select("div.title").text().trim()
manga.title = document.select("div.title").text().trim()
return manga
} }
override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> {
val body = response.asJsoup() return document.select("img.lazy.comic_img").mapIndexed { i, el ->
body.select("img.lazy.comic_img")?.forEach { Page(i, "", el.attr("data-original"))
add(Page(size, "", it.attr("data-original")))
} }
} }
override fun pageListParse(document: Document) = throw Exception("Not Used") override fun imageUrlParse(document: Document) = ""
override fun imageUrlParse(document: Document) = throw Exception("Not Used")
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Qimiaomh' extName = 'Qimiaomh'
pkgNameSuffix = 'zh.qimiaomh' pkgNameSuffix = 'zh.qimiaomh'
extClass = '.Qimiaomh' extClass = '.Qimiaomh'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,40 +1,53 @@
package eu.kanade.tachiyomi.extension.zh.qimiaomh 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.network.GET
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource 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.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.Random import java.util.Random
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class Qimiaomh : ParsedHttpSource() { class Qimiaomh : ParsedHttpSource() {
override val name: String = "奇妙漫画" override val name: String = "奇妙漫画"
override val lang: String = "zh" override val lang: String = "zh"
override val baseUrl: String = "https://www.qimiaomh.com" override val baseUrl: String = "https://www.qimiaomh.com"
override val supportsLatest: Boolean = true override val supportsLatest: Boolean = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES) .connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true) .retryOnConnectionFailure(true)
.followRedirects(true) .followRedirects(true)
.build()!! .build()
private val json: Json by injectLazy()
// Popular // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/list-1------hits--$page.html", headers) return GET("$baseUrl/list-1------hits--$page.html", headers)
} }
override fun popularMangaNextPageSelector(): String? = "a:contains(下一页)" override fun popularMangaNextPageSelector(): String? = "a:contains(下一页)"
override fun popularMangaSelector(): String = "div.classification" override fun popularMangaSelector(): String = "div.classification"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
url = element.select("a").first().attr("href") url = element.select("a").first().attr("href")
thumbnail_url = element.select("img.lazyload").attr("abs:data-src") thumbnail_url = element.select("img.lazyload").attr("abs:data-src")
@ -45,8 +58,11 @@ class Qimiaomh : ParsedHttpSource() {
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/list-1------updatetime--$page.html", headers) return GET("$baseUrl/list-1------updatetime--$page.html", headers)
} }
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesSelector(): String = popularMangaSelector() override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
// Search // Search
@ -54,8 +70,11 @@ class Qimiaomh : ParsedHttpSource() {
throw Exception("不管用 (T_T)") throw Exception("不管用 (T_T)")
// Todo Filters // Todo Filters
} }
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
override fun searchMangaSelector(): String = popularMangaSelector() override fun searchMangaSelector(): String = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
// Details // Details
@ -78,31 +97,31 @@ class Qimiaomh : ParsedHttpSource() {
// Chapters // Chapters
override fun chapterListSelector(): String = "div.comic-content-list ul.comic-content-c" override fun chapterListSelector(): String = "div.comic-content-list ul.comic-content-c"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
url = element.select("a").first().attr("href") url = element.select("a").first().attr("href")
name = element.select("li.tit").text().trim() name = element.select("li.tit").text().trim()
} }
private fun parseDate(date: String): Long { private fun parseDate(date: String): Long {
return SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L return SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L
} }
// Pages // Pages
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> {
val script = document.select("script:containsData(var did =)").html() val script = document.select("script:containsData(var did =)").html()
val did = script.substringAfter("var did = ").substringBefore(";") val did = script.substringAfter("var did = ").substringBefore(";")
val sid = script.substringAfter("var sid = ").substringBefore(";") val sid = script.substringAfter("var sid = ").substringBefore(";")
val url = "$baseUrl/Action/Play/AjaxLoadImgUrl?did=$did&sid=$sid&tmp=${Random().nextFloat()}" val url = "$baseUrl/Action/Play/AjaxLoadImgUrl?did=$did&sid=$sid&tmp=${Random().nextFloat()}"
val body = client.newCall(GET(url, headers)).execute().body!!.string() val response = client.newCall(GET(url, headers)).execute()
val json = JsonParser().parse(body).asJsonObject val result = json.parseToJsonElement(response.body!!.string()).jsonObject
val images = json["listImg"].asJsonArray
images.forEachIndexed { index, jsonElement -> return result["listImg"]!!.jsonArray.mapIndexed { i, jsonEl ->
add(Page(index, "", jsonElement.string)) Page(i, "", jsonEl.jsonPrimitive.content)
} }
} }
override fun imageUrlParse(document: Document): String {
throw Exception("Not Used")
}
// Not Used override fun imageUrlParse(document: Document): String = ""
} }