Added Mangakawaii, based on MMRCMS (#907)

Added Mangakawaii, based on MMRCMS
This commit is contained in:
Amine A 2019-03-14 22:01:18 +01:00 committed by Carlos
parent 0b7431cae9
commit 3b82504fc0
8 changed files with 341 additions and 0 deletions

View File

@ -0,0 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Mangakawaii'
pkgNameSuffix = 'fr.mangakawaii'
extClass = '.MangaKawaiiSource'
extVersionCode = 1
libVersion = '1.2'
}
dependencies {
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,324 @@
package eu.kanade.tachiyomi.extension.fr.mangakawaii
import android.net.Uri
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
class MangaKawaiiSource : HttpSource() {
override val lang = "fr"
override val name = "Mangakawaii"
override val baseUrl="https://www.mangakawaii.to"
override val supportsLatest = false
private val itemUrl= "https://www.mangakawaii.to/manga/"
private val jsonParser = JsonParser()
private val itemUrlPath = Uri.parse(itemUrl).pathSegments.first()
private val parsedBaseUrl = Uri.parse(baseUrl)
private val categoriesJson = """{"categories":[{"id":"1","name":"Action"},{"id":"2","name":"Aventure"},{"id":"3","name":"Comédie"},{"id":"5","name":"Drame"},{"id":"7","name":"Fantastique"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historique"},{"id":"11","name":"Horreur"},{"id":"12","name":"Josei"},{"id":"13","name":"Arts Martiaux"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystère"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychologique"},{"id":"19","name":"Romance"},{"id":"20","name":"Vie Scolaire"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shojo"},{"id":"24","name":"Shojo Ai"},{"id":"25","name":"Shonen"},{"id":"26","name":"Shonen Ai"},{"id":"27","name":"Tranche de vie"},{"id":"28","name":"Sports"},{"id":"29","name":"Surnaturel"},{"id":"30","name":"Adulte"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Webtoon"},{"id":"35","name":"Ecchi"},{"id":"36","name":"Doujin"}]}"""
private val jsonCategories = jsonParser.parse(categoriesJson) as JsonObject
private val categoryMappings = mapToPairs(jsonCategories["categories"].array)
override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaRequest(page: Int) = GET("$baseUrl/filterLists?page=$page&sortBy=views&asc=false")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
//Query overrides everything
val url: Uri.Builder
if (query.isNotBlank()) {
url = Uri.parse("$baseUrl/recherche")!!.buildUpon()
url.appendQueryParameter("query", query)
} else {
url = Uri.parse("$baseUrl/filterLists?page=$page")!!.buildUpon()
filters.filterIsInstance<UriFilter>()
.forEach { it.addToUri(url) }
}
return GET(url.toString())
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filterLists?page=$page&sortBy=last_release&asc=false")
override fun popularMangaParse(response: Response) = internalMangaParse(response)
override fun searchMangaParse(response: Response): MangasPage {
return if (response.request().url().queryParameter("query")?.isNotBlank() == true) {
//If a search query was specified, use search instead!
MangasPage(jsonParser
.parse(response.body()!!.string())["suggestions"].array
.map {
SManga.create().apply {
val segment = it["data"].string
url = getUrlWithoutBaseUrl(itemUrl + segment)
title = it["value"].string
// Guess thumbnails
// thumbnail_url = "$baseUrl/uploads/manga/$segment/cover/cover_250x350.jpg"
}
}, false)
} else {
internalMangaParse(response)
}
}
override fun latestUpdatesParse(response: Response) = internalMangaParse(response)
private fun internalMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
return MangasPage(document.select("div[class^=col-sm]").map {
SManga.create().apply {
val urlElement = it.getElementsByClass("infobubble")
url = getUrlWithoutBaseUrl(urlElement.attr("href"))
title = it.select("p.infotitle").text().trim()
val cover = it.select(".media-left img").attr("data-src")
thumbnail_url =
if (cover.isEmpty()) {
coverGuess(it.select("img").attr("data-src"), url)
} else {
coverGuess(cover, url)
}
}
}, document.select(".pagination a[rel=next]").isNotEmpty())
}
// Guess thumbnails on broken websites
private fun coverGuess(url: String?, mangaUrl: String): String {
// Guess thumbnails on broken websites
if (url != null && url.isNotBlank()) {
if (url.startsWith("//")) {
return "$baseUrl/uploads/manga/${url.substringBeforeLast("/cover/").substringAfter("/manga/")}/cover/cover_250x350.jpg"
}
if (url.endsWith("no-image.png")) {
return "$baseUrl/uploads/manga/${mangaUrl?.substringAfterLast('/')}/cover/cover_250x350.jpg"
}
return url
}
return ""
}
private fun getUrlWithoutBaseUrl(newUrl: String): String {
val parsedNewUrl = Uri.parse(newUrl)
val newPathSegments = parsedNewUrl.pathSegments.toMutableList()
for (i in parsedBaseUrl.pathSegments) {
if (i.trim().equals(newPathSegments.first(), true)) {
newPathSegments.removeAt(0)
} else break
}
val builtUrl = parsedNewUrl.buildUpon().path("/")
newPathSegments.forEach { builtUrl.appendPath(it) }
var out = builtUrl.build().encodedPath
if (parsedNewUrl.encodedQuery != null)
out += "?" + parsedNewUrl.encodedQuery
if (parsedNewUrl.encodedFragment != null)
out += "#" + parsedNewUrl.encodedFragment
return out
}
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val document = response.asJsoup()
title = document.select(".info-desc__content").text().trim()
thumbnail_url = coverGuess(document.select(".manga__image .manga__cover").attr("src"), document.location())
description = document.select(".info-desc__content").text().trim()
var cur: String? = null
for (element in document.select(".manga-info .info-list__row").select("strong,a,span")) {
when (element.tagName()) {
"strong" -> cur = element.text().trim().toLowerCase()
"a","span" -> when (cur) {
"auteur(s)" -> author = element.text()
"artiste(s)" -> artist = element.text()
"categories" -> genre = element.getElementsByTag("a").joinToString {
it.text().trim()
}
"statut"-> status = when (element.text().trim().toLowerCase()) {
"terminé" -> SManga.COMPLETED
"en cours" -> SManga.ONGOING
else -> SManga.UNKNOWN
}
}
}
}
}
/**
* Parses the response from the site and returns a list of chapters.
*
* Overriden to allow for null chapters
*
* @param response the response from the site.
*/
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return document.select(chapterListSelector()).mapNotNull { nullableChapterFromElement(it) }
}
/**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter.
*/
fun chapterListSelector() = ".chapters-list > .chapter-item:not(.btn)"
/**
* Returns a chapter from the given element.
*
* @param element an element obtained from [chapterListSelector].
*/
private fun nullableChapterFromElement(element: Element): SChapter? {
val titleWrapper = element.getElementsByClass("list-item__title").first()
val url = titleWrapper.getElementsByTag("a").attr("href")
// Ensure chapter actually links to a manga
// Some websites use the chapters box to link to post announcements
if (!Uri.parse(url).pathSegments.firstOrNull().equals(itemUrlPath, true)) {
return null
}
val chapter = SChapter.create()
chapter.url = getUrlWithoutBaseUrl(url)
chapter.name = titleWrapper.text()
// Parse date
val dateText = element.getElementsByClass("chapter-item__date").text().trim()
val formattedDate = try {
DATE_FORMAT.parse(dateText).time
} catch (e: ParseException) {
0L
}
chapter.date_upload = formattedDate
return chapter
}
override fun pageListParse(response: Response) = response.asJsoup().select("#all > .img-responsive")
.mapIndexed { i, e ->
var url = e.attr("data-src")
if (url.isBlank()) {
url = e.attr("src")
}
url = url.trim()
Page(i, url, url)
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Unused method called!")
private fun getInitialFilterList() = listOf<Filter<*>>(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
AuthorFilter(),
UriSelectFilter("Category",
"cat",
arrayOf("" to "Any",
*categoryMappings.toTypedArray()
)
),
UriSelectFilter("Begins with",
"alpha",
arrayOf("" to "Any",
*"#ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray().map {
Pair(it.toString(), it.toString())
}.toTypedArray()
)
),
SortFilter()
)
/**
* Returns the list of filters for the source.
*/
override fun getFilterList() = FilterList(
getInitialFilterList()
)
/**
* Class that creates a select filter. Each entry in the dropdown has a name and a display name.
* If an entry is selected it is appended as a query parameter onto the end of the URI.
* If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI.
*/
//vals: <name, display>
open class UriSelectFilter(displayName: String, val uriParam: String, val vals: Array<Pair<String, String>>,
val firstIsUnspecified: Boolean = true,
defaultValue: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
override fun addToUri(uri: Uri.Builder) {
if (state != 0 || !firstIsUnspecified)
uri.appendQueryParameter(uriParam, vals[state].first)
}
}
class AuthorFilter : Filter.Text("Author"), UriFilter {
override fun addToUri(uri: Uri.Builder) {
uri.appendQueryParameter("author", state)
}
}
class SortFilter : Filter.Sort("Sort",
sortables.map { it.second }.toTypedArray(),
Filter.Sort.Selection(0, true)), UriFilter {
override fun addToUri(uri: Uri.Builder) {
uri.appendQueryParameter("sortBy", sortables[state!!.index].first)
uri.appendQueryParameter("asc", state!!.ascending.toString())
}
companion object {
private val sortables = arrayOf(
"name" to "Name",
"views" to "Popularity",
"last_release" to "Last update"
)
}
}
/**
* Represents a filter that is able to modify a URI.
*/
interface UriFilter {
fun addToUri(uri: Uri.Builder)
}
companion object {
private val DATE_FORMAT = SimpleDateFormat("DD.MM.yyyy", Locale.FRANCE)
}
/**
* Map an array of JSON objects to pairs. Each JSON object must have
* the following properties:
*
* id: first item in pair
* name: second item in pair
*
* @param array The array to process
* @return The new list of pairs
*/
private fun mapToPairs(array: JsonArray): List<Pair<String, String>> = array.map {
it as JsonObject
it["id"].string to it["name"].string
}
}