Reorganize project structure
This commit is contained in:
13
src/de/wiemanga/build.gradle
Normal file
13
src/de/wiemanga/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: WieManga'
|
||||
pkgNameSuffix = "de.wiemanga"
|
||||
extClass = '.WieManga'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,122 @@
|
||||
package eu.kanade.tachiyomi.extension.de.wiemanga
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class WieManga : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 10
|
||||
|
||||
override val name = "Wie Manga!"
|
||||
|
||||
override val baseUrl = "http://www.wiemanga.com"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaSelector() = ".booklist td > div"
|
||||
|
||||
override fun latestUpdatesSelector() = ".booklist td > div"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list/Hot-Book/", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list/New-Update/", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val image = element.select("dt img")
|
||||
val title = element.select("dd a:first-child")
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.setUrlWithoutDomain(title.attr("href"))
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = image.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = null
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
return GET("$baseUrl/search/?wd=$query", headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = ".searchresult td > div"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val image = element.select(".resultimg img")
|
||||
val title = element.select(".resultbookname")
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.setUrlWithoutDomain(title.attr("href"))
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = image.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = ".pagetor a.l"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
|
||||
val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
|
||||
manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
|
||||
manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
|
||||
manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
|
||||
|
||||
if (manga.author == "RSS")
|
||||
manga.author = null
|
||||
|
||||
if (manga.artist == "RSS")
|
||||
manga.artist = null
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select(".col1 a").first()
|
||||
val dateElement = element.select(".col3 a").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = urlElement.text()
|
||||
chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
document.select("select#page").first().select("option").forEach {
|
||||
pages.add(Page(pages.size, it.attr("value")))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
|
||||
|
||||
}
|
13
src/en/kissmanga/build.gradle
Normal file
13
src/en/kissmanga/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Kissmanga'
|
||||
pkgNameSuffix = "en.kissmanga"
|
||||
extClass = '.Kissmanga'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,197 @@
|
||||
package eu.kanade.tachiyomi.extension.en.kissmanga
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Kissmanga : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 4
|
||||
|
||||
override val name = "Kissmanga"
|
||||
|
||||
override val baseUrl = "http://kissmanga.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
override fun popularMangaSelector() = "table.listing tr:gt(1)"
|
||||
|
||||
override fun latestUpdatesSelector() = "table.listing tr:gt(1)"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/MangaList/MostPopular?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("td a:eq(0)").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "li > a:contains(› Next)"
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val form = FormBody.Builder().apply {
|
||||
add("mangaName", query)
|
||||
|
||||
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
|
||||
when (filter) {
|
||||
is Author -> add("authorArtist", filter.state)
|
||||
is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state])
|
||||
is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return POST("$baseUrl/AdvanceSearch", headers, form.build())
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = null
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select("div.barContent").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text()
|
||||
manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text()
|
||||
manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text()
|
||||
manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) }
|
||||
manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing") -> SManga.ONGOING
|
||||
status.contains("Completed") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "table.listing tr:gt(1)"
|
||||
|
||||
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("td:eq(1)").first()?.text()?.let {
|
||||
SimpleDateFormat("MM/dd/yyyy").parse(it).time
|
||||
} ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers)
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
//language=RegExp
|
||||
val p = Pattern.compile("""lstImages.push\("(.+?)"""")
|
||||
val m = p.matcher(response.body().string())
|
||||
|
||||
var i = 0
|
||||
while (m.find()) {
|
||||
pages.add(Page(i++, "", m.group(1)))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
override fun imageUrlRequest(page: Page) = GET(page.url)
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
private class Status : Filter.TriState("Completed")
|
||||
private class Author : Filter.Text("Author")
|
||||
private class Genre(name: String) : Filter.TriState(name)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
Author(),
|
||||
Status(),
|
||||
GenreList(getGenreList())
|
||||
)
|
||||
|
||||
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
|
||||
// on http://kissmanga.com/AdvanceSearch
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("4-Koma"),
|
||||
Genre("Action"),
|
||||
Genre("Adult"),
|
||||
Genre("Adventure"),
|
||||
Genre("Comedy"),
|
||||
Genre("Comic"),
|
||||
Genre("Cooking"),
|
||||
Genre("Doujinshi"),
|
||||
Genre("Drama"),
|
||||
Genre("Ecchi"),
|
||||
Genre("Fantasy"),
|
||||
Genre("Gender Bender"),
|
||||
Genre("Harem"),
|
||||
Genre("Historical"),
|
||||
Genre("Horror"),
|
||||
Genre("Josei"),
|
||||
Genre("Lolicon"),
|
||||
Genre("Manga"),
|
||||
Genre("Manhua"),
|
||||
Genre("Manhwa"),
|
||||
Genre("Martial Arts"),
|
||||
Genre("Mature"),
|
||||
Genre("Mecha"),
|
||||
Genre("Medical"),
|
||||
Genre("Music"),
|
||||
Genre("Mystery"),
|
||||
Genre("One shot"),
|
||||
Genre("Psychological"),
|
||||
Genre("Romance"),
|
||||
Genre("School Life"),
|
||||
Genre("Sci-fi"),
|
||||
Genre("Seinen"),
|
||||
Genre("Shotacon"),
|
||||
Genre("Shoujo"),
|
||||
Genre("Shoujo Ai"),
|
||||
Genre("Shounen"),
|
||||
Genre("Shounen Ai"),
|
||||
Genre("Slice of Life"),
|
||||
Genre("Smut"),
|
||||
Genre("Sports"),
|
||||
Genre("Supernatural"),
|
||||
Genre("Tragedy"),
|
||||
Genre("Webtoon"),
|
||||
Genre("Yaoi"),
|
||||
Genre("Yuri")
|
||||
)
|
||||
}
|
13
src/en/mangaeden/build.gradle
Normal file
13
src/en/mangaeden/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangaeden'
|
||||
pkgNameSuffix = "en.mangaeden"
|
||||
extClass = '.Mangaeden'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,203 @@
|
||||
package eu.kanade.tachiyomi.extension.en.mangaeden
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Mangaeden : ParsedHttpSource() {
|
||||
|
||||
override val name = "Manga Eden"
|
||||
|
||||
override val baseUrl = "http://www.mangaeden.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=3&page=$page", headers)
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=1&page=$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/en/en-directory/").newBuilder().addQueryParameter("title", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is StatusList -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("status", it) }
|
||||
is Types -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("type", it) }
|
||||
is GenreList -> filter.state
|
||||
.filter { !it.isIgnored() }
|
||||
.forEach { genre -> url.addQueryParameter(if (genre.isIncluded()) "categoriesInc" else "categoriesExcl", genre.id) }
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is OrderBy -> filter.state?.let {
|
||||
val sortId = it.index
|
||||
url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId")
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "table#mangaList > tbody > tr:has(td:gt(1))"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||
element.select("td > a").first()?.let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val infos = document.select("div.rightbox")
|
||||
|
||||
author = infos.select("a[href^=/en/en-directory/?author]").first()?.text()
|
||||
artist = infos.select("a[href^=/en/en-directory/?artist]").first()?.text()
|
||||
genre = infos.select("a[href^=/en/en-directory/?categoriesInc]").map { it.text() }.joinToString()
|
||||
description = document.select("h2#mangaDescription").text()
|
||||
status = parseStatus(infos.select("h4:containsOwn(Status)").first()?.nextSibling().toString())
|
||||
val img = infos.select("div.mangaImage2 > img").first()?.attr("src")
|
||||
if (!img.isNullOrBlank()) thumbnail_url = img.let { "http:$it" }
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing", true) -> SManga.ONGOING
|
||||
status.contains("Completed", true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div#leftContent > table > tbody > tr"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
val a = element.select("a[href^=/en/en-manga/]").first()
|
||||
|
||||
setUrlWithoutDomain(a.attr("href"))
|
||||
name = a?.select("b")?.first()?.text().orEmpty()
|
||||
date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long =
|
||||
if ("Today" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else if ("Yesterday" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else try {
|
||||
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||
document.select("option[value^=/en/en-manga/]").forEach {
|
||||
add(Page(size, "$baseUrl${it.attr("value")}"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = document.select("a#nextA.next > img").first()?.attr("src").let { "http:$it" }
|
||||
|
||||
private class NamedId(name: String, val id: Int) : Filter.CheckBox(name)
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class OrderBy : Filter.Sort("Order by", arrayOf("Manga title", "Views", "Chapters", "Latest chapter"),
|
||||
Filter.Sort.Selection(1, false))
|
||||
|
||||
private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses)
|
||||
private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Author", "author"),
|
||||
TextField("Artist", "artist"),
|
||||
OrderBy(),
|
||||
Types(types()),
|
||||
StatusList(statuses()),
|
||||
GenreList(genres())
|
||||
)
|
||||
|
||||
private fun types() = listOf(
|
||||
NamedId("Japanese Manga", 0),
|
||||
NamedId("Korean Manhwa", 1),
|
||||
NamedId("Chinese Manhua", 2),
|
||||
NamedId("Comic", 3),
|
||||
NamedId("Doujinshi", 4)
|
||||
)
|
||||
|
||||
private fun statuses() = listOf(
|
||||
NamedId("Ongoing", 1),
|
||||
NamedId("Completed", 2),
|
||||
NamedId("Suspended", 0)
|
||||
)
|
||||
|
||||
private fun genres() = listOf(
|
||||
Genre("Action", "4e70e91bc092255ef70016f8"),
|
||||
Genre("Adult", "4e70e92fc092255ef7001b94"),
|
||||
Genre("Adventure", "4e70e918c092255ef700168e"),
|
||||
Genre("Comedy", "4e70e918c092255ef7001675"),
|
||||
Genre("Doujinshi", "4e70e928c092255ef7001a0a"),
|
||||
Genre("Drama", "4e70e918c092255ef7001693"),
|
||||
Genre("Ecchi", "4e70e91ec092255ef700175e"),
|
||||
Genre("Fantasy", "4e70e918c092255ef7001676"),
|
||||
Genre("Gender Bender", "4e70e921c092255ef700184b"),
|
||||
Genre("Harem", "4e70e91fc092255ef7001783"),
|
||||
Genre("Historical", "4e70e91ac092255ef70016d8"),
|
||||
Genre("Horror", "4e70e919c092255ef70016a8"),
|
||||
Genre("Josei", "4e70e920c092255ef70017de"),
|
||||
Genre("Martial Arts", "4e70e923c092255ef70018d0"),
|
||||
Genre("Mature", "4e70e91bc092255ef7001705"),
|
||||
Genre("Mecha", "4e70e922c092255ef7001877"),
|
||||
Genre("Mystery", "4e70e918c092255ef7001681"),
|
||||
Genre("One Shot", "4e70e91dc092255ef7001747"),
|
||||
Genre("Psychological", "4e70e919c092255ef70016a9"),
|
||||
Genre("Romance", "4e70e918c092255ef7001677"),
|
||||
Genre("School Life", "4e70e918c092255ef7001688"),
|
||||
Genre("Sci-fi", "4e70e91bc092255ef7001706"),
|
||||
Genre("Seinen", "4e70e918c092255ef700168b"),
|
||||
Genre("Shoujo", "4e70e918c092255ef7001667"),
|
||||
Genre("Shounen", "4e70e918c092255ef700166f"),
|
||||
Genre("Slice of Life", "4e70e918c092255ef700167e"),
|
||||
Genre("Smut", "4e70e922c092255ef700185a"),
|
||||
Genre("Sports", "4e70e91dc092255ef700172e"),
|
||||
Genre("Supernatural", "4e70e918c092255ef700166a"),
|
||||
Genre("Tragedy", "4e70e918c092255ef7001672"),
|
||||
Genre("Webtoons", "4e70ea70c092255ef7006d9c"),
|
||||
Genre("Yaoi", "4e70e91ac092255ef70016e5"),
|
||||
Genre("Yuri", "4e70e92ac092255ef7001a57")
|
||||
)
|
||||
}
|
13
src/en/mangafox/build.gradle
Normal file
13
src/en/mangafox/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangafox'
|
||||
pkgNameSuffix = "en.mangafox"
|
||||
extClass = '.Mangafox'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,223 @@
|
||||
package eu.kanade.tachiyomi.extension.en.mangafox
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Mangafox : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 3
|
||||
|
||||
override val name = "Mangafox"
|
||||
|
||||
override val baseUrl = "http://mangafox.me"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaSelector() = "div#mangalist > ul.list > li"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
val pageStr = if (page != 1) "$page.htm" else ""
|
||||
return GET("$baseUrl/directory/$pageStr", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = "div#mangalist > ul.list > li"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val pageStr = if (page != 1) "$page.htm" else ""
|
||||
return GET("$baseUrl/directory/$pageStr?latest")
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a.title").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is Status -> url.addQueryParameter(filter.id, filter.state.toString())
|
||||
is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) }
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
|
||||
is OrderBy -> {
|
||||
url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index])
|
||||
url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za")
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "div#mangalist > ul.list > li"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a.title").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select("div#title").first()
|
||||
val rowElement = infoElement.select("table > tbody > tr:eq(1)").first()
|
||||
val sideInfoElement = document.select("#series_info").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = rowElement.select("td:eq(1)").first()?.text()
|
||||
manga.artist = rowElement.select("td:eq(2)").first()?.text()
|
||||
manga.genre = rowElement.select("td:eq(3)").first()?.text()
|
||||
manga.description = infoElement.select("p.summary").first()?.text()
|
||||
manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) }
|
||||
manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing") -> SManga.ONGOING
|
||||
status.contains("Completed") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div#chapters li div"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select("a.tips").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = urlElement.text()
|
||||
chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return if ("Today" in date || " ago" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else if ("Yesterday" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else {
|
||||
try {
|
||||
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val url = document.baseUri().substringBeforeLast('/')
|
||||
|
||||
val pages = mutableListOf<Page>()
|
||||
document.select("select.m").first()?.select("option:not([value=0])")?.forEach {
|
||||
pages.add(Page(pages.size, "$url/${it.attr("value")}.html"))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
val url = document.getElementById("image").attr("src")
|
||||
return if ("compressed?token=" !in url) {
|
||||
url
|
||||
} else {
|
||||
"http://mangafox.me/media/logo.png"
|
||||
}
|
||||
}
|
||||
|
||||
private class Status(val id: String = "is_completed") : Filter.TriState("Completed")
|
||||
private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
|
||||
private class OrderBy : Filter.Sort("Order by",
|
||||
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
||||
Selection(2, false))
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Author", "author"),
|
||||
TextField("Artist", "artist"),
|
||||
Type(),
|
||||
Status(),
|
||||
OrderBy(),
|
||||
GenreList(getGenreList())
|
||||
)
|
||||
|
||||
// $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n')
|
||||
// on http://mangafox.me/search.php
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action"),
|
||||
Genre("Adult"),
|
||||
Genre("Adventure"),
|
||||
Genre("Comedy"),
|
||||
Genre("Doujinshi"),
|
||||
Genre("Drama"),
|
||||
Genre("Ecchi"),
|
||||
Genre("Fantasy"),
|
||||
Genre("Gender Bender"),
|
||||
Genre("Harem"),
|
||||
Genre("Historical"),
|
||||
Genre("Horror"),
|
||||
Genre("Josei"),
|
||||
Genre("Martial Arts"),
|
||||
Genre("Mature"),
|
||||
Genre("Mecha"),
|
||||
Genre("Mystery"),
|
||||
Genre("One Shot"),
|
||||
Genre("Psychological"),
|
||||
Genre("Romance"),
|
||||
Genre("School Life"),
|
||||
Genre("Sci-fi"),
|
||||
Genre("Seinen"),
|
||||
Genre("Shoujo"),
|
||||
Genre("Shoujo Ai"),
|
||||
Genre("Shounen"),
|
||||
Genre("Shounen Ai"),
|
||||
Genre("Slice of Life"),
|
||||
Genre("Smut"),
|
||||
Genre("Sports"),
|
||||
Genre("Supernatural"),
|
||||
Genre("Tragedy"),
|
||||
Genre("Webtoons"),
|
||||
Genre("Yaoi"),
|
||||
Genre("Yuri")
|
||||
)
|
||||
|
||||
}
|
13
src/en/mangahere/build.gradle
Normal file
13
src/en/mangahere/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangahere'
|
||||
pkgNameSuffix = "en.mangahere"
|
||||
extClass = '.Mangahere'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,220 @@
|
||||
package eu.kanade.tachiyomi.extension.en.mangahere
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Mangahere : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 2
|
||||
|
||||
override val name = "Mangahere"
|
||||
|
||||
override val baseUrl = "http://www.mangahere.co"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaSelector() = "div.directory_list > ul > li"
|
||||
|
||||
override fun latestUpdatesSelector() = "div.directory_list > ul > li"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/directory/$page.htm?views.za", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers)
|
||||
}
|
||||
|
||||
private fun mangaFromElement(query: String, element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select(query).first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return mangaFromElement("div.title > a", element)
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "div.next-page > a.next"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "div.next-page > a.next"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state])
|
||||
is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) }
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state])
|
||||
is OrderBy -> {
|
||||
url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index])
|
||||
url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za")
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "div.result_search > dl:has(dt)"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return mangaFromElement("a.manga_info", element)
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "div.next-page > a.next"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val detailElement = document.select(".manga_detail_top").first()
|
||||
val infoElement = detailElement.select(".detail_topText").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("a[href^=http://www.mangahere.co/author/]").first()?.text()
|
||||
manga.artist = infoElement.select("a[href^=http://www.mangahere.co/artist/]").first()?.text()
|
||||
manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):")
|
||||
manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less")
|
||||
manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) }
|
||||
manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing") -> SManga.ONGOING
|
||||
status.contains("Completed") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = ".detail_list > ul:not([class]) > li"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val parentEl = element.select("span.left").first()
|
||||
|
||||
val urlElement = parentEl.select("a").first()
|
||||
|
||||
var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: ""
|
||||
if (volume.length > 0) {
|
||||
volume = " - " + volume
|
||||
}
|
||||
|
||||
var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: ""
|
||||
if (title.length > 0) {
|
||||
title = " - " + title
|
||||
}
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = urlElement.text() + volume + title
|
||||
chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return if ("Today" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else if ("Yesterday" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else {
|
||||
try {
|
||||
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
document.select("select.wid60").first()?.getElementsByTag("option")?.forEach {
|
||||
pages.add(Page(pages.size, it.attr("value")))
|
||||
}
|
||||
pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src")
|
||||
|
||||
private class Status : Filter.TriState("Completed")
|
||||
private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)"))
|
||||
private class OrderBy : Filter.Sort("Order by",
|
||||
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
|
||||
Selection(2, false))
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Author", "author"),
|
||||
TextField("Artist", "artist"),
|
||||
Type(),
|
||||
Status(),
|
||||
OrderBy(),
|
||||
GenreList(getGenreList())
|
||||
)
|
||||
|
||||
// [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n')
|
||||
// http://www.mangahere.co/advsearch.htm
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action"),
|
||||
Genre("Adventure"),
|
||||
Genre("Comedy"),
|
||||
Genre("Doujinshi"),
|
||||
Genre("Drama"),
|
||||
Genre("Ecchi"),
|
||||
Genre("Fantasy"),
|
||||
Genre("Gender Bender"),
|
||||
Genre("Harem"),
|
||||
Genre("Historical"),
|
||||
Genre("Horror"),
|
||||
Genre("Josei"),
|
||||
Genre("Martial Arts"),
|
||||
Genre("Mature"),
|
||||
Genre("Mecha"),
|
||||
Genre("Mystery"),
|
||||
Genre("One Shot"),
|
||||
Genre("Psychological"),
|
||||
Genre("Romance"),
|
||||
Genre("School Life"),
|
||||
Genre("Sci-fi"),
|
||||
Genre("Seinen"),
|
||||
Genre("Shoujo"),
|
||||
Genre("Shoujo Ai"),
|
||||
Genre("Shounen"),
|
||||
Genre("Shounen Ai"),
|
||||
Genre("Slice of Life"),
|
||||
Genre("Sports"),
|
||||
Genre("Supernatural"),
|
||||
Genre("Tragedy"),
|
||||
Genre("Yaoi"),
|
||||
Genre("Yuri")
|
||||
)
|
||||
|
||||
}
|
13
src/en/mangasee/build.gradle
Normal file
13
src/en/mangasee/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangasee'
|
||||
pkgNameSuffix = "en.mangasee"
|
||||
extClass = '.Mangasee'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,243 @@
|
||||
package eu.kanade.tachiyomi.extension.en.mangasee
|
||||
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Mangasee : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 9
|
||||
|
||||
override val name = "Mangasee"
|
||||
|
||||
override val baseUrl = "http://mangaseeonline.net"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?")
|
||||
|
||||
private val indexPattern = Pattern.compile("-index-(.*?)-")
|
||||
|
||||
override fun popularMangaSelector() = "div.requested > div.row"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending")
|
||||
return POST(requestUrl, headers, body.build())
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a.resultLink").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "button.requestMore"
|
||||
|
||||
override fun searchMangaSelector() = "div.requested > div.row"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder()
|
||||
if (!query.isEmpty()) url.addQueryParameter("keyword", query)
|
||||
val genres = mutableListOf<String>()
|
||||
val genresNo = mutableListOf<String>()
|
||||
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
|
||||
when (filter) {
|
||||
is Sort -> {
|
||||
if (filter.state?.index != 0)
|
||||
url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity")
|
||||
if (filter.state?.ascending != true)
|
||||
url.addQueryParameter("sortOrder", "descending")
|
||||
}
|
||||
is SelectField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state])
|
||||
is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
|
||||
is GenreList -> filter.state.forEach { genre ->
|
||||
when (genre.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> genres.add(genre.name)
|
||||
Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(","))
|
||||
if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(","))
|
||||
|
||||
val (body, requestUrl) = convertQueryToPost(page, url.toString())
|
||||
return POST(requestUrl, headers, body.build())
|
||||
}
|
||||
|
||||
private fun convertQueryToPost(page: Int, url: String): Pair<FormBody.Builder, String> {
|
||||
val url = HttpUrl.parse(url)
|
||||
val body = FormBody.Builder().add("page", page.toString())
|
||||
for (i in 0..url.querySize() - 1) {
|
||||
body.add(url.queryParameterName(i), url.queryParameterValue(i))
|
||||
}
|
||||
val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath()
|
||||
return Pair(body, requestUrl)
|
||||
}
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a.resultLink").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "button.requestMore"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val detailElement = document.select("div.well > div.row").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text()
|
||||
manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString()
|
||||
manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text()
|
||||
manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) }
|
||||
manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing (Scan)") -> SManga.ONGOING
|
||||
status.contains("Complete (Scan)") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.chapter-list > a"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select("a").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: ""
|
||||
chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
private fun parseChapterDate(dateAsString: String): Long {
|
||||
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val fullUrl = document.baseUri()
|
||||
val url = fullUrl.substringBeforeLast('/')
|
||||
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
val series = document.select("input.IndexName").first().attr("value")
|
||||
val chapter = document.select("span.CurChapter").first().text()
|
||||
var index = ""
|
||||
|
||||
val m = indexPattern.matcher(fullUrl)
|
||||
if (m.find()) {
|
||||
val indexNumber = m.group(1)
|
||||
index = "-index-$indexNumber"
|
||||
}
|
||||
|
||||
document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach {
|
||||
pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html"))
|
||||
}
|
||||
pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src")
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "button.requestMore"
|
||||
|
||||
override fun latestUpdatesSelector(): String = "a.latestSeries"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val url = "http://mangaseeonline.net/home/latest.request.php"
|
||||
val (body, requestUrl) = convertQueryToPost(page, url)
|
||||
return POST(requestUrl, headers, body.build())
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a.latestSeries").first().let {
|
||||
val chapterUrl = it.attr("href")
|
||||
val indexOfMangaUrl = chapterUrl.indexOf("-chapter-")
|
||||
val indexOfLastPath = chapterUrl.lastIndexOf("/")
|
||||
val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl)
|
||||
val defaultText = it.select("p.clamp2").text()
|
||||
val m = recentUpdatesPattern.matcher(defaultText)
|
||||
val title = if (m.matches()) m.group(1) else defaultText
|
||||
manga.setUrlWithoutDomain("/manga" + mangaUrl)
|
||||
manga.title = title
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
|
||||
private class Genre(name: String) : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Years", "year"),
|
||||
TextField("Author", "author"),
|
||||
SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
|
||||
SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
|
||||
SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
|
||||
Sort(),
|
||||
GenreList(getGenreList())
|
||||
)
|
||||
|
||||
// [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n')
|
||||
// http://mangasee.co/advanced-search/
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action"),
|
||||
Genre("Adult"),
|
||||
Genre("Adventure"),
|
||||
Genre("Comedy"),
|
||||
Genre("Doujinshi"),
|
||||
Genre("Drama"),
|
||||
Genre("Ecchi"),
|
||||
Genre("Fantasy"),
|
||||
Genre("Gender Bender"),
|
||||
Genre("Harem"),
|
||||
Genre("Hentai"),
|
||||
Genre("Historical"),
|
||||
Genre("Horror"),
|
||||
Genre("Josei"),
|
||||
Genre("Lolicon"),
|
||||
Genre("Martial Arts"),
|
||||
Genre("Mature"),
|
||||
Genre("Mecha"),
|
||||
Genre("Mystery"),
|
||||
Genre("Psychological"),
|
||||
Genre("Romance"),
|
||||
Genre("School Life"),
|
||||
Genre("Sci-fi"),
|
||||
Genre("Seinen"),
|
||||
Genre("Shotacon"),
|
||||
Genre("Shoujo"),
|
||||
Genre("Shoujo Ai"),
|
||||
Genre("Shounen"),
|
||||
Genre("Shounen Ai"),
|
||||
Genre("Slice of Life"),
|
||||
Genre("Smut"),
|
||||
Genre("Sports"),
|
||||
Genre("Supernatural"),
|
||||
Genre("Tragedy"),
|
||||
Genre("Yaoi"),
|
||||
Genre("Yuri")
|
||||
)
|
||||
|
||||
}
|
13
src/en/perveden/build.gradle
Normal file
13
src/en/perveden/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Perveden'
|
||||
pkgNameSuffix = "en.perveden"
|
||||
extClass = '.Perveden'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
File diff suppressed because it is too large
Load Diff
13
src/en/readmangatoday/build.gradle
Normal file
13
src/en/readmangatoday/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: ReadMangaToday'
|
||||
pkgNameSuffix = "en.readmangatoday"
|
||||
extClass = '.Readmangatoday'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,219 @@
|
||||
package eu.kanade.tachiyomi.extension.en.readmangatoday
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.*
|
||||
|
||||
class Readmangatoday : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 8
|
||||
|
||||
override val name = "ReadMangaToday"
|
||||
|
||||
override val baseUrl = "http://www.readmanga.today"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient get() = network.cloudflareClient
|
||||
|
||||
/**
|
||||
* Search only returns data with this set
|
||||
*/
|
||||
override fun headersBuilder() = Headers.Builder().apply {
|
||||
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
|
||||
add("X-Requested-With", "XMLHttpRequest")
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/hot-manga/$page", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/latest-releases/$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box"
|
||||
|
||||
override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("div.title > h2 > a").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.attr("title")
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val builder = okhttp3.FormBody.Builder()
|
||||
builder.add("manga-name", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is TextField -> builder.add(filter.key, filter.state)
|
||||
is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state])
|
||||
is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state])
|
||||
is GenreList -> filter.state.forEach { genre ->
|
||||
when (genre.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString())
|
||||
Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return POST("$baseUrl/service/advanced_search", headers, builder.build())
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "div.style-list > div.box"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("div.title > h2 > a").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.attr("title")
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "div.next-page > a.next"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val detailElement = document.select("div.movie-meta").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = document.select("ul.cast-list li.director > ul a").first()?.text()
|
||||
manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text()
|
||||
manga.genre = detailElement.select("dl.dl-horizontal > dd:eq(5)").first()?.text()
|
||||
manga.description = detailElement.select("li.movie-detail").first()?.text()
|
||||
manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) }
|
||||
manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Ongoing") -> SManga.ONGOING
|
||||
status.contains("Completed") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "ul.chp_lst > li"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select("a").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
||||
chapter.name = urlElement.select("span.val").text()
|
||||
chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
val dateWords: List<String> = date.split(" ")
|
||||
|
||||
if (dateWords.size == 3) {
|
||||
val timeAgo = Integer.parseInt(dateWords[0])
|
||||
val date: Calendar = Calendar.getInstance()
|
||||
|
||||
if (dateWords[1].contains("Minute")) {
|
||||
date.add(Calendar.MINUTE, -timeAgo)
|
||||
} else if (dateWords[1].contains("Hour")) {
|
||||
date.add(Calendar.HOUR_OF_DAY, -timeAgo)
|
||||
} else if (dateWords[1].contains("Day")) {
|
||||
date.add(Calendar.DAY_OF_YEAR, -timeAgo)
|
||||
} else if (dateWords[1].contains("Week")) {
|
||||
date.add(Calendar.WEEK_OF_YEAR, -timeAgo)
|
||||
} else if (dateWords[1].contains("Month")) {
|
||||
date.add(Calendar.MONTH, -timeAgo)
|
||||
} else if (dateWords[1].contains("Year")) {
|
||||
date.add(Calendar.YEAR, -timeAgo)
|
||||
}
|
||||
|
||||
return date.timeInMillis
|
||||
}
|
||||
|
||||
return 0L
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach {
|
||||
pages.add(Page(pages.size, it.attr("value")))
|
||||
}
|
||||
pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src")
|
||||
|
||||
private class Status : Filter.TriState("Completed")
|
||||
private class Genre(name: String, val id: Int) : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class Type : Filter.Select<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Author", "author-name"),
|
||||
TextField("Artist", "artist-name"),
|
||||
Type(),
|
||||
Status(),
|
||||
GenreList(getGenreList())
|
||||
)
|
||||
|
||||
// [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n')
|
||||
// http://www.readmanga.today/advanced-search
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action", 2),
|
||||
Genre("Adventure", 4),
|
||||
Genre("Comedy", 5),
|
||||
Genre("Doujinshi", 6),
|
||||
Genre("Drama", 7),
|
||||
Genre("Ecchi", 8),
|
||||
Genre("Fantasy", 9),
|
||||
Genre("Gender Bender", 10),
|
||||
Genre("Harem", 11),
|
||||
Genre("Historical", 12),
|
||||
Genre("Horror", 13),
|
||||
Genre("Josei", 14),
|
||||
Genre("Lolicon", 15),
|
||||
Genre("Martial Arts", 16),
|
||||
Genre("Mature", 17),
|
||||
Genre("Mecha", 18),
|
||||
Genre("Mystery", 19),
|
||||
Genre("One shot", 20),
|
||||
Genre("Psychological", 21),
|
||||
Genre("Romance", 22),
|
||||
Genre("School Life", 23),
|
||||
Genre("Sci-fi", 24),
|
||||
Genre("Seinen", 25),
|
||||
Genre("Shotacon", 26),
|
||||
Genre("Shoujo", 27),
|
||||
Genre("Shoujo Ai", 28),
|
||||
Genre("Shounen", 29),
|
||||
Genre("Shounen Ai", 30),
|
||||
Genre("Slice of Life", 31),
|
||||
Genre("Smut", 32),
|
||||
Genre("Sports", 33),
|
||||
Genre("Supernatural", 34),
|
||||
Genre("Tragedy", 35),
|
||||
Genre("Yaoi", 36),
|
||||
Genre("Yuri", 37)
|
||||
)
|
||||
}
|
13
src/it/mangaeden/build.gradle
Normal file
13
src/it/mangaeden/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangaeden'
|
||||
pkgNameSuffix = "it.mangaeden"
|
||||
extClass = '.Mangaeden'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,204 @@
|
||||
package eu.kanade.tachiyomi.extension.it.mangaeden
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Mangaeden : ParsedHttpSource() {
|
||||
|
||||
override val name = "Manga Eden"
|
||||
|
||||
override val baseUrl = "http://www.mangaeden.com"
|
||||
|
||||
override val lang = "it"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?order=3&page=$page", headers)
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?page=$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/it/it-directory/").newBuilder().addQueryParameter("title", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is StatusList -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("status", it) }
|
||||
is Types -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("type", it) }
|
||||
is GenreList -> filter.state
|
||||
.filterNot { it.isIgnored() }
|
||||
.forEach { genre -> url.addQueryParameter(if (genre.isIncluded()) "categoriesInc" else "categoriesExcl", genre.id) }
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is OrderBy -> filter.state?.let {
|
||||
val sortId = it.index
|
||||
url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId")
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "table#mangaList > tbody > tr:has(td:gt(1))"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||
element.select("td > a").first()?.let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val infos = document.select("div.rightbox")
|
||||
|
||||
author = infos.select("a[href^=/it/it-directory/?author]").first()?.text()
|
||||
artist = infos.select("a[href^=/it/it-directory/?artist]").first()?.text()
|
||||
genre = infos.select("a[href^=/it/it-directory/?categoriesInc]").map { it.text() }.joinToString()
|
||||
description = document.select("h2#mangaDescription").text()
|
||||
status = parseStatus(infos.select("h4:containsOwn(Stato)").first()?.nextSibling().toString())
|
||||
val img = infos.select("div.mangaImage2 > img").first()?.attr("src")
|
||||
if (!img.isNullOrBlank()) thumbnail_url = img.let { "http:$it" }
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("In Corso", true) -> SManga.ONGOING
|
||||
status.contains("Completato", true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div#leftContent > table > tbody > tr"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
val a = element.select("a[href^=/it/it-manga/]").first()
|
||||
|
||||
setUrlWithoutDomain(a?.attr("href").orEmpty())
|
||||
name = a?.select("b")?.first()?.text().orEmpty()
|
||||
date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long =
|
||||
if ("Oggi" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else if ("Ieri" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else try {
|
||||
SimpleDateFormat("d MMM yyyy", Locale.ITALIAN).parse(date).time
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||
document.select("option[value^=/it/it-manga/]").forEach {
|
||||
add(Page(size, "$baseUrl${it.attr("value")}"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = document.select("a#nextA.next > img").first()?.attr("src").let { "http:$it" }
|
||||
|
||||
private class NamedId(name: String, val id: Int) : Filter.CheckBox(name)
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class OrderBy : Filter.Sort("Ordina per", arrayOf("Titolo manga", "Visite", "Capitoli", "Ultimo capitolo"),
|
||||
Filter.Sort.Selection(1, false))
|
||||
|
||||
private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses)
|
||||
private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Autore", "author"),
|
||||
TextField("Artista", "artist"),
|
||||
OrderBy(),
|
||||
Types(types()),
|
||||
StatusList(statuses()),
|
||||
GenreList(genres())
|
||||
)
|
||||
|
||||
private fun types() = listOf(
|
||||
NamedId("Japanese Manga", 0),
|
||||
NamedId("Korean Manhwa", 1),
|
||||
NamedId("Chinese Manhua", 2),
|
||||
NamedId("Comic", 3),
|
||||
NamedId("Doujinshi", 4)
|
||||
)
|
||||
|
||||
private fun statuses() = listOf(
|
||||
NamedId("In corso", 1),
|
||||
NamedId("Completato", 2),
|
||||
NamedId("Sospeso", 0)
|
||||
)
|
||||
|
||||
private fun genres() = listOf(
|
||||
Genre("Avventura", "4e70ea8cc092255ef70073d3"),
|
||||
Genre("Azione", "4e70ea8cc092255ef70073c3"),
|
||||
Genre("Bara", "4e70ea90c092255ef70074b7"),
|
||||
Genre("Commedia", "4e70ea8cc092255ef70073d0"),
|
||||
Genre("Demenziale", "4e70ea8fc092255ef7007475"),
|
||||
Genre("Dounshinji", "4e70ea93c092255ef70074e4"),
|
||||
Genre("Drama", "4e70ea8cc092255ef70073f9"),
|
||||
Genre("Ecchi", "4e70ea8cc092255ef70073cd"),
|
||||
Genre("Fantasy", "4e70ea8cc092255ef70073c4"),
|
||||
Genre("Harem", "4e70ea8cc092255ef70073d1"),
|
||||
Genre("Hentai", "4e70ea90c092255ef700749a"),
|
||||
Genre("Horror", "4e70ea8cc092255ef70073ce"),
|
||||
Genre("Josei", "4e70ea90c092255ef70074bd"),
|
||||
Genre("Magico", "4e70ea93c092255ef700751b"),
|
||||
Genre("Mecha", "4e70ea8cc092255ef70073ef"),
|
||||
Genre("Misteri", "4e70ea8dc092255ef700740a"),
|
||||
Genre("Musica", "4e70ea8fc092255ef7007456"),
|
||||
Genre("Psicologico", "4e70ea8ec092255ef7007439"),
|
||||
Genre("Raccolta", "4e70ea90c092255ef70074ae"),
|
||||
Genre("Romantico", "4e70ea8cc092255ef70073c5"),
|
||||
Genre("Sci-Fi", "4e70ea8cc092255ef70073e4"),
|
||||
Genre("Scolastico", "4e70ea8cc092255ef70073e5"),
|
||||
Genre("Seinen", "4e70ea8cc092255ef70073ea"),
|
||||
Genre("Sentimentale", "4e70ea8dc092255ef7007432"),
|
||||
Genre("Shota", "4e70ea90c092255ef70074b8"),
|
||||
Genre("Shoujo", "4e70ea8dc092255ef7007421"),
|
||||
Genre("Shounen", "4e70ea8cc092255ef70073c6"),
|
||||
Genre("Sovrannaturale", "4e70ea8cc092255ef70073c7"),
|
||||
Genre("Splatter", "4e70ea99c092255ef70075a3"),
|
||||
Genre("Sportivo", "4e70ea8dc092255ef7007426"),
|
||||
Genre("Storico", "4e70ea8cc092255ef70073f4"),
|
||||
Genre("Vita Quotidiana", "4e70ea8ec092255ef700743f"),
|
||||
Genre("Yaoi", "4e70ea8cc092255ef70073de"),
|
||||
Genre("Yuri", "4e70ea9ac092255ef70075d1")
|
||||
)
|
||||
}
|
13
src/it/perveden/build.gradle
Normal file
13
src/it/perveden/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Perveden'
|
||||
pkgNameSuffix = "it.perveden"
|
||||
extClass = '.Perveden'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,400 @@
|
||||
package eu.kanade.tachiyomi.extension.it.perveden
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Perveden : ParsedHttpSource() {
|
||||
|
||||
override val name = "PervEden"
|
||||
|
||||
override val baseUrl = "http://www.perveden.com"
|
||||
|
||||
override val lang = "it"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?order=3&page=$page", headers)
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?page=$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = HttpUrl.parse("$baseUrl/it/it-directory/").newBuilder().addQueryParameter("title", query)
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is StatusList -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("status", it) }
|
||||
is Types -> filter.state
|
||||
.filter { it.state }
|
||||
.map { it.id.toString() }
|
||||
.forEach { url.addQueryParameter("type", it) }
|
||||
is TextField -> url.addQueryParameter(filter.key, filter.state)
|
||||
is OrderBy -> filter.state?.let {
|
||||
val sortId = it.index
|
||||
url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId")
|
||||
}
|
||||
is GenreField -> filter.state.toLowerCase(Locale.ENGLISH).split(',', ';').forEach {
|
||||
val id = genres[it.trim()]
|
||||
if (id != null) url.addQueryParameter(filter.key, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "table#mangaList > tbody > tr:has(td:gt(1))"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||
element.select("td > a").first()?.let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a:has(span.next)"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val infos = document.select("div.rightbox")
|
||||
|
||||
author = infos.select("a[href^=/it/it-directory/?author]").first()?.text()
|
||||
artist = infos.select("a[href^=/it/it-directory/?artist]").first()?.text()
|
||||
genre = infos.select("a[href^=/it/it-directory/?categoriesInc]").map { it.text() }.joinToString()
|
||||
description = document.select("h2#mangaDescription").text()
|
||||
status = parseStatus(infos.select("h4:containsOwn(Stato)").first()?.nextSibling().toString())
|
||||
val img = infos.select("div.mangaImage2 > img").first()?.attr("src")
|
||||
if (!img.isNullOrBlank()) thumbnail_url = img.let { "http:$it" }
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("In Corso", true) -> SManga.ONGOING
|
||||
status.contains("Completato", true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div#leftContent > table > tbody > tr"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
val a = element.select("a[href^=/it/it-manga/]").first()
|
||||
|
||||
setUrlWithoutDomain(a?.attr("href").orEmpty())
|
||||
name = a?.select("b")?.first()?.text().orEmpty()
|
||||
date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long =
|
||||
if ("Oggi" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else if ("Ieri" in date) {
|
||||
Calendar.getInstance().apply {
|
||||
add(Calendar.DATE, -1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.timeInMillis
|
||||
} else try {
|
||||
SimpleDateFormat("d MMM yyyy", Locale.ITALIAN).parse(date).time
|
||||
} catch (e: ParseException) {
|
||||
0L
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||
document.select("option[value^=/it/it-manga/]").forEach {
|
||||
add(Page(size, "$baseUrl${it.attr("value")}"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = document.select("a#nextA.next > img").first()?.attr("src").let { "http:$it" }
|
||||
|
||||
private class NamedId(name: String, val id: Int) : Filter.CheckBox(name)
|
||||
private class TextField(name: String, val key: String) : Filter.Text(name)
|
||||
private class GenreField(name: String, val key: String) : Filter.Text(name)
|
||||
private class OrderBy : Filter.Sort("Ordina per", arrayOf("Titolo manga", "Visite", "Capitoli", "Ultimo capitolo"),
|
||||
Filter.Sort.Selection(1, false))
|
||||
|
||||
private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses)
|
||||
private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types)
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
TextField("Autore", "author"),
|
||||
TextField("Artista", "artist"),
|
||||
GenreField("Generi inclusi", "categoriesInc"),
|
||||
GenreField("Generi esclusi", "categoriesExcl"),
|
||||
OrderBy(),
|
||||
Types(types()),
|
||||
StatusList(statuses())
|
||||
)
|
||||
|
||||
private fun types() = listOf(
|
||||
NamedId("Japanese Manga", 0),
|
||||
NamedId("Korean Manhwa", 1),
|
||||
NamedId("Chinese Manhua", 2),
|
||||
NamedId("Comic", 3),
|
||||
NamedId("Doujinshi", 4)
|
||||
)
|
||||
|
||||
private fun statuses() = listOf(
|
||||
NamedId("In corso", 1),
|
||||
NamedId("Completato", 2),
|
||||
NamedId("Sospeso", 0)
|
||||
)
|
||||
|
||||
private val genres = mapOf(
|
||||
Pair("commedia", "4e70ea9ac092255ef70075d8"),
|
||||
Pair("ecchi", "4e70ea9ac092255ef70075d9"),
|
||||
Pair("age progression", "5782b043719a16947390104a"),
|
||||
Pair("ahegao", "577e6f90719a168e7d256a3f"),
|
||||
Pair("anal", "577e6f90719a168e7d256a3b"),
|
||||
Pair("angel", "577e724a719a168ef96a74d6"),
|
||||
Pair("apron", "577e720a719a166f4719a7be"),
|
||||
Pair("armpit licking", "577e71db719a166f4719a3e7"),
|
||||
Pair("assjob", "58474a08719a1668eeeea29b"),
|
||||
Pair("aunt", "577e6f8d719a168e7d256a20"),
|
||||
Pair("bbw", "5782ae42719a1675f68a6e29"),
|
||||
Pair("bdsm", "577e723d719a168ef96a7416"),
|
||||
Pair("bestiality", "57ad8919719a1629a0a327cf"),
|
||||
Pair("big areolae", "577e7226719a166f4719a9d0"),
|
||||
Pair("big ass", "577e6f8d719a168e7d256a21"),
|
||||
Pair("big balls", "577e7267719a168ef96a76ee"),
|
||||
Pair("big breasts", "577e6f8d719a168e7d256a1c"),
|
||||
Pair("big clit", "57ef0396719a163dffb8fdff"),
|
||||
Pair("big nipples", "5782ae42719a1675f68a6e2a"),
|
||||
Pair("big penis", "577e7267719a168ef96a76ef"),
|
||||
Pair("bike shorts", "577e7210719a166f4719a820"),
|
||||
Pair("bikini", "577e6f91719a168e7d256a77"),
|
||||
Pair("birth", "577e7273719a168ef96a77cf"),
|
||||
Pair("blackmail", "577e6f91719a168e7d256a78"),
|
||||
Pair("blindfold", "577e7208719a166f4719a78d"),
|
||||
Pair("blood", "577e7295719a168ef96a79e6"),
|
||||
Pair("bloomers", "5782b051719a1694739010ee"),
|
||||
Pair("blowjob", "577e6f8d719a168e7d256a22"),
|
||||
Pair("blowjob face", "577e71eb719a166f4719a544"),
|
||||
Pair("body modification", "577e6f93719a168e7d256a8e"),
|
||||
Pair("bodystocking", "5782b05c719a169473901151"),
|
||||
Pair("bodysuit", "577e6f90719a168e7d256a42"),
|
||||
Pair("bondage", "577e6f90719a168e7d256a45"),
|
||||
Pair("breast expansion", "577e71c3719a166f4719a235"),
|
||||
Pair("bukkake", "577e7210719a166f4719a821"),
|
||||
Pair("bunny girl", "577e7224719a166f4719a9b9"),
|
||||
Pair("business suit", "577e71e5719a166f4719a4b2"),
|
||||
Pair("catgirl", "577e71d5719a166f4719a366"),
|
||||
Pair("centaur", "577e7297719a168ef96a7a06"),
|
||||
Pair("cervix penetration", "577e7273719a168ef96a77d0"),
|
||||
Pair("cheating", "577e71b5719a166f4719a13b"),
|
||||
Pair("cheerleader", "57c0a6de719a1641240e9257"),
|
||||
Pair("chikan", "5782b0c6719a1679528762ac"),
|
||||
Pair("chinese dress", "5782b059719a169473901131"),
|
||||
Pair("chloroform", "577e6f92719a168e7d256a7f"),
|
||||
Pair("christmas", "5782af2b719a169473900752"),
|
||||
Pair("clit growth", "57ef0396719a163dffb8fe00"),
|
||||
Pair("collar", "577e6f93719a168e7d256a8f"),
|
||||
Pair("condom", "577e71d5719a166f4719a36c"),
|
||||
Pair("corruption", "577e6f90719a168e7d256a41"),
|
||||
Pair("cosplaying", "5782b185719a167952876944"),
|
||||
Pair("cousin", "577e7283719a168ef96a78c3"),
|
||||
Pair("cow", "5865d767719a162cce299571"),
|
||||
Pair("cunnilingus", "577e6f8d719a168e7d256a23"),
|
||||
Pair("dark skin", "577e6f90719a168e7d256a55"),
|
||||
Pair("daughter", "577e7250719a168ef96a7539"),
|
||||
Pair("deepthroat", "577e6f90719a168e7d256a3c"),
|
||||
Pair("defloration", "577e6f92719a168e7d256a82"),
|
||||
Pair("demon girl", "577e7218719a166f4719a8c8"),
|
||||
Pair("dick growth", "577e6f93719a168e7d256a90"),
|
||||
Pair("dickgirl on dickgirl", "5782af0e719a16947390067a"),
|
||||
Pair("dog girl", "577e7218719a166f4719a8c9"),
|
||||
Pair("double penetration", "577e6f90719a168e7d256a3d"),
|
||||
Pair("double vaginal", "577e7226719a166f4719a9d1"),
|
||||
Pair("drugs", "577e71da719a166f4719a3cb"),
|
||||
Pair("drunk", "577e7199719a16697b9853ea"),
|
||||
Pair("elf", "577e6f93719a168e7d256a91"),
|
||||
Pair("enema", "5782aff7719a169473900d8a"),
|
||||
Pair("exhibitionism", "577e72a7719a168ef96a7b26"),
|
||||
Pair("eyemask", "577e7208719a166f4719a78e"),
|
||||
Pair("facesitting", "577e7230719a166f4719aa8c"),
|
||||
Pair("females only", "577e6f90719a168e7d256a44"),
|
||||
Pair("femdom", "577e6f8c719a168e7d256a13"),
|
||||
Pair("filming", "577e7242719a168ef96a7465"),
|
||||
Pair("fingering", "577e6f90719a168e7d256a5d"),
|
||||
Pair("fisting", "57c349e1719a1625b42603f4"),
|
||||
Pair("foot licking", "5782b152719a16795287677d"),
|
||||
Pair("footjob", "577e6f8d719a168e7d256a17"),
|
||||
Pair("freckles", "5782ae42719a1675f68a6e2b"),
|
||||
Pair("fundoshi", "577e71d9719a166f4719a3bf"),
|
||||
Pair("furry", "5782ae45719a1675f68a6e49"),
|
||||
Pair("futanari", "577e6f92719a168e7d256a80"),
|
||||
Pair("gag", "577e6f90719a168e7d256a56"),
|
||||
Pair("gaping", "577e7210719a166f4719a822"),
|
||||
Pair("garter belt", "577e7201719a166f4719a704"),
|
||||
Pair("glasses", "577e6f90719a168e7d256a5e"),
|
||||
Pair("gothic lolita", "577e7201719a166f4719a705"),
|
||||
Pair("group", "577e726e719a168ef96a7764"),
|
||||
Pair("gyaru", "577e6f91719a168e7d256a79"),
|
||||
Pair("hairjob", "57bcea9f719a1687ea2bc092"),
|
||||
Pair("hairy", "577e7250719a168ef96a753a"),
|
||||
Pair("hairy armpits", "5782b13c719a16795287669c"),
|
||||
Pair("handjob", "577e71c8719a166f4719a29b"),
|
||||
Pair("harem", "577e71c3719a166f4719a239"),
|
||||
Pair("heterochromia", "577e7201719a166f4719a706"),
|
||||
Pair("hotpants", "585b302d719a1648da4f0389"),
|
||||
Pair("huge breasts", "577e71d9719a166f4719a3c0"),
|
||||
Pair("huge penis", "585b302d719a1648da4f038a"),
|
||||
Pair("human on furry", "577e7203719a166f4719a722"),
|
||||
Pair("human pet", "577e6f90719a168e7d256a57"),
|
||||
Pair("humiliation", "577e7210719a166f4719a823"),
|
||||
Pair("impregnation", "577e6f90719a168e7d256a47"),
|
||||
Pair("incest", "577e6f93719a168e7d256a92"),
|
||||
Pair("inflation", "577e7273719a168ef96a77d1"),
|
||||
Pair("insect girl", "577e71fc719a166f4719a692"),
|
||||
Pair("inverted nipples", "5813993a719a165f236ddacd"),
|
||||
Pair("kimono", "577e723d719a168ef96a7417"),
|
||||
Pair("kissing", "5782ae4f719a1675f68a6ece"),
|
||||
Pair("lactation", "577e6f93719a168e7d256a93"),
|
||||
Pair("latex", "577e6f90719a168e7d256a58"),
|
||||
Pair("layer cake", "577e7230719a166f4719aa8d"),
|
||||
Pair("leg lock", "57b7c0c2719a169265b768bd"),
|
||||
Pair("leotard", "579b141e719a16881d14ccfe"),
|
||||
Pair("lingerie", "577e71fc719a166f4719a693"),
|
||||
Pair("living clothes", "577e6f90719a168e7d256a49"),
|
||||
Pair("lizard girl", "5782b127719a1679528765e9"),
|
||||
Pair("lolicon", "5782af84719a1694739009b5"),
|
||||
Pair("long tongue", "5782b158719a1679528767d5"),
|
||||
Pair("machine", "57ef0396719a163dffb8fe01"),
|
||||
Pair("magical girl", "577e71c3719a166f4719a236"),
|
||||
Pair("maid", "5782ae3f719a1675f68a6e19"),
|
||||
Pair("male on dickgirl", "577e7267719a168ef96a76f0"),
|
||||
Pair("masked face", "57c349e1719a1625b42603f5"),
|
||||
Pair("masturbation", "577e71b5719a166f4719a13c"),
|
||||
Pair("mermaid", "578d3c5b719a164fa798c09e"),
|
||||
Pair("metal armor", "5782b158719a1679528767d6"),
|
||||
Pair("miko", "577e726e719a168ef96a7765"),
|
||||
Pair("milf", "577e6f8d719a168e7d256a24"),
|
||||
Pair("military", "577e6f8d719a168e7d256a18"),
|
||||
Pair("milking", "577e6f93719a168e7d256a94"),
|
||||
Pair("mind break", "577e6f90719a168e7d256a4b"),
|
||||
Pair("mind control", "577e6f90719a168e7d256a4d"),
|
||||
Pair("monster girl", "577e6f90719a168e7d256a4f"),
|
||||
Pair("monster girl", "577e6f90719a168e7d256a46"),
|
||||
Pair("moral degeneration", "577e71da719a166f4719a3cc"),
|
||||
Pair("mother", "577e71c7719a166f4719a293"),
|
||||
Pair("mouse girl", "5782ae45719a1675f68a6e4a"),
|
||||
Pair("multiple breasts", "5782ae45719a1675f68a6e4b"),
|
||||
Pair("multiple penises", "577e722a719a166f4719aa29"),
|
||||
Pair("muscle", "577e7250719a168ef96a753c"),
|
||||
Pair("nakadashi", "577e6f8e719a168e7d256a26"),
|
||||
Pair("netorare", "577e71c7719a166f4719a294"),
|
||||
Pair("niece", "5782b10a719a1679528764b5"),
|
||||
Pair("nurse", "577e6f8d719a168e7d256a1d"),
|
||||
Pair("oil", "5782af5e719a1694739008b1"),
|
||||
Pair("onahole", "582324e5719a1674f99b3444"),
|
||||
Pair("orgasm denial", "577e725d719a168ef96a762f"),
|
||||
Pair("paizuri", "577e6f90719a168e7d256a3e"),
|
||||
Pair("pantyhose", "577e6f8d719a168e7d256a19"),
|
||||
Pair("pantyjob", "577e7276719a168ef96a77f9"),
|
||||
Pair("parasite", "577e6f90719a168e7d256a50"),
|
||||
Pair("pasties", "5782b029719a169473900f3b"),
|
||||
Pair("piercing", "577e6f90719a168e7d256a59"),
|
||||
Pair("plant girl", "577e71f4719a166f4719a5fa"),
|
||||
Pair("policewoman", "57af673b719a1655a6ca8b58"),
|
||||
Pair("ponygirl", "577e6f90719a168e7d256a5a"),
|
||||
Pair("possession", "5782aff7719a169473900d8b"),
|
||||
Pair("pregnant", "577e71da719a166f4719a3cd"),
|
||||
Pair("prolapse", "5782cc79719a165f600844e0"),
|
||||
Pair("prostitution", "577e7242719a168ef96a7466"),
|
||||
Pair("pubic stubble", "577e71da719a166f4719a3ce"),
|
||||
Pair("public use", "5782cc79719a165f600844e1"),
|
||||
Pair("rape", "577e6f90719a168e7d256a51"),
|
||||
Pair("rimjob", "577e725f719a168ef96a765e"),
|
||||
Pair("robot", "5782b144719a1679528766f3"),
|
||||
Pair("ryona", "577e723e719a168ef96a7424"),
|
||||
Pair("saliva", "5884ed6f719a1678dfbb2258"),
|
||||
Pair("scar", "5782b081719a167952876168"),
|
||||
Pair("school swimsuit", "5782b05f719a169473901177"),
|
||||
Pair("schoolgirl uniform", "577e7199719a16697b9853e6"),
|
||||
Pair("selfcest", "5782b152719a16795287677e"),
|
||||
Pair("sex toys", "577e6f90719a168e7d256a5b"),
|
||||
Pair("sheep girl", "5782affa719a169473900da2"),
|
||||
Pair("shemale", "577e7267719a168ef96a76f1"),
|
||||
Pair("shibari", "577e72a6719a168ef96a7b18"),
|
||||
Pair("shimapan", "5782aebd719a1694739004c5"),
|
||||
Pair("sister", "577e6f8c719a168e7d256a14"),
|
||||
Pair("slave", "577e71b4719a166f4719a138"),
|
||||
Pair("sleeping", "577e71e5719a166f4719a4b3"),
|
||||
Pair("slime", "577e6f93719a168e7d256a95"),
|
||||
Pair("slime girl", "577e6f90719a168e7d256a48"),
|
||||
Pair("small breasts", "577e6f90719a168e7d256a5f"),
|
||||
Pair("smell", "577e7210719a166f4719a824"),
|
||||
Pair("snake girl", "577e721e719a166f4719a94b"),
|
||||
Pair("sole dickgirl", "582324e5719a1674f99b3445"),
|
||||
Pair("sole female", "577e6f91719a168e7d256a7a"),
|
||||
Pair("solo action", "5782afbf719a169473900ba2"),
|
||||
Pair("spanking", "577e7199719a16697b9853e7"),
|
||||
Pair("squirting", "577e7250719a168ef96a753d"),
|
||||
Pair("stockings", "577e6f8d719a168e7d256a1a"),
|
||||
Pair("stomach deformation", "5782aef2719a169473900606"),
|
||||
Pair("strap-on", "577e71d5719a166f4719a367"),
|
||||
Pair("stuck in wall", "5782aecf719a16947390055b"),
|
||||
Pair("sundress", "577e7216719a166f4719a8a2"),
|
||||
Pair("sweating", "577e71b5719a166f4719a13d"),
|
||||
Pair("swimsuit", "577e71d3719a166f4719a342"),
|
||||
Pair("swinging", "577e7203719a166f4719a723"),
|
||||
Pair("syringe", "577e71da719a166f4719a3cf"),
|
||||
Pair("tall girl", "577e71d9719a166f4719a3c1"),
|
||||
Pair("tanlines", "577e6f91719a168e7d256a7b"),
|
||||
Pair("teacher", "577e7199719a16697b9853e8"),
|
||||
Pair("tentacles", "577e6f90719a168e7d256a52"),
|
||||
Pair("thigh high boots", "577e6f93719a168e7d256a96"),
|
||||
Pair("tiara", "5782cc74719a165f600844d3"),
|
||||
Pair("tights", "5782b059719a169473901132"),
|
||||
Pair("tomboy", "577e7201719a166f4719a6fb"),
|
||||
Pair("torture", "577e725d719a168ef96a7630"),
|
||||
Pair("tracksuit", "5782b146719a167952876708"),
|
||||
Pair("transformation", "577e6f90719a168e7d256a4a"),
|
||||
Pair("tribadism", "577e6f90719a168e7d256a60"),
|
||||
Pair("tube", "577e7208719a166f4719a78f"),
|
||||
Pair("tutor", "5782af34719a1694739007a3"),
|
||||
Pair("twins", "577e726a719a168ef96a7729"),
|
||||
Pair("unusual pupils", "577e6f90719a168e7d256a53"),
|
||||
Pair("urethra insertion", "5877c07f719a163627a2ceb0"),
|
||||
Pair("urination", "577e7210719a166f4719a825"),
|
||||
Pair("vaginal sticker", "577e721c719a166f4719a930"),
|
||||
Pair("vomit", "5782ae45719a1675f68a6e4c"),
|
||||
Pair("vore", "577e6f8c719a168e7d256a15"),
|
||||
Pair("voyeurism", "583ca1ef719a161795a60847"),
|
||||
Pair("waitress", "5782ae3f719a1675f68a6e1a"),
|
||||
Pair("widow", "5782b13c719a16795287669d"),
|
||||
Pair("wings", "5782b158719a1679528767d7"),
|
||||
Pair("witch", "577e6f93719a168e7d256a97"),
|
||||
Pair("wolf girl", "577e724c719a168ef96a74fd"),
|
||||
Pair("wrestling", "577e7230719a166f4719aa8e"),
|
||||
Pair("x-ray", "577e6f90719a168e7d256a40"),
|
||||
Pair("yandere", "577e7295719a168ef96a79e7"),
|
||||
Pair("yuri", "577e6f90719a168e7d256a4c")
|
||||
)
|
||||
}
|
13
src/ru/mangachan/build.gradle
Normal file
13
src/ru/mangachan/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mangachan'
|
||||
pkgNameSuffix = "ru.mangachan"
|
||||
extClass = '.Mangachan'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,230 @@
|
||||
package eu.kanade.tachiyomi.extension.ru.mangachan
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class Mangachan : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 7
|
||||
|
||||
override val name = "Mangachan"
|
||||
|
||||
override val baseUrl = "http://mangachan.me"
|
||||
|
||||
override val lang = "ru"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = if (query.isNotEmpty()) {
|
||||
"$baseUrl/?do=search&subaction=search&story=$query"
|
||||
} else {
|
||||
val filt = filters.filterIsInstance<Genre>().filter { !it.isIgnored() }
|
||||
if (filt.isNotEmpty()) {
|
||||
var genres = ""
|
||||
filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' }
|
||||
"$baseUrl/tags/${genres.dropLast(1)}"
|
||||
} else {
|
||||
"$baseUrl/?do=search&subaction=search&story=$query"
|
||||
}
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/newestch?page=$page")
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.content_row"
|
||||
|
||||
override fun latestUpdatesSelector() = "ul.area_rightNews li"
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("h2 > a").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("a:nth-child(1)").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.text()
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a:contains(Вперед)"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a:contains(Далее)"
|
||||
|
||||
private fun searchGenresNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
val mangas = document.select(searchMangaSelector()).map { element ->
|
||||
searchMangaFromElement(element)
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE }
|
||||
// searchMangaNextPageSelector().let { selector ->
|
||||
// if (page.nextPageUrl.isNullOrEmpty() && allIgnore) {
|
||||
// val onClick = document.select(selector).first()?.attr("onclick")
|
||||
// val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)"))
|
||||
// page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// searchGenresNextPageSelector().let { selector ->
|
||||
// if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) {
|
||||
// val url = document.select(selector).first()?.attr("href")
|
||||
// page.nextPageUrl = searchMangaInitialUrl(query, filters) + url
|
||||
// }
|
||||
// }
|
||||
|
||||
return MangasPage(mangas, false)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select("table.mangatitle").first()
|
||||
val descElement = document.select("div#description").first()
|
||||
val imgElement = document.select("img#cover").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text()
|
||||
manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text()
|
||||
manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text())
|
||||
manga.description = descElement.textNodes().first().text()
|
||||
manga.thumbnail_url = baseUrl + imgElement.attr("src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(element: String): Int {
|
||||
when {
|
||||
element.contains("перевод завершен") -> return SManga.COMPLETED
|
||||
element.contains("перевод продолжается") -> return SManga.ONGOING
|
||||
else -> return SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "table.table_cha tr:gt(1)"
|
||||
|
||||
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("div.date").first()?.text()?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time
|
||||
} ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val html = response.body().string()
|
||||
val beginIndex = html.indexOf("fullimg\":[") + 10
|
||||
val endIndex = html.indexOf(",]", beginIndex)
|
||||
val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "")
|
||||
val pageUrls = trimmedHtml.split(',')
|
||||
|
||||
return pageUrls.mapIndexed { i, url -> Page(i, "", url) }
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name)
|
||||
|
||||
/* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) =>
|
||||
* { const link=el.getAttribute('href');const id=link.substr(6,link.length);
|
||||
* return `Genre("${id.replace("_", " ")}")` }).join(',\n')
|
||||
* on http://mangachan.me/
|
||||
*/
|
||||
override fun getFilterList() = FilterList(
|
||||
Genre("18 плюс"),
|
||||
Genre("bdsm"),
|
||||
Genre("арт"),
|
||||
Genre("биография"),
|
||||
Genre("боевик"),
|
||||
Genre("боевые искусства"),
|
||||
Genre("вампиры"),
|
||||
Genre("веб"),
|
||||
Genre("гарем"),
|
||||
Genre("гендерная интрига"),
|
||||
Genre("героическое фэнтези"),
|
||||
Genre("детектив"),
|
||||
Genre("дзёсэй"),
|
||||
Genre("додзинси"),
|
||||
Genre("драма"),
|
||||
Genre("игра"),
|
||||
Genre("инцест"),
|
||||
Genre("искусство"),
|
||||
Genre("история"),
|
||||
Genre("киберпанк"),
|
||||
Genre("кодомо"),
|
||||
Genre("комедия"),
|
||||
Genre("литРПГ"),
|
||||
Genre("магия"),
|
||||
Genre("махо-сёдзё"),
|
||||
Genre("меха"),
|
||||
Genre("мистика"),
|
||||
Genre("музыка"),
|
||||
Genre("научная фантастика"),
|
||||
Genre("повседневность"),
|
||||
Genre("постапокалиптика"),
|
||||
Genre("приключения"),
|
||||
Genre("психология"),
|
||||
Genre("романтика"),
|
||||
Genre("самурайский боевик"),
|
||||
Genre("сборник"),
|
||||
Genre("сверхъестественное"),
|
||||
Genre("сказка"),
|
||||
Genre("спорт"),
|
||||
Genre("супергерои"),
|
||||
Genre("сэйнэн"),
|
||||
Genre("сёдзё"),
|
||||
Genre("сёдзё-ай"),
|
||||
Genre("сёнэн"),
|
||||
Genre("сёнэн-ай"),
|
||||
Genre("тентакли"),
|
||||
Genre("трагедия"),
|
||||
Genre("триллер"),
|
||||
Genre("ужасы"),
|
||||
Genre("фантастика"),
|
||||
Genre("фурри"),
|
||||
Genre("фэнтези"),
|
||||
Genre("школа"),
|
||||
Genre("эротика"),
|
||||
Genre("юри"),
|
||||
Genre("яой"),
|
||||
Genre("ёнкома")
|
||||
)
|
||||
}
|
13
src/ru/mintmanga/build.gradle
Normal file
13
src/ru/mintmanga/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Mintmanga'
|
||||
pkgNameSuffix = "ru.mintmanga"
|
||||
extClass = '.Mintmanga'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,184 @@
|
||||
package eu.kanade.tachiyomi.extension.ru.mintmanga
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Mintmanga : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 6
|
||||
|
||||
override val name = "Mintmanga"
|
||||
|
||||
override val baseUrl = "http://mintmanga.com"
|
||||
|
||||
override val lang = "ru"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.desc"
|
||||
|
||||
override fun latestUpdatesSelector() = "div.desc"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("h3 > a").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.attr("title")
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a.nextLink"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "a.nextLink"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")
|
||||
return GET("$baseUrl/search?q=$query&$genres", headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
// max 200 results
|
||||
override fun searchMangaNextPageSelector() = null
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select("div.leftContent").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("span.elem_author").first()?.text()
|
||||
manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",")
|
||||
manga.description = infoElement.select("div.manga-description").text()
|
||||
manga.status = parseStatus(infoElement.html())
|
||||
manga.thumbnail_url = infoElement.select("img").attr("data-full")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(element: String): Int {
|
||||
when {
|
||||
element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED
|
||||
element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED
|
||||
element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING
|
||||
else -> return SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.chapters-link tbody tr"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select("a").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
|
||||
chapter.name = urlElement.text().replace(" новое", "")
|
||||
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
||||
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
||||
} ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
|
||||
chapter.chapter_number = -2f
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val html = response.body().string()
|
||||
val beginIndex = html.indexOf("rm_h.init( [")
|
||||
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
||||
val trimmedHtml = html.substring(beginIndex, endIndex)
|
||||
|
||||
val p = Pattern.compile("'.+?','.+?',\".+?\"")
|
||||
val m = p.matcher(trimmedHtml)
|
||||
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
var i = 0
|
||||
while (m.find()) {
|
||||
val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',')
|
||||
pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2]))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
/* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => {
|
||||
* const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33);
|
||||
* return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n')
|
||||
* on http://mintmanga.com/search
|
||||
*/
|
||||
override fun getFilterList() = FilterList(
|
||||
Genre("арт", "el_2220"),
|
||||
Genre("бара", "el_1353"),
|
||||
Genre("боевик", "el_1346"),
|
||||
Genre("боевые искусства", "el_1334"),
|
||||
Genre("вампиры", "el_1339"),
|
||||
Genre("гарем", "el_1333"),
|
||||
Genre("гендерная интрига", "el_1347"),
|
||||
Genre("героическое фэнтези", "el_1337"),
|
||||
Genre("детектив", "el_1343"),
|
||||
Genre("дзёсэй", "el_1349"),
|
||||
Genre("додзинси", "el_1332"),
|
||||
Genre("драма", "el_1310"),
|
||||
Genre("игра", "el_5229"),
|
||||
Genre("история", "el_1311"),
|
||||
Genre("киберпанк", "el_1351"),
|
||||
Genre("комедия", "el_1328"),
|
||||
Genre("меха", "el_1318"),
|
||||
Genre("мистика", "el_1324"),
|
||||
Genre("научная фантастика", "el_1325"),
|
||||
Genre("повседневность", "el_1327"),
|
||||
Genre("постапокалиптика", "el_1342"),
|
||||
Genre("приключения", "el_1322"),
|
||||
Genre("психология", "el_1335"),
|
||||
Genre("романтика", "el_1313"),
|
||||
Genre("самурайский боевик", "el_1316"),
|
||||
Genre("сверхъестественное", "el_1350"),
|
||||
Genre("сёдзё", "el_1314"),
|
||||
Genre("сёдзё-ай", "el_1320"),
|
||||
Genre("сёнэн", "el_1326"),
|
||||
Genre("сёнэн-ай", "el_1330"),
|
||||
Genre("спорт", "el_1321"),
|
||||
Genre("сэйнэн", "el_1329"),
|
||||
Genre("трагедия", "el_1344"),
|
||||
Genre("триллер", "el_1341"),
|
||||
Genre("ужасы", "el_1317"),
|
||||
Genre("фантастика", "el_1331"),
|
||||
Genre("фэнтези", "el_1323"),
|
||||
Genre("школа", "el_1319"),
|
||||
Genre("эротика", "el_1340"),
|
||||
Genre("этти", "el_1354"),
|
||||
Genre("юри", "el_1315"),
|
||||
Genre("яой", "el_1336")
|
||||
)
|
||||
}
|
13
src/ru/readmanga/build.gradle
Normal file
13
src/ru/readmanga/build.gradle
Normal file
@ -0,0 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: Readmanga'
|
||||
pkgNameSuffix = "ru.readmanga"
|
||||
extClass = '.Readmanga'
|
||||
extVersionCode = 1
|
||||
extVersionSuffix = 1
|
||||
libVersion = '1.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -0,0 +1,183 @@
|
||||
package eu.kanade.tachiyomi.extension.ru.readmanga
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.*
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Readmanga : ParsedHttpSource() {
|
||||
|
||||
override val id: Long = 5
|
||||
|
||||
override val name = "Readmanga"
|
||||
|
||||
override val baseUrl = "http://readmanga.me"
|
||||
|
||||
override val lang = "ru"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun popularMangaSelector() = "div.desc"
|
||||
|
||||
override fun latestUpdatesSelector() = "div.desc"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select("h3 > a").first().let {
|
||||
manga.setUrlWithoutDomain(it.attr("href"))
|
||||
manga.title = it.attr("title")
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a.nextLink"
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = "a.nextLink"
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")
|
||||
return GET("$baseUrl/search?q=$query&$genres", headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
// max 200 results
|
||||
override fun searchMangaNextPageSelector() = null
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select("div.leftContent").first()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.select("span.elem_author").first()?.text()
|
||||
manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",")
|
||||
manga.description = infoElement.select("div.manga-description").text()
|
||||
manga.status = parseStatus(infoElement.html())
|
||||
manga.thumbnail_url = infoElement.select("img").attr("data-full")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun parseStatus(element: String): Int {
|
||||
when {
|
||||
element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED
|
||||
element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED
|
||||
element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING
|
||||
else -> return SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.chapters-link tbody tr"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val urlElement = element.select("a").first()
|
||||
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
|
||||
chapter.name = urlElement.text().replace(" новое", "")
|
||||
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
|
||||
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
|
||||
} ?: 0
|
||||
return chapter
|
||||
}
|
||||
|
||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
|
||||
chapter.chapter_number = -2f
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val html = response.body().string()
|
||||
val beginIndex = html.indexOf("rm_h.init( [")
|
||||
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
||||
val trimmedHtml = html.substring(beginIndex, endIndex)
|
||||
|
||||
val p = Pattern.compile("'.+?','.+?',\".+?\"")
|
||||
val m = p.matcher(trimmedHtml)
|
||||
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
var i = 0
|
||||
while (m.find()) {
|
||||
val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',')
|
||||
pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2]))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
throw Exception("Not used")
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
/* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => {
|
||||
* const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33);
|
||||
* return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n')
|
||||
* on http://readmanga.me/search
|
||||
*/
|
||||
override fun getFilterList() = FilterList(
|
||||
Genre("арт", "el_5685"),
|
||||
Genre("боевик", "el_2155"),
|
||||
Genre("боевые искусства", "el_2143"),
|
||||
Genre("вампиры", "el_2148"),
|
||||
Genre("гарем", "el_2142"),
|
||||
Genre("гендерная интрига", "el_2156"),
|
||||
Genre("героическое фэнтези", "el_2146"),
|
||||
Genre("детектив", "el_2152"),
|
||||
Genre("дзёсэй", "el_2158"),
|
||||
Genre("додзинси", "el_2141"),
|
||||
Genre("драма", "el_2118"),
|
||||
Genre("игра", "el_2154"),
|
||||
Genre("история", "el_2119"),
|
||||
Genre("киберпанк", "el_8032"),
|
||||
Genre("кодомо", "el_2137"),
|
||||
Genre("комедия", "el_2136"),
|
||||
Genre("махо-сёдзё", "el_2147"),
|
||||
Genre("меха", "el_2126"),
|
||||
Genre("мистика", "el_2132"),
|
||||
Genre("научная фантастика", "el_2133"),
|
||||
Genre("повседневность", "el_2135"),
|
||||
Genre("постапокалиптика", "el_2151"),
|
||||
Genre("приключения", "el_2130"),
|
||||
Genre("психология", "el_2144"),
|
||||
Genre("романтика", "el_2121"),
|
||||
Genre("самурайский боевик", "el_2124"),
|
||||
Genre("сверхъестественное", "el_2159"),
|
||||
Genre("сёдзё", "el_2122"),
|
||||
Genre("сёдзё-ай", "el_2128"),
|
||||
Genre("сёнэн", "el_2134"),
|
||||
Genre("сёнэн-ай", "el_2139"),
|
||||
Genre("спорт", "el_2129"),
|
||||
Genre("сэйнэн", "el_2138"),
|
||||
Genre("трагедия", "el_2153"),
|
||||
Genre("триллер", "el_2150"),
|
||||
Genre("ужасы", "el_2125"),
|
||||
Genre("фантастика", "el_2140"),
|
||||
Genre("фэнтези", "el_2131"),
|
||||
Genre("школа", "el_2127"),
|
||||
Genre("этти", "el_2149"),
|
||||
Genre("юри", "el_2123")
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user