Split FoolSlide Extension (#5840)
* Split FoolSlide Extension * remove FoolSlide * add className * change default_res #5845 * add nsfw * nsfw2
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.fr.ajianoscantrad
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class AjiaNoScantrad : FoolSlide("Ajia no Scantrad", "https://www.ajianoscantrad.fr", "fr", "/reader")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.anatanomotokare
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class AnataNoMotokare : FoolSlide("Anata no Motokare", "https://motokare.xyz", "en", "/reader")
|
@ -0,0 +1,19 @@
|
||||
package eu.kanade.tachiyomi.extension.pt.baixarhentai
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
@Nsfw
|
||||
class BaixarHentai : FoolSlide("Baixar Hentai", "https://leitura.baixarhentai.net", "pt-BR") {
|
||||
// Hardcode the id because the language wasn't specific.
|
||||
override val id: Long = 8908032188831949972
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
title = document.select("h1.title").text()
|
||||
thumbnail_url = getDetailsThumbnail(document, "div.title a")
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 9.6 KiB |
BIN
multisrc/overrides/foolslide/default/res/web_hi_res_512.png
Normal file
After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.fallenworldorder
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class FallenWorldOrder : FoolSlide("Fall World Reader", "https://faworeader.altervista.org", "it", "/slide")
|
@ -0,0 +1,89 @@
|
||||
package eu.kanade.tachiyomi.extension.all.foolslidecustomizable
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.support.v7.preference.EditTextPreference
|
||||
import android.support.v7.preference.PreferenceScreen
|
||||
import android.widget.Toast
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class FoolSlideCustomizableFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
FoolSlideCustomizable(),
|
||||
)
|
||||
}
|
||||
class FoolSlideCustomizable : ConfigurableSource, FoolSlide("FoolSlide Customizable", "", "other") {
|
||||
override val baseUrl: String by lazy { getPrefBaseUrl() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
|
||||
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF_TITLE
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
this.setDefaultValue(DEFAULT_BASEURL)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: $DEFAULT_BASEURL"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit()
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
res
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(baseUrlPref)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val baseUrlPref = EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF_TITLE
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
this.setDefaultValue(DEFAULT_BASEURL)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: $DEFAULT_BASEURL"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit()
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
res
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(baseUrlPref)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the user to include /directory/ in the URL even though we remove it
|
||||
* To increase the chance they input a usable URL
|
||||
*/
|
||||
private fun getPrefBaseUrl() = preferences.getString(BASE_URL_PREF, DEFAULT_BASEURL)!!.substringBefore("/directory")
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_BASEURL = "https://127.0.0.1"
|
||||
private const val BASE_URL_PREF_TITLE = "Example URL: https://domain.com/path_to/directory/"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_NAME}"
|
||||
private const val BASE_URL_PREF_SUMMARY = "Connect to a designated FoolSlide server"
|
||||
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
||||
}
|
||||
}
|
5
multisrc/overrides/foolslide/gto/src/GTO.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.gto
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class GTO : FoolSlide("GTO The Great Site", "https://www.gtothegreatsite.net", "it", "/reader")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.helveticascans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class HelveticaScans : FoolSlide("Helvetica Scans", "https://helveticascans.com", "en", "/r")
|
352
multisrc/overrides/foolslide/hentaicafe/src/HentaiCafe.kt
Normal file
@ -0,0 +1,352 @@
|
||||
package eu.kanade.tachiyomi.extension.en.hentaicafe
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import eu.kanade.tachiyomi.network.asObservable
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import rx.Observable
|
||||
import java.net.URLEncoder
|
||||
|
||||
@Nsfw
|
||||
class HentaiCafe : FoolSlide("Hentai Cafe", "https://hentai.cafe", "en", "/manga") {
|
||||
// We have custom latest updates logic so do not dedupe latest updates
|
||||
override val dedupeLatestUpdates = false
|
||||
|
||||
// Does not support popular manga
|
||||
override fun fetchPopularManga(page: Int) = fetchLatestUpdates(page)
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||
val urlElement = element.select(".entry-thumb").first()
|
||||
if (urlElement != null) {
|
||||
setUrlWithoutDomain(urlElement.attr("href"))
|
||||
thumbnail_url = urlElement.child(0).attr("src")
|
||||
} else {
|
||||
setUrlWithoutDomain(element.select(".entry-title a").attr("href"))
|
||||
}
|
||||
title = element.select(".entry-title").text().trim()
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = ".x-pagination li:last-child a"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = pagedRequest("$baseUrl/", page)
|
||||
|
||||
override fun latestUpdatesSelector() = "article:not(#post-0)"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
title = document.select(".entry-title").text()
|
||||
val contentElement = document.select(".entry-content").first()
|
||||
thumbnail_url = contentElement.child(0).child(0).attr("src")
|
||||
val genres = mutableListOf<String>()
|
||||
document.select(".content a[rel=tag]").forEach { element ->
|
||||
if (!element.attr("href").contains("artist"))
|
||||
genres.add(element.text())
|
||||
else {
|
||||
artist = element.text()
|
||||
author = element.text()
|
||||
}
|
||||
}
|
||||
status = SManga.COMPLETED
|
||||
genre = genres.joinToString(", ")
|
||||
}
|
||||
|
||||
// Note that the reader URL cannot be deduced from the manga URL all the time which is why
|
||||
// we still need to parse the manga info page
|
||||
// Example: https://hentai.cafe/aiya-youngest-daughters-circumstances/
|
||||
override fun chapterListParse(response: Response) = listOf(
|
||||
SChapter.create().apply {
|
||||
// Some URLs wrongly end with "<br />\n" and need to be removed
|
||||
// Example: https://hentai.cafe/hc.fyi/12106
|
||||
setUrlWithoutDomain(response.asJsoup().select("[title=Read]").attr("href").replace("<br />\\s*".toRegex(), ""))
|
||||
name = "Chapter"
|
||||
chapter_number = 1f
|
||||
}
|
||||
)
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
var url: String? = null
|
||||
var queryString: String? = null
|
||||
fun requireNoUrl() = require(url == null && queryString == null) {
|
||||
"You cannot combine filters or use text search with filters!"
|
||||
}
|
||||
|
||||
filters.findInstance<ArtistFilter>()?.let { f ->
|
||||
if (f.state.isNotBlank()) {
|
||||
requireNoUrl()
|
||||
url = "/hc.fyi/artist/${f.state
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(ARTIST_INVALID_CHAR_REGEX, "-")}/"
|
||||
}
|
||||
}
|
||||
filters.findInstance<BookFilter>()?.let { f ->
|
||||
if (f.state) {
|
||||
requireNoUrl()
|
||||
url = "/hc.fyi/category/book/"
|
||||
}
|
||||
}
|
||||
filters.findInstance<TagFilter>()?.let { f ->
|
||||
if (f.state != 0) {
|
||||
requireNoUrl()
|
||||
url = "/hc.fyi/tag/${f.values[f.state].name}/"
|
||||
}
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
requireNoUrl()
|
||||
url = "/"
|
||||
queryString = "s=" + URLEncoder.encode(query, "UTF-8")
|
||||
}
|
||||
|
||||
return url?.let {
|
||||
pagedRequest("$baseUrl$url", page, queryString)
|
||||
} ?: latestUpdatesRequest(page)
|
||||
}
|
||||
|
||||
private fun pagedRequest(url: String, page: Int, queryString: String? = null): Request {
|
||||
// The site redirects page 1 -> url-without-page so we do this redirect early for optimization
|
||||
val builtUrl = if (page == 1) url else "${url}page/$page/"
|
||||
return GET(if (queryString != null) "$builtUrl?$queryString" else builtUrl)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservable().doOnNext { response ->
|
||||
if (!response.isSuccessful) {
|
||||
response.close()
|
||||
// Better error message for invalid artist
|
||||
if (response.code() == 404 &&
|
||||
!filters.findInstance<ArtistFilter>()?.state.isNullOrBlank()
|
||||
)
|
||||
error("Invalid artist!")
|
||||
else throw Exception("HTTP error ${response.code()}")
|
||||
}
|
||||
}
|
||||
.map { response ->
|
||||
searchMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
Filter.Header("Filters cannot be used while searching."),
|
||||
Filter.Header("Only one filter may be used at a time."),
|
||||
Filter.Separator(),
|
||||
ArtistFilter(),
|
||||
BookFilter(),
|
||||
TagFilter()
|
||||
)
|
||||
|
||||
class ArtistFilter : Filter.Text("Artist (must be exact match)")
|
||||
class BookFilter : Filter.CheckBox("Show books only", false)
|
||||
class TagFilter : Filter.Select<Tag>(
|
||||
"Tag",
|
||||
arrayOf(
|
||||
Tag("", "<select>"),
|
||||
Tag("ahegao", "Ahegao"),
|
||||
Tag("anal", "Anal"),
|
||||
Tag("apron", "Apron"),
|
||||
Tag("ashioki", "Ashioki"),
|
||||
Tag("bakunyuu", "Bakunyuu"),
|
||||
Tag("bathroom-sex", "Bathroom sex"),
|
||||
Tag("beauty-mark", "Beauty mark"),
|
||||
Tag("big-ass", "Big ass"),
|
||||
Tag("big-breast", "Big breast"),
|
||||
Tag("big-dick", "Big dick"),
|
||||
Tag("biting", "Biting"),
|
||||
Tag("black-mail", "Blackmail"),
|
||||
Tag("blindfold", "Blindfold"),
|
||||
Tag("blowjob", "Blowjob"),
|
||||
Tag("body-swap", "Body swap"),
|
||||
Tag("bondage", "Bondage"),
|
||||
Tag("booty", "Booty"),
|
||||
Tag("bride", "Bride"),
|
||||
Tag("bukkake", "Bukkake"),
|
||||
Tag("bunny-girl", "Bunny girl"),
|
||||
Tag("busty", "Busty"),
|
||||
Tag("cat", "Cat"),
|
||||
Tag("cat-girl", "Cat girl"),
|
||||
Tag("catgirl", "Catgirl"),
|
||||
Tag("cheating", "Cheating"),
|
||||
Tag("cheerleader", "Cheerleader"),
|
||||
Tag("chikan", "Chikan"),
|
||||
Tag("christmas", "Christmas"),
|
||||
Tag("chubby", "Chubby"),
|
||||
Tag("color", "Color"),
|
||||
Tag("comedy", "Comedy"),
|
||||
Tag("condom", "Condom"),
|
||||
Tag("cosplay", "Cosplay"),
|
||||
Tag("creampie", "Creampie"),
|
||||
Tag("crossdressing", "Crossdressing"),
|
||||
Tag("crotch-tattoo", "Crotch tattoo"),
|
||||
Tag("cunnilingus", "Cunnilingus"),
|
||||
Tag("dark-skin", "Dark skin"),
|
||||
Tag("deepthroat", "Deepthroat"),
|
||||
Tag("defloration", "Defloration"),
|
||||
Tag("devil", "Devil"),
|
||||
Tag("double-penetration", "Double penetration"),
|
||||
Tag("doujin", "Doujin"),
|
||||
Tag("doujinshi", "Doujinshi"),
|
||||
Tag("drama", "Drama"),
|
||||
Tag("drug", "Drug"),
|
||||
Tag("drunk", "Drunk"),
|
||||
Tag("elf", "Elf"),
|
||||
Tag("exhibitionism", "Exhibitionism"),
|
||||
Tag("eyebrows", "Eyebrows"),
|
||||
Tag("eyepatch", "Eyepatch"),
|
||||
Tag("facesitting", "Facesitting"),
|
||||
Tag("fangs", "Fangs"),
|
||||
Tag("fantasy", "Fantasy"),
|
||||
Tag("fellatio", "Fellatio"),
|
||||
Tag("femboy", "Femboy"),
|
||||
Tag("femdom", "Femdom"),
|
||||
Tag("filming", "Filming"),
|
||||
Tag("flat-chest", "Flat chest"),
|
||||
Tag("footjob", "Footjob"),
|
||||
Tag("freckles", "Freckles"),
|
||||
Tag("full-color", "Full color"),
|
||||
Tag("furry", "Furry"),
|
||||
Tag("futanari", "Futanari"),
|
||||
Tag("gangbang", "Gangbang"),
|
||||
Tag("gender-bender", "Gender bender"),
|
||||
Tag("genderbend", "Genderbend"),
|
||||
Tag("girls4m", "Girls4m"),
|
||||
Tag("glasses", "Glasses"),
|
||||
Tag("group", "Group"),
|
||||
Tag("gyaru", "Gyaru"),
|
||||
Tag("hairy", "Hairy"),
|
||||
Tag("hairy-armpit", "Hairy armpit"),
|
||||
Tag("handjob", "Handjob"),
|
||||
Tag("harem", "Harem"),
|
||||
Tag("headphones", "Headphones"),
|
||||
Tag("heart-pupils", "Heart pupils"),
|
||||
Tag("hentai", "Hentai"),
|
||||
Tag("historical", "Historical"),
|
||||
Tag("horns", "Horns"),
|
||||
Tag("horror", "Horror"),
|
||||
Tag("housewife", "Housewife"),
|
||||
Tag("huge-boobs", "Huge-boobs"),
|
||||
Tag("humiliation", "Humiliation"),
|
||||
Tag("idol", "Idol"),
|
||||
Tag("imouto", "Imouto"),
|
||||
Tag("impregnation", "Impregnation"),
|
||||
Tag("incest", "Incest"),
|
||||
Tag("inseki", "Inseki"),
|
||||
Tag("inverted-nipples", "Inverted nipples"),
|
||||
Tag("irrumatio", "Irrumatio"),
|
||||
Tag("isekai", "Isekai"),
|
||||
Tag("kemono-mimi", "Kemono mimi"),
|
||||
Tag("kimono", "Kimono"),
|
||||
Tag("kogal", "Kogal"),
|
||||
Tag("lactation", "Lactation"),
|
||||
Tag("large-breast", "Large breast"),
|
||||
Tag("lingerie", "Lingerie"),
|
||||
Tag("loli", "Loli"),
|
||||
Tag("love-hotel", "Love hotel"),
|
||||
Tag("magical-girl", "Magical girl"),
|
||||
Tag("maid", "Maid"),
|
||||
Tag("masturbation", "Masturbation"),
|
||||
Tag("miko", "Miko"),
|
||||
Tag("milf", "Milf"),
|
||||
Tag("mind-break", "Mind break"),
|
||||
Tag("mind-control", "Mind control"),
|
||||
Tag("monster-girl", "Monster girl"),
|
||||
Tag("muscles", "Muscles"),
|
||||
Tag("nakadashi", "Nakadashi"),
|
||||
Tag("naked-apron", "Naked apron"),
|
||||
Tag("netorare", "Netorare"),
|
||||
Tag("netorase", "Netorase"),
|
||||
Tag("netori", "Netori"),
|
||||
Tag("ninja", "Ninja"),
|
||||
Tag("nun", "Nun"),
|
||||
Tag("nurse", "Nurse"),
|
||||
Tag("office-lady", "Office lady"),
|
||||
Tag("ojousama", "Ojousama"),
|
||||
Tag("old-man", "Old man"),
|
||||
Tag("onani", "Onani"),
|
||||
Tag("oni", "Oni"),
|
||||
Tag("orgasm-denial", "Orgasm denial"),
|
||||
Tag("osananajimi", "Osananajimi"),
|
||||
Tag("pailoli", "Pailoli"),
|
||||
Tag("paizuri", "Paizuri"),
|
||||
Tag("pegging", "Pegging"),
|
||||
Tag("petite", "Petite"),
|
||||
Tag("pettanko", "Pettanko"),
|
||||
Tag("ponytail", "Ponytail"),
|
||||
Tag("pregnant", "Pregnant"),
|
||||
Tag("prositution", "Prositution"),
|
||||
Tag("pubic-hair", "Pubic Hair"),
|
||||
Tag("qipao", "Qipao"),
|
||||
Tag("rape", "Rape"),
|
||||
Tag("reverse-rape", "Reverse rape"),
|
||||
Tag("rimjob", "Rimjob"),
|
||||
Tag("schoolgirl", "Schoolgirl"),
|
||||
Tag("schoolgirl-outfit", "Schoolgirl outfit"),
|
||||
Tag("sci-fi", "Sci-fi"),
|
||||
Tag("senpai", "Senpai"),
|
||||
Tag("sex", "Sex"),
|
||||
Tag("sex-toys", "Sex toys"),
|
||||
Tag("shimapan", "Shimapan"),
|
||||
Tag("shota", "Shota"),
|
||||
Tag("shouta", "Shouta"),
|
||||
Tag("sister", "Sister"),
|
||||
Tag("sleeping", "Sleeping"),
|
||||
Tag("small-breast", "Small breast"),
|
||||
Tag("socks", "Socks"),
|
||||
Tag("spats", "Spats"),
|
||||
Tag("spread", "Spread"),
|
||||
Tag("squirting", "Squirting"),
|
||||
Tag("stocking", "Stocking"),
|
||||
Tag("stockings", "Stockings"),
|
||||
Tag("succubus", "Succubus"),
|
||||
Tag("swimsuit", "Swimsuit"),
|
||||
Tag("swinging", "Swinging"),
|
||||
Tag("tall-girl", "Tall-girl"),
|
||||
Tag("tanlines", "Tanlines"),
|
||||
Tag("teacher", "Teacher"),
|
||||
Tag("tentacles", "Tentacles"),
|
||||
Tag("threesome", "Threesome"),
|
||||
Tag("time-stop", "Time stop"),
|
||||
Tag("tomboy", "Tomboy"),
|
||||
Tag("toys", "Toys"),
|
||||
Tag("trans", "Trans"),
|
||||
Tag("tsundere", "Tsundere"),
|
||||
Tag("twin", "Twin"),
|
||||
Tag("twintails", "Twintails"),
|
||||
Tag("ugly-bastard", "Ugly bastard"),
|
||||
Tag("uncensored", "Uncensored"),
|
||||
Tag("unlimited", "Unlimited"),
|
||||
Tag("urination", "Urination"),
|
||||
Tag("vanilla", "Vanilla"),
|
||||
Tag("virgin", "Virgin"),
|
||||
Tag("vomit", "Vomit"),
|
||||
Tag("voyeurism", "Voyeurism"),
|
||||
Tag("waitress", "Waitress"),
|
||||
Tag("x-ray", "X-Ray"),
|
||||
Tag("yandere", "Yandere"),
|
||||
Tag("yukata", "Yukata"),
|
||||
Tag("yuri", "Yuri")
|
||||
)
|
||||
)
|
||||
|
||||
class Tag(val name: String, private val displayName: String) {
|
||||
override fun toString() = displayName
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Do not include dashes in this regex, this way we can deduplicate dashes
|
||||
private val ARTIST_INVALID_CHAR_REGEX = Regex("[^a-zA-Z0-9]+")
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.fr.hniscantrad
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class HNIScantrad : FoolSlide("HNI-Scantrad", "https://hni-scantrad.com", "fr", "/lel")
|
@ -0,0 +1,41 @@
|
||||
package eu.kanade.tachiyomi.extension.en.hniscantraden
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
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 okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class HNIScantradEN : FoolSlide("HNI-Scantrad", "https://hni-scantrad.com", "en", "/eng/lel") {
|
||||
override val supportsLatest = false
|
||||
override fun popularMangaRequest(page: Int) = GET(baseUrl + urlModifier, headers)
|
||||
override fun popularMangaSelector() = "div.listed"
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("a:has(h3)").let {
|
||||
title = it.text()
|
||||
setUrlWithoutDomain(it.attr("abs:href"))
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl$urlModifier/?manga=${query.replace(" ", "+")}")
|
||||
override fun searchMangaSelector(): String = popularMangaSelector()
|
||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||
override fun chapterListSelector() = "div.theList > a"
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
return SChapter.create().apply {
|
||||
name = element.select("div.chapter b").text()
|
||||
setUrlWithoutDomain(element.attr("abs:href"))
|
||||
}
|
||||
}
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
return Regex("""imageArray\[\d+]='(.*)'""").findAll(response.body()!!.string()).toList().mapIndexed { i, mr ->
|
||||
Page(i, "", "$baseUrl$urlModifier/${mr.groupValues[1]}")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.iskultripscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class IskultripScans : FoolSlide("Iskultrip Scans", "https://maryfaye.net", "en", "/reader")
|
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 23 KiB |
BIN
multisrc/overrides/foolslide/kireicake/res/web_hi_res_512.png
Normal file
After Width: | Height: | Size: 115 KiB |
15
multisrc/overrides/foolslide/kireicake/src/KireiCake.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package eu.kanade.tachiyomi.extension.en.kireicake
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class KireiCake : FoolSlide("Kirei Cake", "https://reader.kireicake.com", "en") {
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
description = document.select("$mangaDetailsInfoSelector li:has(b:contains(description))")
|
||||
.first()?.ownText()?.substringAfter(":")
|
||||
thumbnail_url = getDetailsThumbnail(document)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.es.kirishimafansub
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class KirishimaFansub : FoolSlide("Kirishima Fansub", "https://www.kirishimafansub.net", "es", "/lector")
|
26
multisrc/overrides/foolslide/lupiteam/src/LupiTeam.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package eu.kanade.tachiyomi.extension.it.lupiteam
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class LupiTeam : FoolSlide("LupiTeam", "https://lupiteam.net", "it", "/reader") {
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.select(mangaDetailsInfoSelector).first().text()
|
||||
|
||||
val manga = SManga.create()
|
||||
manga.author = infoElement.substringAfter("Autore: ").substringBefore("Artista: ")
|
||||
manga.artist = infoElement.substringAfter("Artista: ").substringBefore("Target: ")
|
||||
val stato = infoElement.substringAfter("Stato: ").substringBefore("Trama: ").substring(0, 8)
|
||||
manga.status = when (stato) {
|
||||
"In corso" -> SManga.ONGOING
|
||||
"Completa" -> SManga.COMPLETED
|
||||
"Licenzia" -> SManga.LICENSED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
manga.description = infoElement.substringAfter("Trama: ")
|
||||
manga.thumbnail_url = getDetailsThumbnail(document)
|
||||
|
||||
return manga
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.tr.mabushimajo
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class Mabushimajo : FoolSlide("Mabushimajo", "http://mabushimajo.com", "tr", "/onlineokuma")
|
@ -0,0 +1,11 @@
|
||||
package eu.kanade.tachiyomi.extension.en.mangatellers
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Request
|
||||
|
||||
class Mangatellers : FoolSlide("Mangatellers", "http://www.mangatellers.gr", "en", "/reader/reader") {
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl$urlModifier/list/$page/", headers)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.es.menudofansub
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class MenudoFansub : FoolSlide("Menudo-Fansub", "http://www.menudo-fansub.com", "es", "/slide")
|
5
multisrc/overrides/foolslide/nifteam/src/NIFTeam.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.nifteam
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class NIFTeam : FoolSlide("NIFTeam", "http://read-nifteam.info", "it", "/slide")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.phoenixscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class PhoenixScans : FoolSlide("The Phoenix Scans", "https://www.phoenixscans.com", "it", "/reader")
|
5
multisrc/overrides/foolslide/rama/src/Rama.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.rama
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class Rama : FoolSlide("Rama", "https://www.ramareader.it", "it", "/read")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.sensescans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class SenseScans : FoolSlide("Sense-Scans", "http://sensescans.com", "en", "/reader")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.storminheaven
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class StormInHeaven : FoolSlide("Storm in Heaven", "https://www.storm-in-heaven.net", "it", "/reader-sih")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.tr.tortugaceviri
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class TortugaCeviri : FoolSlide("Tortuga Ceviri", "http://tortuga-ceviri.com", "tr", "/okuma")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.it.tuttoanimemanga
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class TuttoAnimeManga : FoolSlide("TuttoAnimeManga", "https://tuttoanimemanga.net", "it", "/slide")
|
5
multisrc/overrides/foolslide/yuriism/src/YuriIsm.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.yuriism
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class YuriIsm : FoolSlide("Yuri-ism", "https://www.yuri-ism.net", "en", "/slide")
|
@ -0,0 +1,5 @@
|
||||
package eu.kanade.tachiyomi.extension.en.zandynofansub
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class ZandynoFansub : FoolSlide("Zandy no Fansub", "https://zandynofansub.aishiteru.org", "en", "/reader")
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|