Reorganize project structure

This commit is contained in:
len
2017-02-25 16:51:30 +01:00
parent 8580b9138c
commit c883afbcd6
94 changed files with 37 additions and 36 deletions

View 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"

View File

@ -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")
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}

View 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

View 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"

View File

@ -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)
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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("ёнкома")
)
}

View 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"

View File

@ -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")
)
}

View 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"

View File

@ -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")
)
}