diff --git a/src/pt/centraldemangas/build.gradle b/src/pt/centraldemangas/build.gradle new file mode 100644 index 000000000..596b8cf59 --- /dev/null +++ b/src/pt/centraldemangas/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Central de Mangás' + pkgNameSuffix = 'pt.centraldemangas' + extClass = '.CentralDeMangas' + extVersionCode = 1 + libVersion = '1.2' +} + +dependencies { + compileOnly 'com.google.code.gson:gson:2.8.2' + compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..0c796f9f1 Binary files /dev/null and b/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..e523f21e8 Binary files /dev/null and b/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..1cf9880d9 Binary files /dev/null and b/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..04f295d46 Binary files /dev/null and b/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..3ae754011 Binary files /dev/null and b/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/centraldemangas/res/web_hi_res_512.png b/src/pt/centraldemangas/res/web_hi_res_512.png new file mode 100644 index 000000000..1c5928edb Binary files /dev/null and b/src/pt/centraldemangas/res/web_hi_res_512.png differ diff --git a/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt b/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt new file mode 100644 index 000000000..958c4db03 --- /dev/null +++ b/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt @@ -0,0 +1,197 @@ +package eu.kanade.tachiyomi.extension.pt.centraldemangas + +import com.github.salomonbrys.kotson.array +import com.github.salomonbrys.kotson.get +import com.google.gson.JsonParser +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +class CentralDeMangas : ParsedHttpSource() { + + override val name = "Central de Mangás" + + override val baseUrl = "http://cdmnet.com.br" + + override val lang = "pt" + + override val supportsLatest = true + + // Sometimes the site is very slow. + override val client = + network.client.newBuilder() + .connectTimeout(3, TimeUnit.MINUTES) + .readTimeout(3, TimeUnit.MINUTES) + .writeTimeout(3, TimeUnit.MINUTES) + .build() + + private val catalogHeaders = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("Host", "cdmnet.com.br") + add("Referer", baseUrl) + }.build() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun popularMangaSelector(): String = "div.ui.eight.doubling.stackable.cards div.card" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + + manga.thumbnail_url = element.select("div.ui.image a img") + .first()?.attr("src")?.replace("60x80", "150x200") + element.select("div.content a").last().let { + manga.url = it.attr("href") + manga.title = it.text() + } + + return manga + } + + override fun popularMangaNextPageSelector() = null + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun latestUpdatesSelector() = "div.ui.black.segment div.ui.divided.celled.list div.item" + + override fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + + manga.thumbnail_url = element.select("div.ui.tiny.bordered.image a img") + .first()?.attr("src")?.replace("60x80", "150x200") + element.select("div.content div.header a.popar").last().let { + manga.url = it.attr("href") + manga.title = it.text() + } + + return manga + } + + override fun latestUpdatesNextPageSelector() = null + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response, query) + } + } + + override fun searchMangaParse(response: Response): MangasPage = searchMangaParse(response, "") + + private fun searchMangaParse(response: Response, query: String?): MangasPage { + val result = jsonParser.parse(response.body()!!.string()).array + + val resultFiltered = result + .filter { it["title"].asString.contains(query ?: "", true) } + .map { + SManga.create().apply { + title = it["title"].asString + url = it["url"].asString + } + } + + return MangasPage(resultFiltered, false) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/api/titulos", headers) + } + + override fun searchMangaSelector() = throw Exception("This method should not be called!") + + override fun searchMangaFromElement(element: Element): SManga = throw Exception("This method should not be called!") + + override fun searchMangaNextPageSelector() = "" + + override fun mangaDetailsParse(document: Document): SManga { + val elementList = document.select("div.ui.black.segment div.ui.relaxed.list").first() + + return SManga.create().apply { + author = elementList.select("div.item:eq(3) div.content div.description").text() + artist = elementList.select("div.item:eq(2) div.content div.description").text() + genre = elementList.select("div.item:eq(4) div.content div.description a") + .joinToString { it.text() } + + status = elementList.select("div.item:eq(6) div.content div.description") + .text().orEmpty().let { parseStatus(it) } + + description = elementList.select("div.item:eq(0) div.content div.description").text() + thumbnail_url = elementList.select("div.item:eq(0) div.content div.description img").attr("src") + } + } + + private fun parseStatus(status: String) = when { + status.contains("Em publicação") -> SManga.ONGOING + status.contains("Completo") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListParse(response: Response): List { + // Filter only manga chapters. + return super.chapterListParse(response).filter { !it.url.contains("/novel/") } + } + + override fun chapterListSelector() = "table.ui.small.compact.very.basic.table tbody tr:not(.active)" + + override fun chapterFromElement(element: Element): SChapter { + val firstColumn = element.select("td:eq(0)") + val secondColumn = element.select("td:eq(1)") + + return SChapter.create().apply { + url = firstColumn.select("a").first().attr("href") + name = firstColumn.select("a").first().text() + date_upload = secondColumn.select("small").first()?.text()?.let { parseChapterDate(it) } ?: 0 + } + } + + private fun parseChapterDate(date: String) : Long { + return try { + SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { + 0L + } + } + + override fun pageListParse(document: Document): List { + val script = document.select("script").last().data() + val urlSuffix = script.substringAfter(SCRIPT_URL_BEGIN).substringBefore(SCRIPT_URL_END) + val pages = script.substringAfter(SCRIPT_PAGES_BEGIN).substringBefore(SCRIPT_PAGES_END) + .replace("'", "").split(",") + + return pages + .mapIndexed { i, page -> Page(i, "", "$urlSuffix$page.jpg")} + } + + override fun imageRequest(page: Page): Request { + var imageHeaders = Headers.Builder().apply { + add("Referer", baseUrl) + } + + return GET(page.imageUrl!!, imageHeaders.build()) + } + + override fun imageUrlParse(document: Document) = "" + + companion object { + private const val SCRIPT_URL_BEGIN = "var urlSulfix = '" + private const val SCRIPT_URL_END = "';" + private const val SCRIPT_PAGES_BEGIN = "var pages = [" + private const val SCRIPT_PAGES_END = ",];" + + val jsonParser by lazy { + JsonParser() + } + } +} diff --git a/src/pt/saikaiscan/build.gradle b/src/pt/saikaiscan/build.gradle new file mode 100644 index 000000000..11a73f8e7 --- /dev/null +++ b/src/pt/saikaiscan/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Saikai Scan' + pkgNameSuffix = 'pt.saikaiscan' + extClass = '.SaikaiScan' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..46f81b08b Binary files /dev/null and b/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..30d69e7b7 Binary files /dev/null and b/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..59aa8670b Binary files /dev/null and b/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..af09cefb3 Binary files /dev/null and b/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7bb38b06b Binary files /dev/null and b/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/saikaiscan/res/web_hi_res_512.png b/src/pt/saikaiscan/res/web_hi_res_512.png new file mode 100644 index 000000000..0a77c312c Binary files /dev/null and b/src/pt/saikaiscan/res/web_hi_res_512.png differ diff --git a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt new file mode 100644 index 000000000..e932317ed --- /dev/null +++ b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt @@ -0,0 +1,136 @@ +package eu.kanade.tachiyomi.extension.pt.saikaiscan + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class SaikaiScan : ParsedHttpSource() { + + override val name = "Saikai Scan" + + override val baseUrl = "https://saikaiscan.com.br" + + override val lang = "pt" + + override val supportsLatest = true + + private val catalogHeaders = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("Host", "saikaiscan.com.br") + add("Referer", baseUrl) + }.build() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun popularMangaSelector(): String = "div#menu ul li.has_submenu:eq(3) li a" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.text().substringBeforeLast("(") + url = element.attr("href") + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun latestUpdatesSelector(): String = "ul.manhuas li.manhua-item" + + override fun latestUpdatesFromElement(element: Element): SManga { + var image = element.select("div.image.lazyload") + var name = element.select("h3") + + return SManga.create().apply { + title = name.text().substringBeforeLast("(") + thumbnail_url = baseUrl + image.attr("data-src") + url = image.select("a").attr("href") + } + } + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/busca?q=$query", catalogHeaders) + } + + override fun searchMangaParse(response: Response): MangasPage { + var results = super.searchMangaParse(response) + var manhuas = results.mangas.filter { it.url.contains("/manhuas/") } + + return MangasPage(manhuas, results.hasNextPage) + } + + override fun searchMangaSelector(): String = "div#news-content ul li" + + override fun searchMangaFromElement(element: Element): SManga { + var image = element.select("div.image.lazyload") + var name = element.select("h3") + + return SManga.create().apply { + title = name.text().substringBeforeLast("(") + thumbnail_url = baseUrl + image.attr("data-src") + url = image.select("a").attr("href") + } + } + + override fun searchMangaNextPageSelector(): String? = null + + override fun mangaDetailsParse(document: Document): SManga { + var projectContent = document.select("div#project-content") + var name = projectContent.select("h2").first() + var cover = projectContent.select("div.cover img.lazyload") + var genres = projectContent.select("div.info:contains(Gênero)") + var author = projectContent.select("div.info:contains(Autor)") + var status = projectContent.select("div.info:contains(Status)") + var summary = projectContent.select("div.summary-text") + + return SManga.create().apply { + title = name.text() + thumbnail_url = baseUrl + cover.attr("data-src") + genre = removeLabel(genres.text()) + this.author = removeLabel(author.text()) + artist = removeLabel(author.text()) + this.status = parseStatus(removeLabel(status.text())) + description = summary.text() + } + } + + private fun removeLabel(info: String) = info.substringAfter(":") + + private fun parseStatus(status: String) = when { + status.contains("Completo") -> SManga.COMPLETED + status.contains("Em Tradução", true) -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + override fun chapterListParse(response: Response): List { + return super.chapterListParse(response).reversed() + } + + override fun chapterListSelector(): String = "div#project-content div.project-chapters div.chapters ul li a" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + scanlator = "Saikai Scan" + chapter_number = CHAPTER_REGEX.toRegex().find(element.text())?.groupValues?.get(1)?.toFloatOrNull() ?: 1f + name = element.text() + url = element.attr("href") + } + + override fun pageListParse(document: Document): List { + var imagesBlock = document.select("div.manhua-slide div.images-block img.lazyload") + + return imagesBlock + .mapIndexed { i, el -> Page(i, "", el.absUrl("src")) } + } + + override fun imageUrlParse(document: Document): String = "" + + companion object { + private const val CHAPTER_REGEX = "Capítulo (\\d+)" + } + +} diff --git a/src/pt/yesmangas/build.gradle b/src/pt/yesmangas/build.gradle new file mode 100644 index 000000000..11e011373 --- /dev/null +++ b/src/pt/yesmangas/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: YES Mangás' + pkgNameSuffix = 'pt.yesmangas' + extClass = '.YesMangas' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..3b91c99e5 Binary files /dev/null and b/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..51afc3037 Binary files /dev/null and b/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..f0662aa59 Binary files /dev/null and b/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..7f3a529c6 Binary files /dev/null and b/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..dc63f07bd Binary files /dev/null and b/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/yesmangas/res/web_hi_res_512.png b/src/pt/yesmangas/res/web_hi_res_512.png new file mode 100644 index 000000000..9c518e509 Binary files /dev/null and b/src/pt/yesmangas/res/web_hi_res_512.png differ diff --git a/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt b/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt new file mode 100644 index 000000000..0213c4c0f --- /dev/null +++ b/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt @@ -0,0 +1,121 @@ +package eu.kanade.tachiyomi.extension.pt.yesmangas + +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.Headers +import okhttp3.Request +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class YesMangas : ParsedHttpSource() { + + override val name = "YES Mangás" + + override val baseUrl = "https://yesmangasbr.com" + + override val lang = "pt" + + override val supportsLatest = true + + private val catalogHeaders = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("Host", "yesmangasbr.com") + add("Referer", baseUrl) + }.build() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun popularMangaSelector(): String = "div#destaques div.three.columns a.img" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.attr("title").replace(LANG_REGEX.toRegex(), "") + thumbnail_url = element.select("img").attr("data-path") + .replace("xmedium", "xlarge") + url = element.attr("href") + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, catalogHeaders) + + override fun latestUpdatesSelector(): String = "div#lancamentos table.u-full-width tbody tr td:eq(0) a" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.attr("title").replace(LANG_REGEX.toRegex(), "") + thumbnail_url = element.select("img").attr("data-path") + .replace("medium", "xlarge") + setUrlWithoutDomain(element.attr("href")) + } + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/search?q=$query", catalogHeaders) + } + + override fun searchMangaSelector(): String = "tbody#leituras tr td:eq(0) a" + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.select("img").attr("alt").replace(LANG_REGEX.toRegex(), "") + thumbnail_url = element.select("img").attr("data-path").replace("medium", "xlarge") + setUrlWithoutDomain(element.attr("href")) + } + + override fun searchMangaNextPageSelector(): String? = null + + override fun mangaDetailsParse(document: Document): SManga { + var container = document.select("div#descricao") + var status = container.select("ul li:contains(Status)") + var author = container.select("ul li:contains(Autor)") + var artist = container.select("ul li:contains(Desenho)") + var synopsis = container.select("article") + + return SManga.create().apply { + this.status = parseStatus(removeLabel(status.text())) + this.author = removeLabel(author.text()) + this.artist = removeLabel(artist.text()) + description = synopsis.text().substringBefore("Relacionados") + } + } + + private fun removeLabel(info: String) = info.substringAfter(":") + + private fun parseStatus(status: String) = when { + status.contains("Completo") -> SManga.COMPLETED + status.contains("Ativo") -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + override fun chapterListSelector(): String = "div#capitulos a.button" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + chapter_number = element.text().toFloatOrNull() ?: 1f + name = element.attr("title").substringAfter(" - ") + } + + override fun pageListParse(document: Document): List { + var script = document.select("script").last().data() + var images = script.substringAfter(SCRIPT_BEGIN).substringBefore(SCRIPT_END) + .replace(SCRIPT_REGEX.toRegex(), "") + + var newDocument = Jsoup.parse(images) + + return newDocument.select("a img") + .mapIndexed { i, el -> Page(i, "", el.attr("src")) } + } + + override fun imageUrlParse(document: Document): String = "" + + companion object { + private const val LANG_REGEX = "( )?\\((PT-)?BR\\)" + private const val SCRIPT_BEGIN = "var images = [" + private const val SCRIPT_END = "];" + private const val SCRIPT_REGEX = "\"|," + } +}