Update FoolSlide multisrc (#8301)

* FoolSlide code cleanup

* Add adult content preference

* Update The Cat Scans URL & add icons

* Update Hyakuro URL

* Update Mangatellers URL & add icons

* Update English HNI-Scantrad URL

* Move LetItGo Scans to FoolSlide
This commit is contained in:
ObserverOfTime 2021-07-29 19:20:43 +03:00 committed by GitHub
parent 959f64ff8c
commit 95c8d93798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 128 additions and 202 deletions

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.letitgoscans
import eu.kanade.tachiyomi.multisrc.comicake.ComiCake
class LetItGoScans : ComiCake("LetItGo Scans", "https://reader.letitgo.scans.today", "en", "/")

View File

@ -1,30 +1,29 @@
package eu.kanade.tachiyomi.extension.pt.baixarhentai
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.OkHttpClient
import org.jsoup.nodes.Document
import java.util.concurrent.TimeUnit
@Nsfw
class BaixarHentai : FoolSlide(
"Baixar Hentai",
"https://leitura.baixarhentai.net",
"pt-BR"
) {
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 val id = 8908032188831949972
override val client: OkHttpClient = super.client.newBuilder()
override val client = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h1.title").text()
thumbnail_url = getDetailsThumbnail(document, "div.title a")
}
}
// Always show adult content
override val allowAdult = true
override fun setupPreferenceScreen(screen: PreferenceScreen) {}
}

View File

@ -1,34 +1,28 @@
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.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.source.ConfigurableSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import eu.kanade.tachiyomi.source.SourceFactory
class FoolSlideCustomizableFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
FoolSlideCustomizable(),
)
override fun createSources() = 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)
class FoolSlideCustomizable : FoolSlide("FoolSlide Customizable", "", "other") {
override val baseUrl: String by lazy {
preferences.getString(BASE_URL_PREF, DEFAULT_BASEURL)!!.substringBefore("/directory")
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen)
EditTextPreference(screen.context).apply {
key = BASE_URL_PREF_TITLE
title = BASE_URL_PREF_TITLE
summary = BASE_URL_PREF_SUMMARY
this.setDefaultValue(DEFAULT_BASEURL)
setDefaultValue(DEFAULT_BASEURL)
dialogTitle = BASE_URL_PREF_TITLE
dialogMessage = "Default: $DEFAULT_BASEURL"
@ -42,17 +36,9 @@ class FoolSlideCustomizable : ConfigurableSource, FoolSlide("FoolSlide Customiza
false
}
}
}.let(screen::addPreference)
}
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/"

View File

@ -1,50 +1,19 @@
package eu.kanade.tachiyomi.extension.all.hniscantrad
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
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 HNIScantradFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
HNIScantradFR(),
HNIScantradEN(),
)
override fun createSources() = listOf(HNIScantradFR(), HNIScantradEN())
}
class HNIScantradFR : FoolSlide("HNI-Scantrad", "https://hni-scantrad.com", "fr", "/lel")
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]}")
}
}
class HNIScantradFR : FoolSlide("HNI-Scantrad", "https://hni-scantrad.com", "fr", "/lel") {
override fun chapterListParse(response: Response) =
super.chapterListParse(response).filter { "/fr/" in it.url }
}
class HNIScantradEN : FoolSlide("HNI-Scantrad", "https://hni-scantrad.com", "en", "/lel") {
override fun chapterListParse(response: Response) =
super.chapterListParse(response).filter { "/en-us/" in it.url }
}

View File

@ -4,12 +4,10 @@ import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
class Hyakuro : FoolSlide("Hyakuro", "https://hyakuro.com/reader/", "en") {
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
class Hyakuro : FoolSlide("Hyakuro", "https://hyakuro.com", "en", "/reader") {
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
description = document.select("$mangaDetailsInfoSelector li:has(b:contains(description))")
.first()?.ownText()?.substringAfter(":")
thumbnail_url = getDetailsThumbnail(document)
}
}
}

View File

@ -5,11 +5,9 @@ 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 {
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
description = document.select("$mangaDetailsInfoSelector li:has(b:contains(description))")
.first()?.ownText()?.substringAfter(":")
thumbnail_url = getDetailsThumbnail(document)
}
}
}

View File

@ -1,18 +1,15 @@
package eu.kanade.tachiyomi.extension.fr.lecercleduscan
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
import java.util.Locale
class LeCercleDuScan : FoolSlide("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr") {
override fun parseChapterDate(date: String): Long? {
val dateToEnglish = when (val lcDate = date.toLowerCase()) {
override fun parseChapterDate(date: String) = super.parseChapterDate(
when (val lcDate = date.toLowerCase(Locale.FRENCH)) {
"hier" -> "yesterday"
"aujourd'hui" -> "today"
"demain" -> "tomorrow"
else -> lcDate
}
return super.parseChapterDate(dateToEnglish)
}
)
}

View File

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 355 KiB

View File

@ -5,22 +5,17 @@ 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 {
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
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) {
author = infoElement.substringAfter("Autore: ").substringBefore("Artista: ")
artist = infoElement.substringAfter("Artista: ").substringBefore("Target: ")
status = when (infoElement.substringAfter("Stato: ").substringBefore("Trama: ").take(8)) {
"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
description = infoElement.substringAfter("Trama: ")
thumbnail_url = getDetailsThumbnail(document)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,11 +1,5 @@
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)
}
}
class Mangatellers : FoolSlide("Mangatellers", "https://reader.mangatellers.gr", "en")

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -12,7 +12,6 @@ class ComiCakeGenerator : ThemeSourceGenerator {
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang("LetItGo Scans", "https://reader.letitgo.scans.today", "en"),
SingleLang("WhimSubs", "https://whimsubs.xyz", "en")
)

View File

@ -1,7 +1,13 @@
package eu.kanade.tachiyomi.multisrc.foolslide
import android.app.Application
import android.os.Build
import androidx.preference.CheckBoxPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -17,26 +23,24 @@ import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
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
open val urlModifier: String = ""
) : ConfigurableSource, ParsedHttpSource() {
override val supportsLatest = true
private val json: Json by injectLazy()
private val json by lazy { Injekt.get<Json>() }
override fun popularMangaSelector() = "div.group"
@ -44,48 +48,39 @@ abstract class FoolSlide(
return GET("$baseUrl$urlModifier/directory/$page/", headers)
}
val latestUpdatesUrls = HashSet<String>()
private val latestUpdatesUrls = mutableSetOf<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
return mp.copy(
mp.mangas.distinctBy { it.url }.filter {
latestUpdatesUrls.add(it.url)
}
)
}
override fun latestUpdatesSelector() = "div.group"
override fun latestUpdatesRequest(page: Int): Request {
if (page == 1) {
latestUpdatesUrls.clear()
}
if (page == 1) latestUpdatesUrls.clear()
return GET("$baseUrl$urlModifier/latest/$page/")
}
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
element.select("a[title]").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text()
setUrlWithoutDomain(it.attr("href"))
title = it.text()
}
element.select("img").first()?.let {
manga.thumbnail_url = it.absUrl("src").replace("/thumb_", "/")
thumbnail_url = it.absUrl("src").replace("/thumb_", "/")
}
}
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
element.select("a[title]").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text()
setUrlWithoutDomain(it.attr("href"))
title = it.text()
}
return manga
}
override fun popularMangaNextPageSelector() = "div.next"
@ -94,11 +89,8 @@ abstract class FoolSlide(
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())
val form = FormBody.Builder().add("search", query).build()
return POST("$baseUrl$urlModifier/search/", searchHeaders, form)
}
override fun searchMangaSelector() = "div.group"
@ -116,18 +108,17 @@ abstract class FoolSlide(
override fun mangaDetailsRequest(manga: SManga) = allowAdult(super.mangaDetailsRequest(manga))
open val mangaDetailsInfoSelector = "div.info"
protected 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? {
protected 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 { url -> client.newCall(allowAdult(GET(url))).execute() }
.let { response -> pageListParse(response).first().imageUrl }
}
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
override fun mangaDetailsParse(document: Document) = 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)
@ -135,42 +126,34 @@ abstract class FoolSlide(
}
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())
protected open val allowAdult: Boolean
get() = preferences.getBoolean("adult", true)
private fun allowAdult(url: String): Request {
return POST(
url,
body = FormBody.Builder()
.add("adult", "true")
.build()
)
private fun allowAdult(request: Request): Request {
val form = FormBody.Builder().add("adult", allowAdult.toString()).build()
return POST(request.url.toString(), headers, form)
}
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"
protected open val chapterDateSelector = "div.meta_r"
open val chapterUrlSelector = "a[title]"
protected open val chapterUrlSelector = "a[title]"
override fun chapterFromElement(element: Element): SChapter {
override fun chapterFromElement(element: Element) = SChapter.create().apply {
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
setUrlWithoutDomain(urlElement.attr("href"))
name = urlElement.text()
date_upload = dateElement.text()?.let {
parseChapterDate(it.substringAfter(", "))
} ?: 0
}
open fun parseChapterDate(date: String): Long? {
protected open fun parseChapterDate(date: String): Long? {
val lcDate = date.toLowerCase(Locale.ROOT)
if (lcDate.endsWith(" ago"))
parseRelativeDate(lcDate)?.let { return it }
@ -279,19 +262,33 @@ abstract class FoolSlide(
val doc = document.toString()
val jsonStr = doc.substringAfter("var pages = ").substringBefore(";")
val pages = json.parseToJsonElement(jsonStr).jsonArray
return pages.mapIndexed { i, jsonEl ->
// Create dummy element to resolve relative URL
val absUrl = document.createElement("a")
.attr("href", jsonEl.jsonObject["url"]!!.jsonPrimitive.content)
.absUrl("href")
Page(i, "", absUrl)
}
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
protected val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)!!
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
CheckBoxPreference(screen.context).apply {
key = "adult"
summary = "Show adult content"
setDefaultValue(true)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}.let(screen::addPreference)
}
companion object {
private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th")
private val DATE_FORMAT_1 = SimpleDateFormat("yyyy.MM.dd", Locale.US)

View File

@ -10,10 +10,10 @@ class FoolSlideGenerator : ThemeSourceGenerator {
override val themeClass = "FoolSlide"
override val baseVersionCode: Int = 2
override val baseVersionCode: Int = 3
override val sources = listOf(
SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"),
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("MangaScouts", "http://onlinereader.mangascouts.org", "de"),
@ -25,7 +25,7 @@ class FoolSlideGenerator : ThemeSourceGenerator {
SingleLang("Menudo-Fansub", "https://www.menudo-fansub.com", "es", className = "MenudoFansub", overrideVersionCode = 1),
SingleLang("Sense-Scans", "https://sensescans.com", "en", className = "SenseScans", overrideVersionCode = 1),
SingleLang("Kirei Cake", "https://reader.kireicake.com", "en"),
SingleLang("Mangatellers", "http://www.mangatellers.gr", "en"),
SingleLang("Mangatellers", "https://reader.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"),
@ -42,10 +42,9 @@ class FoolSlideGenerator : ThemeSourceGenerator {
SingleLang("Tortuga Ceviri", "http://tortuga-ceviri.com", "tr"),
SingleLang("Rama", "https://www.ramareader.it", "it"),
SingleLang("Mabushimajo", "http://mabushimajo.com", "tr"),
SingleLang("Hyakuro", "https://hyakuro.com/reader", "en"),
SingleLang("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr", className = "LeCercleDuScan", overrideVersionCode = 1)
//Sites that are down
//SingleLang("One Time Scans", "https://reader.otscans.com", "en"),
SingleLang("Hyakuro", "https://hyakuro.com", "en"),
SingleLang("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr", className = "LeCercleDuScan", overrideVersionCode = 1),
SingleLang("LetItGo Scans", "https://reader.letitgo.scans.today", "en", overrideVersionCode = 1)
)
companion object {