Split FoolSlide Extension (#5840)

* Split FoolSlide Extension

* remove FoolSlide

* add className

* change default_res 

#5845

* add nsfw

* nsfw2
This commit is contained in:
Riztard Lanthorn
2021-02-15 22:36:28 +07:00
committed by GitHub
parent d2cdca33e2
commit a49001e314
43 changed files with 362 additions and 291 deletions

View File

@ -0,0 +1,301 @@
package eu.kanade.tachiyomi.multisrc.foolslide
import com.github.salomonbrys.kotson.get
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.ParsedHttpSource
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.HashSet
import java.util.Locale
abstract class FoolSlide(
override val name: String,
override val baseUrl: String,
override val lang: String,
val urlModifier: String = ""
) : ParsedHttpSource() {
protected open val dedupeLatestUpdates = true
override val supportsLatest = true
override fun popularMangaSelector() = "div.group"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl$urlModifier/directory/$page/", headers)
}
val latestUpdatesUrls = HashSet<String>()
override fun latestUpdatesParse(response: Response): MangasPage {
val mp = super.latestUpdatesParse(response)
return if (dedupeLatestUpdates) {
val mangas = mp.mangas.distinctBy { it.url }.filterNot { latestUpdatesUrls.contains(it.url) }
latestUpdatesUrls.addAll(mangas.map { it.url })
MangasPage(mangas, mp.hasNextPage)
} else mp
}
override fun latestUpdatesSelector() = "div.group"
override fun latestUpdatesRequest(page: Int): Request {
if (page == 1) {
latestUpdatesUrls.clear()
}
return GET("$baseUrl$urlModifier/latest/$page/")
}
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()
}
element.select("img").first()?.let {
manga.thumbnail_url = it.absUrl("src").replace("/thumb_", "/")
}
return manga
}
override fun latestUpdatesFromElement(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 popularMangaNextPageSelector() = "div.next"
override fun latestUpdatesNextPageSelector(): String? = "div.next"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val searchHeaders = headersBuilder().add("Content-Type", "application/x-www-form-urlencoded").build()
val form = FormBody.Builder()
.add("search", query)
return POST("$baseUrl$urlModifier/search/", searchHeaders, form.build())
}
override fun searchMangaSelector() = "div.group"
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 mangaDetailsRequest(manga: SManga) = allowAdult(super.mangaDetailsRequest(manga))
open val mangaDetailsInfoSelector = "div.info"
// if there's no image on the details page, get the first page of the first chapter
fun getDetailsThumbnail(document: Document, urlSelector: String = chapterUrlSelector): String? {
return document.select("div.thumbnail img, table.thumb img").firstOrNull()?.attr("abs:src")
?: document.select(chapterListSelector()).last().select(urlSelector).attr("abs:href")
.let { url -> client.newCall(allowAdult(GET(url, headers))).execute() }
.let { response -> pageListParse(response).first().imageUrl }
}
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select(mangaDetailsInfoSelector).firstOrNull()?.html()?.let { infoHtml ->
author = Regex("""(?i)(Author|Autore)</b>:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2)
artist = Regex("""Artist</b>:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(1)
description = Regex("""(?i)(Synopsis|Description|Trama)</b>:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2)
}
thumbnail_url = getDetailsThumbnail(document)
}
}
/**
* Transform a GET request into a POST request that automatically authorizes all adult content
*/
private fun allowAdult(request: Request) = allowAdult(request.url().toString())
private fun allowAdult(url: String): Request {
return POST(
url,
body = FormBody.Builder()
.add("adult", "true")
.build()
)
}
override fun chapterListRequest(manga: SManga) = allowAdult(super.chapterListRequest(manga))
override fun chapterListSelector() = "div.group div.element, div.list div.element"
open val chapterDateSelector = "div.meta_r"
open val chapterUrlSelector = "a[title]"
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select(chapterUrlSelector).first()
val dateElement = element.select(chapterDateSelector).first()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text()
chapter.date_upload = dateElement.text()?.let { parseChapterDate(it.substringAfter(", ")) }
?: 0
return chapter
}
open fun parseChapterDate(date: String): Long? {
val lcDate = date.toLowerCase()
if (lcDate.endsWith(" ago"))
parseRelativeDate(lcDate)?.let { return it }
// Handle 'yesterday' and 'today', using midnight
var relativeDate: Calendar? = null
// Result parsed but no year, copy current year over
when {
lcDate.startsWith("yesterday") -> {
relativeDate = Calendar.getInstance()
relativeDate.add(Calendar.DAY_OF_MONTH, -1) // yesterday
relativeDate.set(Calendar.HOUR_OF_DAY, 0)
relativeDate.set(Calendar.MINUTE, 0)
relativeDate.set(Calendar.SECOND, 0)
relativeDate.set(Calendar.MILLISECOND, 0)
}
lcDate.startsWith("today") -> {
relativeDate = Calendar.getInstance()
relativeDate.set(Calendar.HOUR_OF_DAY, 0)
relativeDate.set(Calendar.MINUTE, 0)
relativeDate.set(Calendar.SECOND, 0)
relativeDate.set(Calendar.MILLISECOND, 0)
}
lcDate.startsWith("tomorrow") -> {
relativeDate = Calendar.getInstance()
relativeDate.add(Calendar.DAY_OF_MONTH, +1) // tomorrow
relativeDate.set(Calendar.HOUR_OF_DAY, 0)
relativeDate.set(Calendar.MINUTE, 0)
relativeDate.set(Calendar.SECOND, 0)
relativeDate.set(Calendar.MILLISECOND, 0)
}
}
relativeDate?.timeInMillis?.let {
return it
}
var result = DATE_FORMAT_1.parseOrNull(date)
for (dateFormat in DATE_FORMATS_WITH_ORDINAL_SUFFIXES) {
if (result == null)
result = dateFormat.parseOrNull(date)
else
break
}
for (dateFormat in DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR) {
if (result == null) {
result = dateFormat.parseOrNull(date)
if (result != null) {
// Result parsed but no year, copy current year over
result = Calendar.getInstance().apply {
time = result!!
set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR))
}.time
}
} else break
}
return result?.time ?: 0L
}
/**
* Parses dates in this form:
* `11 days ago`
*/
private fun parseRelativeDate(date: String): Long? {
val trimmedDate = date.split(" ")
if (trimmedDate[2] != "ago") return null
val number = trimmedDate[0].toIntOrNull() ?: return null
val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix
val now = Calendar.getInstance()
// Map English unit to Java unit
val javaUnit = when (unit) {
"year", "yr" -> Calendar.YEAR
"month" -> Calendar.MONTH
"week", "wk" -> Calendar.WEEK_OF_MONTH
"day" -> Calendar.DAY_OF_MONTH
"hour", "hr" -> Calendar.HOUR
"minute", "min" -> Calendar.MINUTE
"second", "sec" -> Calendar.SECOND
else -> return null
}
now.add(javaUnit, -number)
return now.timeInMillis
}
private fun SimpleDateFormat.parseOrNull(string: String): Date? {
return try {
parse(string)
} catch (e: ParseException) {
null
}
}
override fun pageListRequest(chapter: SChapter) = allowAdult(super.pageListRequest(chapter))
override fun pageListParse(document: Document): List<Page> {
val doc = document.toString()
val jsonstr = doc.substringAfter("var pages = ").substringBefore(";")
val json = JsonParser().parse(jsonstr).asJsonArray
val pages = mutableListOf<Page>()
json.forEach {
// Create dummy element to resolve relative URL
val absUrl = document.createElement("a")
.attr("href", it["url"].asString)
.absUrl("href")
pages.add(Page(pages.size, "", absUrl))
}
return pages
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
companion object {
private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th")
private val DATE_FORMAT_1 = SimpleDateFormat("yyyy.MM.dd", Locale.US)
private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = ORDINAL_SUFFIXES.map {
SimpleDateFormat("dd'$it' MMMM, yyyy", Locale.US)
}
private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR = ORDINAL_SUFFIXES.map {
SimpleDateFormat("dd'$it' MMMM", Locale.US)
}
}
}

View File

@ -0,0 +1,59 @@
package eu.kanade.tachiyomi.multisrc.foolslide
import eu.kanade.tachiyomi.multisrc.ThemeSourceData.SingleLang
import eu.kanade.tachiyomi.multisrc.ThemeSourceData.MultiLang
import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator
class FoolSlideGenerator : ThemeSourceGenerator {
override val themePkg = "foolslide"
override val themeClass = "FoolSlide"
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"),
SingleLang("Silent Sky", "https://reader.silentsky-scans.net", "en"),
SingleLang("Death Toll Scans", "https://reader.deathtollscans.net", "en"),
SingleLang("One Time Scans", "https://reader.otscans.com", "en"),
SingleLang("MangaScouts", "http://onlinereader.mangascouts.org", "de"),
SingleLang("Lilyreader", "https://manga.smuglo.li", "en"),
SingleLang("Evil Flowers", "https://reader.evilflowers.com", "en"),
SingleLang("Русификация", "https://rusmanga.ru", "ru", className = "Russification"),
SingleLang("PowerManga", "https://reader.powermanga.org", "it", className = "PowerMangaIT"),
MultiLang("FoolSlide Customizable", "", listOf("other")),
SingleLang("Menudo-Fansub", "http://www.menudo-fansub.com", "es", className = "MenudoFansub"),
SingleLang("Sense-Scans", "http://sensescans.com", "en", className = "SenseScans"),
SingleLang("Kirei Cake", "https://reader.kireicake.com", "en"),
SingleLang("Mangatellers", "http://www.mangatellers.gr", "en"),
SingleLang("Iskultrip Scans", "https://maryfaye.net", "en"),
SingleLang("Anata no Motokare", "https://motokare.xyz", "en", className = "AnataNoMotokare"),
SingleLang("Yuri-ism", "https://www.yuri-ism.net", "en", className = "YuriIsm"),
SingleLang("Ajia no Scantrad", "https://www.ajianoscantrad.fr", "fr", className = "AjiaNoScantrad"),
SingleLang("Storm in Heaven", "https://www.storm-in-heaven.net", "it", className = "StormInHeaven"),
SingleLang("LupiTeam", "https://lupiteam.net", "it"),
SingleLang("Zandy no Fansub", "https://zandynofansub.aishiteru.org", "en"),
SingleLang("Helvetica Scans", "https://helveticascans.com", "en"),
SingleLang("Kirishima Fansub", "https://www.kirishimafansub.net", "es"),
SingleLang("Baixar Hentai", "https://leitura.baixarhentai.net", "pt-BR", isNsfw = true),
SingleLang("HNI-Scantrad", "https://hni-scantrad.com", "fr", className = "HNIScantrad"),
SingleLang("HNI-Scantrad", "https://hni-scantrad.com", "en", className = "HNIScantradEN"),
SingleLang("The Phoenix Scans", "https://www.phoenixscans.com", "it", className = "PhoenixScans"),
SingleLang("GTO The Great Site", "https://www.gtothegreatsite.net", "it", className = "GTO"),
SingleLang("Fall World Reader", "https://faworeader.altervista.org", "it", className = "FallenWorldOrder"),
SingleLang("NIFTeam", "http://read-nifteam.info", "it"),
SingleLang("TuttoAnimeManga", "https://tuttoanimemanga.net", "it"),
SingleLang("Tortuga Ceviri", "http://tortuga-ceviri.com", "tr"),
SingleLang("Rama", "https://www.ramareader.it", "it"),
SingleLang("Mabushimajo", "http://mabushimajo.com", "tr"),
SingleLang("Hentai Cafe", "https://hentai.cafe", "en", isNsfw = true),
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
FoolSlideGenerator().createAll()
}
}
}