New source: TimelessLeaf (#3552)
* TimelessLeaf: code done * TimelessLeaf: dark icons by @AbdullahM0hamed * TimelessLeaf: implemented local search * TimelessLeaf: new icons by @as280093 * TimelessLeaf: done requested changes
This commit is contained in:
parent
51f073fb0c
commit
a2f9d6a4fd
12
src/en/timelessleaf/build.gradle
Normal file
12
src/en/timelessleaf/build.gradle
Normal file
@ -0,0 +1,12 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
appName = 'Tachiyomi: TimelessLeaf'
|
||||
pkgNameSuffix = 'en.timelessleaf'
|
||||
extClass = '.TimelessLeaf'
|
||||
extVersionCode = 1
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/en/timelessleaf/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/timelessleaf/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
src/en/timelessleaf/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/timelessleaf/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
src/en/timelessleaf/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/timelessleaf/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
BIN
src/en/timelessleaf/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/timelessleaf/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
src/en/timelessleaf/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/timelessleaf/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
src/en/timelessleaf/res/web_hi_res_512.png
Normal file
BIN
src/en/timelessleaf/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
@ -0,0 +1,136 @@
|
||||
package eu.kanade.tachiyomi.extension.en.timelessleaf
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
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.HttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import java.util.Locale
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* @author Aria Moradi <aria.moradi007@gmail.com>
|
||||
*/
|
||||
|
||||
class TimelessLeaf : HttpSource() {
|
||||
|
||||
override val name = "TimelessLeaf"
|
||||
|
||||
override val baseUrl = "https://timelessleaf.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest: Boolean = false
|
||||
|
||||
val mangasPageUrl = baseUrl + "/manga/"
|
||||
|
||||
// popular manga
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET(mangasPageUrl)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
// scraping post links
|
||||
val articleLinks = document.select(".site-main article a")
|
||||
|
||||
// scraping menus, ignore the ones that are not manga entries
|
||||
val pagesWeDontWant = listOf(
|
||||
"dropped",
|
||||
"more manga",
|
||||
"recent"
|
||||
).joinToString(prefix = "(?i)", separator = "|").toRegex()
|
||||
|
||||
// all mangas are in sub menus, go straight for that to deal with less menu items
|
||||
val menuLinks = document.select(".sub-menu a").filterNot { element ->
|
||||
element.text().toLowerCase(Locale.ROOT).contains(pagesWeDontWant)
|
||||
}
|
||||
|
||||
// combine the two lists
|
||||
val combinedLinks = articleLinks.map { el ->
|
||||
Pair(el.text(), el.attr("href"))
|
||||
}.toMutableList().apply {
|
||||
val titleList = this.map { it.first }
|
||||
menuLinks.forEach { el ->
|
||||
val title = el.text()
|
||||
// ignore duplicates
|
||||
if (titleList.filter { str -> str.startsWith(title, ignoreCase = true) }.isEmpty())
|
||||
add(Pair(title, el.attr("href")))
|
||||
}
|
||||
}.sortedBy { pair -> pair.first }
|
||||
|
||||
return MangasPage(combinedLinks.map { p ->
|
||||
SManga.create().apply {
|
||||
title = p.first
|
||||
setUrlWithoutDomain(p.second)
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
|
||||
// manga details
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val document = response.asJsoup()
|
||||
|
||||
return SManga.create().apply {
|
||||
// prefer srcset for higher res images, if not available use src
|
||||
thumbnail_url = document.select(".site-main img").attr("srcset").substringBefore(" ")
|
||||
if (thumbnail_url == "")
|
||||
thumbnail_url = document.select(".site-main img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
// chapter list
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
// some chapters are not hosted at TimelessLeaf itself, so can't do anything about them -> ignore
|
||||
val hostedHere = response.asJsoup().select(".site-main a").filter { el ->
|
||||
el.attr("href").startsWith(baseUrl)
|
||||
}
|
||||
|
||||
return hostedHere.map { el ->
|
||||
SChapter.create().apply {
|
||||
setUrlWithoutDomain(el.attr("href"))
|
||||
name = el.text()
|
||||
}
|
||||
}.asReversed()
|
||||
}
|
||||
|
||||
// page list
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
return response.asJsoup().select(".site-main article .gallery-item img").mapIndexed { index, el ->
|
||||
Page(index, "", el.attr("abs:src"))
|
||||
}
|
||||
}
|
||||
|
||||
// search manga, implementing a local search
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1)
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
val allManga = popularMangaParse(response)
|
||||
val filtered = allManga.mangas.filter { manga -> manga.title.contains(query, ignoreCase = true) }
|
||||
MangasPage(filtered, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.")
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user