diff --git a/src/en/manhwahentai/build.gradle b/src/en/manhwahentai/build.gradle new file mode 100644 index 000000000..d4d9b38fc --- /dev/null +++ b/src/en/manhwahentai/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Manhwahentai' + pkgNameSuffix = 'en.manhwahentai' + extClass = '.Manhwahentai' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher.xml b/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/en/manhwahentai/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher.png b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..2d3286cc1 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_foreground.png b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..a6815bfb9 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_round.png b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..7f10e8d14 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher.png b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..cdb6b0880 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_foreground.png b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d05e6bb1b Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_round.png b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..43ba53ad8 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher.png b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..48434937c Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..76153d4ec Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_round.png b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..c4c1cfa15 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..51ac95df0 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..cf66f6e26 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_round.png b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..9edd38446 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..1a05ccb74 Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..45e83f59e Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..64415a26b Binary files /dev/null and b/src/en/manhwahentai/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src/en/manhwahentai/res/values/ic_launcher_background.xml b/src/en/manhwahentai/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..b15a8af4f --- /dev/null +++ b/src/en/manhwahentai/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #1A1A1A + \ No newline at end of file diff --git a/src/en/manhwahentai/res/web_hi_res_512.png b/src/en/manhwahentai/res/web_hi_res_512.png new file mode 100644 index 000000000..595ab7908 Binary files /dev/null and b/src/en/manhwahentai/res/web_hi_res_512.png differ diff --git a/src/en/manhwahentai/src/eu/kanade/tachiyomi/extension/en/manhwahentai/Manhwahentai.kt b/src/en/manhwahentai/src/eu/kanade/tachiyomi/extension/en/manhwahentai/Manhwahentai.kt new file mode 100644 index 000000000..3ac12d56e --- /dev/null +++ b/src/en/manhwahentai/src/eu/kanade/tachiyomi/extension/en/manhwahentai/Manhwahentai.kt @@ -0,0 +1,253 @@ +package eu.kanade.tachiyomi.extension.en.manhwahentai + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.* +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.* +import eu.kanade.tachiyomi.source.model.* +import java.text.ParseException + +class Manhwahentai: ParsedHttpSource() { + + override val name = "Manhwahentai" + override val baseUrl = "https://manhwahentai.com" + override val lang = "en" + override val supportsLatest = true + override val client: OkHttpClient = network.cloudflareClient + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers) + } + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers) + } + // LIST SELECTOR + override fun popularMangaSelector() = "div.c-tabs-item__content" + override fun latestUpdatesSelector() = popularMangaSelector() + override fun searchMangaSelector() = popularMangaSelector() + + // ELEMENT + override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) + override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) + + // NEXT SELECTOR + override fun popularMangaNextPageSelector() = "div.nav-previous.float-left > a" + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaFromElement(element: Element):SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("div.tab-thumb > a > img").attr("src") + element.select("div.tab-thumb > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() + url.addQueryParameter("post_type","wp-manga") + val pattern = "\\s+".toRegex() + val q = query.replace(pattern, "+") + if(query.length > 0){ + url.addQueryParameter("s", q) + }else{ + url.addQueryParameter("s", "") + } + + var orderBy = "" + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { +// is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) + is GenreList -> { + val genreInclude = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + genreInclude.add(it.id) + } + } + if(genreInclude.isNotEmpty()){ + genreInclude.forEach{ genre -> + url.addQueryParameter("genre[]", genre) + } + } + } + is StatusList ->{ + val statuses = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + statuses.add(it.id) + } + } + if(statuses.isNotEmpty()){ + statuses.forEach{ status -> + url.addQueryParameter("status[]", status) + } + } + } + + is SortBy -> { + orderBy = filter.toUriPart(); + url.addQueryParameter("m_orderby",orderBy) + } + is TextField -> url.addQueryParameter(filter.key, filter.state) + } + } + + return GET(url.toString(), headers) + } + + + + // max 200 results + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.site-content").first() + + val manga = SManga.create() + manga.author = infoElement.select("div.author-content")?.text() + manga.artist = infoElement.select("div.artist-content")?.text() + + val genres = mutableListOf() + infoElement.select("div.genres-content a").forEach { element -> + val genre = element.text() + genres.add(genre) + } + manga.genre =genres.joinToString(", ") + manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2) div").text()) + + manga.description = document.select("div.description-summary")?.text() + manga.thumbnail_url = document.select("div.summary_image > a > img").attr("src") + + return manga + } + + private fun parseStatus(element: String): Int = when { + + element.toLowerCase().contains("ongoing") -> SManga.ONGOING + element.toLowerCase().contains("completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "li.wp-manga-chapter" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("span.chapter-release-date i").last()?.text()?.let { + try { + SimpleDateFormat("MMMM dd, yyyy", Locale.US).parse(it).time + } catch (e: ParseException) { + SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it).time + } + + } ?: 0 + return chapter + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + val basic = Regex("""Chapter\s([0-9]+)""") + when { + basic.containsMatchIn(chapter.name) -> { + basic.find(chapter.name)?.let { + chapter.chapter_number = it.groups[1]?.value!!.toFloat() + } + } + } + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + var i = 0 + document.select("div.reading-content * img").forEach { element -> + val url = element.attr("src") + i++ + if(url.length != 0){ + pages.add(Page(i, "", url)) + } + } + return pages + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val imgHeader = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") + add("Referer", baseUrl) + }.build() + return GET(page.imageUrl!!, imgHeader) + } + // private class Status : Filter.TriState("Completed") + private class TextField(name: String, val key: String) : Filter.Text(name) + private class SortBy : UriPartFilter("Sort by", arrayOf( + Pair("Relevance", ""), + Pair("Latest", "latest"), + Pair("A-Z", "alphabet"), + Pair("Rating", "rating"), + Pair("Trending", "trending"), + Pair("Most View", "views"), + Pair("New", "new-manga") + )) + private class Genre(name: String, val id: String = name) : Filter.TriState(name) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + private class Status(name: String, val id: String = name) : Filter.TriState(name) + private class StatusList(statuses: List) : Filter.Group("Status", statuses) + + override fun getFilterList() = FilterList( +// TextField("Judul", "title"), + TextField("Author", "author"), + TextField("Year", "release"), + SortBy(), + StatusList(getStatusList()), + GenreList(getGenreList()) + ) + private fun getStatusList() = listOf( + Status("Completed","end"), + Status("Ongoing","on-going"), + Status("Canceled","canceled"), + Status("Onhold","on-hold") + ) + private fun getGenreList() = listOf( + Genre("Action","action"), + Genre("Adventure","adventure"), + Genre("Comedy","comedy"), + Genre("Drama","drama"), + Genre("Fantasy","fantasy"), + Genre("Gender bender","gender-bender"), + Genre("Gossip","gossip"), + Genre("Harem","harem"), + Genre("Historical","historical"), + Genre("Horror","horror"), + Genre("Incest","incest"), + Genre("Martial arts","martial-arts"), + Genre("Mecha","mecha"), + Genre("Medical","medical"), + Genre("Mystery","mystery"), + Genre("One shot","one-shot"), + Genre("Psychological","psychological"), + Genre("Romance","romance"), + Genre("School LIfe","school-life"), + Genre("Sci Fi","sci-fi"), + Genre("Slice of Life","slice-of-life"), + Genre("Smut","smut"), + Genre("Sports","sports"), + Genre("Supernatural","supernatural"), + Genre("Tragedy","tragedy"), + Genre("Yaoi","yaoi"), + Genre("Yuri","yuri") + ) + private open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + +} \ No newline at end of file diff --git a/src/id/komikgo/build.gradle b/src/id/komikgo/build.gradle new file mode 100644 index 000000000..75add5ddf --- /dev/null +++ b/src/id/komikgo/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Komikgo' + pkgNameSuffix = 'id.komikgo' + extClass = '.Komikgo' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher.xml b/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/id/komikgo/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/id/komikgo/res/mipmap-hdpi/ic_launcher.png b/src/id/komikgo/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..a8e5e239b Binary files /dev/null and b/src/id/komikgo/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/id/komikgo/res/mipmap-hdpi/ic_launcher_foreground.png b/src/id/komikgo/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..95f973b31 Binary files /dev/null and b/src/id/komikgo/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src/id/komikgo/res/mipmap-hdpi/ic_launcher_round.png b/src/id/komikgo/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..f584b4b24 Binary files /dev/null and b/src/id/komikgo/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src/id/komikgo/res/mipmap-mdpi/ic_launcher.png b/src/id/komikgo/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..7ed757857 Binary files /dev/null and b/src/id/komikgo/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/id/komikgo/res/mipmap-mdpi/ic_launcher_foreground.png b/src/id/komikgo/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..e6a6ce985 Binary files /dev/null and b/src/id/komikgo/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src/id/komikgo/res/mipmap-mdpi/ic_launcher_round.png b/src/id/komikgo/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..3f4da1eaa Binary files /dev/null and b/src/id/komikgo/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src/id/komikgo/res/mipmap-xhdpi/ic_launcher.png b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..e5fe0fd2a Binary files /dev/null and b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..23146648c Binary files /dev/null and b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_round.png b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..6d892d887 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher.png b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..32526aef5 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..36dbe2d3a Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_round.png b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..cc66cd0f2 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b96960fd2 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..81cf30721 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..aca6bb631 Binary files /dev/null and b/src/id/komikgo/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src/id/komikgo/res/values/ic_launcher_background.xml b/src/id/komikgo/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..b15a8af4f --- /dev/null +++ b/src/id/komikgo/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #1A1A1A + \ No newline at end of file diff --git a/src/id/komikgo/res/web_hi_res_512.png b/src/id/komikgo/res/web_hi_res_512.png new file mode 100644 index 000000000..54876ae38 Binary files /dev/null and b/src/id/komikgo/res/web_hi_res_512.png differ diff --git a/src/id/komikgo/src/eu/kanade/tachiyomi/extension/id/komikgo/Komikgo.kt b/src/id/komikgo/src/eu/kanade/tachiyomi/extension/id/komikgo/Komikgo.kt new file mode 100644 index 000000000..23acf46e9 --- /dev/null +++ b/src/id/komikgo/src/eu/kanade/tachiyomi/extension/id/komikgo/Komikgo.kt @@ -0,0 +1,288 @@ +package eu.kanade.tachiyomi.extension.id.komikgo + +import java.util.* +import android.util.Log +import eu.kanade.tachiyomi.network.GET +import java.text.ParseException +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.* +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import eu.kanade.tachiyomi.source.model.* + + +class Komikgo : ParsedHttpSource() { + + override val name = "Komikgo" + override val baseUrl = "https://komikgo.com" + override val lang = "id" + override val supportsLatest = true + override val client: OkHttpClient = network.cloudflareClient + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers) + } + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers) + } + // LIST SELECTOR + override fun popularMangaSelector() = "div.c-tabs-item__content" + override fun latestUpdatesSelector() = popularMangaSelector() + override fun searchMangaSelector() = popularMangaSelector() + + // ELEMENT + override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) + override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) + + // NEXT SELECTOR + override fun popularMangaNextPageSelector() = "#navigation-ajax" + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaFromElement(element: Element):SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("div.tab-thumb > a > img").attr("src") + element.select("div.tab-thumb > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() + url.addQueryParameter("post_type","wp-manga") + val pattern = "\\s+".toRegex() + val q = query.replace(pattern, "+") + if(query.length > 0){ + url.addQueryParameter("s", q) + }else{ + url.addQueryParameter("s", "") + } + + var orderBy = "" + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { +// is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) + is GenreList -> { + val genreInclude = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + genreInclude.add(it.id) + } + } + if(genreInclude.isNotEmpty()){ + genreInclude.forEach{ genre -> + url.addQueryParameter("genre[]", genre) + } + } + } + is StatusList ->{ + val statuses = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + statuses.add(it.id) + } + } + if(statuses.isNotEmpty()){ + statuses.forEach{ status -> + url.addQueryParameter("status[]", status) + } + } + } + + is SortBy -> { + orderBy = filter.toUriPart(); + url.addQueryParameter("m_orderby",orderBy) + } + is TextField -> url.addQueryParameter(filter.key, filter.state) + } + } + Log.d("TAG", url.toString()) + + return GET(url.toString(), headers) + } + + + + // max 200 results + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.site-content").first() + + val manga = SManga.create() + manga.author = infoElement.select("div.author-content")?.text() + manga.artist = infoElement.select("div.artist-content")?.text() + + val genres = mutableListOf() + infoElement.select("div.genres-content a").forEach { element -> + val genre = element.text() + genres.add(genre) + } + manga.genre =genres.joinToString(", ") + manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2) div").text()) + + manga.description = document.select("div.description-summary")?.text() + manga.thumbnail_url = document.select("div.summary_image > a > img").attr("src") + + return manga + } + + private fun parseStatus(element: String): Int = when { + + element.toLowerCase().contains("ongoing") -> SManga.ONGOING + element.toLowerCase().contains("completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "li.wp-manga-chapter" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("span.chapter-release-date i").last()?.text()?.let { + try { + SimpleDateFormat("MMMM dd, yyyy", Locale.US).parse(it).time + } catch (e: ParseException) { + SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it).time + } + + } ?: 0 + return chapter + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + val basic = Regex("""Chapter\s([0-9]+)""") + when { + basic.containsMatchIn(chapter.name) -> { + basic.find(chapter.name)?.let { + chapter.chapter_number = it.groups[1]?.value!!.toFloat() + } + } + } + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + var i = 0 + document.select("div.reading-content * img").forEach { element -> + val url = element.attr("src") + i++ + if(url.length != 0){ + pages.add(Page(i, "", url)) + } + } + return pages + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val imgHeader = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") + add("Referer", baseUrl) + }.build() + return GET(page.imageUrl!!, imgHeader) + } + // private class Status : Filter.TriState("Completed") + private class TextField(name: String, val key: String) : Filter.Text(name) + private class SortBy : UriPartFilter("Sort by", arrayOf( + Pair("Relevance", ""), + Pair("Latest", "latest"), + Pair("A-Z", "alphabet"), + Pair("Rating", "rating"), + Pair("Trending", "trending"), + Pair("Most View", "views"), + Pair("New", "new-manga") + )) + private class Genre(name: String, val id: String = name) : Filter.TriState(name) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + private class Status(name: String, val id: String = name) : Filter.TriState(name) + private class StatusList(statuses: List) : Filter.Group("Status", statuses) + + override fun getFilterList() = FilterList( +// TextField("Judul", "title"), + TextField("Author", "author"), + TextField("Year", "release"), + SortBy(), + StatusList(getStatusList()), + GenreList(getGenreList()) + ) + private fun getStatusList() = listOf( + Status("Completed","end"), + Status("Ongoing","on-going"), + Status("Canceled","canceled"), + Status("Onhold","on-hold") + ) + private fun getGenreList() = listOf( + Genre("Adventure", "Adventure"), + Genre( "Action", "action"), + Genre( "Adventure", "adventure"), + Genre( "Cars", "cars"), + Genre( "4-Koma", "4-koma"), + Genre( "Comedy", "comedy"), + Genre( "Completed", "completed"), + Genre( "Cooking", "cooking"), + Genre( "Dementia", "dementia"), + Genre( "Demons", "demons"), + Genre( "Doujinshi", "doujinshi"), + Genre( "Drama", "drama"), + Genre( "Ecchi", "ecchi"), + Genre( "Fantasy", "fantasy"), + Genre( "Game", "game"), + Genre( "Gender Bender", "gender-bender"), + Genre( "Harem", "harem"), + Genre( "Historical", "historical"), + Genre( "Horror", "horror"), + Genre( "Isekai", "isekai"), + Genre( "Josei", "josei"), + Genre( "Kids", "kids"), + Genre( "Magic", "magic"), + Genre( "Manga", "manga"), + Genre( "Manhua", "manhua"), + Genre( "Manhwa", "manhwa"), + Genre( "Martial Arts", "martial-arts"), + Genre( "Mature", "mature"), + Genre( "Mecha", "mecha"), + Genre( "Military", "military"), + Genre( "Music", "music"), + Genre( "Mystery", "mystery"), + Genre( "Old Comic", "old-comic"), + Genre( "One Shot", "one-shot"), + Genre( "Oneshot", "oneshot"), + Genre( "Parodi", "parodi"), + Genre( "Parody", "parody"), + Genre( "Police", "police"), + Genre( "Psychological", "psychological"), + Genre( "Romance", "romance"), + Genre( "Samurai", "samurai"), + Genre( "School", "school"), + Genre( "School Life", "school-life"), + Genre( "Sci-Fi", "sci-fi"), + Genre( "Seinen", "seinen"), + Genre( "Shoujo", "shoujo"), + Genre( "Shoujo Ai", "shoujo-ai"), + Genre( "Shounen", "shounen"), + Genre( "Shounen ai", "shounen-ai"), + Genre( "Slice of Life", "slice-of-life"), + Genre( "Sports", "sports"), + Genre( "Super Power", "super-power"), + Genre( "Supernatural", "supernatural"), + Genre( "Thriller", "thriller"), + Genre( "Tragedy", "tragedy"), + Genre( "Vampire", "vampire"), + Genre( "Webtoons", "webtoons"), + Genre( "Yaoi", "yaoi"), + Genre( "Yuri", "yuri") + ) + private open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + +} \ No newline at end of file diff --git a/src/ko/manhwahand/build.gradle b/src/ko/manhwahand/build.gradle new file mode 100644 index 000000000..d1136c17a --- /dev/null +++ b/src/ko/manhwahand/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Manhwahand' + pkgNameSuffix = 'ko.manhwahand' + extClass = '.Manhwahand' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher.xml b/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/src/ko/manhwahand/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher.png b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..79f396016 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_foreground.png b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..27234a0f2 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_round.png b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..b4270a220 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher.png b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..eee7884f0 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_foreground.png b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..8676e233e Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_round.png b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..0f69b26bc Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher.png b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..9136e3f44 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..51d038786 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_round.png b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..43df3d91e Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..e4e36bdc4 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..daa8f60aa Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_round.png b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..7824e5824 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..685a637b9 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..bb7d42312 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..8b8c09553 Binary files /dev/null and b/src/ko/manhwahand/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src/ko/manhwahand/res/values/ic_launcher_background.xml b/src/ko/manhwahand/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..b15a8af4f --- /dev/null +++ b/src/ko/manhwahand/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #1A1A1A + \ No newline at end of file diff --git a/src/ko/manhwahand/res/web_hi_res_512.png b/src/ko/manhwahand/res/web_hi_res_512.png new file mode 100644 index 000000000..06edd7757 Binary files /dev/null and b/src/ko/manhwahand/res/web_hi_res_512.png differ diff --git a/src/ko/manhwahand/src/eu/kanade/tachiyomi/extension/ko/manhwahand/Manhwahand.kt b/src/ko/manhwahand/src/eu/kanade/tachiyomi/extension/ko/manhwahand/Manhwahand.kt new file mode 100644 index 000000000..62a76afe3 --- /dev/null +++ b/src/ko/manhwahand/src/eu/kanade/tachiyomi/extension/ko/manhwahand/Manhwahand.kt @@ -0,0 +1,251 @@ +package eu.kanade.tachiyomi.extension.ko.manhwahand + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import java.util.* +import okhttp3.* +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.text.ParseException + + +class Manhwahand : ParsedHttpSource() { + + override val name = "Manhwahand" + override val baseUrl = "https://manhwahand.com/" + override val lang = "ko" + override val supportsLatest = true + override val client: OkHttpClient = network.cloudflareClient + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers) + } + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers) + } + // LIST SELECTOR + override fun popularMangaSelector() = "div.c-tabs-item__content" + override fun latestUpdatesSelector() = popularMangaSelector() + override fun searchMangaSelector() = popularMangaSelector() + + // ELEMENT + override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) + override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) + + // NEXT SELECTOR + override fun popularMangaNextPageSelector() = "div.nav-previous.float-left > a" + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaFromElement(element: Element):SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("div.tab-thumb > a > img").attr("src") + element.select("div.tab-thumb > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() + url.addQueryParameter("post_type","wp-manga") + val pattern = "\\s+".toRegex() + val q = query.replace(pattern, "+") + if(query.length > 0){ + url.addQueryParameter("s", q) + }else{ + url.addQueryParameter("s", "") + } + + var orderBy = "" + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { +// is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) + is GenreList -> { + val genreInclude = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + genreInclude.add(it.id) + } + } + if(genreInclude.isNotEmpty()){ + genreInclude.forEach{ genre -> + url.addQueryParameter("genre[]", genre) + } + } + } + is StatusList ->{ + val statuses = mutableListOf() + filter.state.forEach { + if (it.state == 1) { + statuses.add(it.id) + } + } + if(statuses.isNotEmpty()){ + statuses.forEach{ status -> + url.addQueryParameter("status[]", status) + } + } + } + + is SortBy -> { + orderBy = filter.toUriPart(); + url.addQueryParameter("m_orderby",orderBy) + } + is TextField -> url.addQueryParameter(filter.key, filter.state) + } + } + return GET(url.toString(), headers) + } + + + + // max 200 results + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.site-content").first() + + val manga = SManga.create() + manga.author = infoElement.select("div.author-content")?.text() + manga.artist = infoElement.select("div.artist-content")?.text() + + val genres = mutableListOf() + infoElement.select("div.genres-content a").forEach { element -> + val genre = element.text() + genres.add(genre) + } + manga.genre =genres.joinToString(", ") + manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2) div").text()) + + manga.description = document.select("div.description-summary")?.text() + manga.thumbnail_url = document.select("div.summary_image > a > img").attr("src") + + return manga + } + + private fun parseStatus(element: String): Int = when { + + element.toLowerCase().contains("ongoing") -> SManga.ONGOING + element.toLowerCase().contains("completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "li.wp-manga-chapter" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("span.chapter-release-date i").last()?.text()?.let { + try { + SimpleDateFormat("MMMM dd, yyyy", Locale.US).parse(it).time + } catch (e: ParseException) { + SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it).time + } + + } ?: 0 + return chapter + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + val basic = Regex("""Chapter\s([0-9]+)""") + when { + basic.containsMatchIn(chapter.name) -> { + basic.find(chapter.name)?.let { + chapter.chapter_number = it.groups[1]?.value!!.toFloat() + } + } + } + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + var i = 0 + document.select("div.reading-content * img").forEach { element -> + val url = element.attr("src") + i++ + if(url.length != 0){ + pages.add(Page(i, "", url)) + } + } + return pages + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val imgHeader = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") + add("Referer", baseUrl) + }.build() + return GET(page.imageUrl!!, imgHeader) + } + // private class Status : Filter.TriState("Completed") + private class TextField(name: String, val key: String) : Filter.Text(name) + private class SortBy : UriPartFilter("Sort by", arrayOf( + Pair("Relevance", ""), + Pair("Latest", "latest"), + Pair("A-Z", "alphabet"), + Pair("Rating", "rating"), + Pair("Trending", "trending"), + Pair("Most View", "views"), + Pair("New", "new-manga") + )) + private class Genre(name: String, val id: String = name) : Filter.TriState(name) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + private class Status(name: String, val id: String = name) : Filter.TriState(name) + private class StatusList(statuses: List) : Filter.Group("Status", statuses) + + override fun getFilterList() = FilterList( +// TextField("Judul", "title"), + TextField("Author", "author"), + TextField("Year", "release"), + SortBy(), + StatusList(getStatusList()), + GenreList(getGenreList()) + ) + private fun getStatusList() = listOf( + Status("Completed","end"), + Status("Ongoing","on-going"), + Status("Canceled","canceled"), + Status("Onhold","on-hold") + ) + private fun getGenreList() = listOf( + Genre("Action","action"), + Genre("Adventure","adventure"), + Genre("Bondage","bondage"), + Genre("Celebrity","celebrity"), + Genre("Comedy","comedy"), + Genre("Crime","crime"), + Genre("Drama","drama"), + Genre("Fantasy","fantasy"), + Genre("Gossip","gossip"), + Genre("Harem","harem"), + Genre("Historical","historical"), + Genre("Horror","horror"), + Genre("Mystery","mystery"), + Genre("Psychological","psychological"), + Genre("Romance","romance"), + Genre("School Life","school-life"), + Genre("Sci-fi","sci-fi"), + Genre("Slice of Life","slice-of-life"), + Genre("Smut","smut"), + Genre("Sports","sports"), + Genre("Supernatural","supernatural"), + Genre("Tragedy","tragedy"), + Genre("Voyeur","voyeur"), + Genre("Webtoon","webtoon") + ) + private open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + +} \ No newline at end of file