lib-themesources, split Genkan into single-source extensions (#5154)

* lib themesources copied from SnakeDoc83/tachiyomi-extensions/library

* update to the newer Genkan

* update genkan generator

* GenkanOriginal

* code cleanup

* add all Genkan sources

* generate inside generated-src, res override

* src override

* move overrides out of library

* move overrides to a better place

* remove leftover generated files

* remove leftover generated files

* add generators main class

* comment the code

* Now sources are purely generated

* uncomment generators

* enhance comments

* icons by @as280093

* fix pathing issues

* nullpointerexception proof

* runAllGenerators task

* more flexibility in lib structure, fix a fiew errors

* update github workflows

* correct nonames scans directory name

* rename SK Scans to Sleeping Knight Scans

* fix typo

* update depencencies

* remove defaultRes from dependencies

* fix bug with nsfw

* fix nsfw generation

* themesourcesLibraryVersion is included in build.gradle extVersionCode

* improve javadoc

* fix formatting and language code generation

* comply with #5214

* common dependencies

* rename and move lib/themesources into /multisrc

* use not depricated form

* cleanup runAllGenerators task

* cleanup even more

* oops extra file

* remove test code

* comments

* update docs and refactor

* update docs

* requested changes

* clean up dependencies

* sealed dataClass

* refactor

* refactor string generators

* bring back writeAndroidManifest

* update overrideVersionCode javadoc

* update overrideVersionCode javadoc

* move dependency to extension source

* refactor runAllGenerators

* improve docs

* remove extra file
This commit is contained in:
Aria Moradi
2021-02-06 14:32:04 -08:00
committed by GitHub
parent 5bdd8924b9
commit 3f081f69ac
83 changed files with 512 additions and 136 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Genkan (multiple sources)'
pkgNameSuffix = 'all.genkan'
extClass = '.GenkanFactory'
extVersionCode = 25
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@ -1,243 +0,0 @@
package eu.kanade.tachiyomi.extension.all.genkan
import eu.kanade.tachiyomi.network.GET
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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
abstract class Genkan(
override val name: String,
override val baseUrl: String,
override val lang: String
) : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaSelector() = "div.list-item"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/comics?page=$page", headers)
}
override fun latestUpdatesSelector() = popularMangaSelector()
// Track which manga titles have been added to latestUpdates's MangasPage
private val latestUpdatesTitles = mutableSetOf<String>()
override fun latestUpdatesRequest(page: Int): Request {
if (page == 1) latestUpdatesTitles.clear()
return GET("$baseUrl/latest?page=$page", headers)
}
// To prevent dupes, only add manga to MangasPage if its title is not one we've added already
override fun latestUpdatesParse(response: Response): MangasPage {
val latestManga = mutableListOf<SManga>()
val document = response.asJsoup()
document.select(latestUpdatesSelector()).forEach { element ->
latestUpdatesFromElement(element).let { manga ->
if (manga.title !in latestUpdatesTitles) {
latestManga.add(manga)
latestUpdatesTitles.add(manga.title)
}
}
}
return MangasPage(latestManga, document.select(latestUpdatesNextPageSelector()).hasText())
}
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
element.select("a.list-title").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text()
}
manga.thumbnail_url = styleToUrl(element.select("a.media-content").first())
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "[rel=next]"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/comics?query=$query", headers)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
private fun styleToUrl(element: Element): String {
return element.attr("style").substringAfter("(").substringBefore(")")
.let { if (it.startsWith("http")) it else baseUrl + it }
}
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
title = document.select("div#content h5").first().text()
description = document.select("div.col-lg-9").text().substringAfter("Description ").substringBefore(" Volume")
thumbnail_url = styleToUrl(document.select("div.media a").first())
}
}
override fun chapterListSelector() = "div.col-lg-9 div.flex"
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
val urlElement = element.select("a.item-author")
val chapNum = urlElement.attr("href").split("/").last()
setUrlWithoutDomain(urlElement.attr("href"))
name = if (urlElement.text().contains("Chapter $chapNum")) {
urlElement.text()
} else {
"Ch. $chapNum: ${urlElement.text()}"
}
date_upload = parseChapterDate(element.select("a.item-company").first().text()) ?: 0
}
}
companion object {
val dateFormat by lazy {
SimpleDateFormat("MMM d, yyyy", Locale.US)
}
}
// If the date string contains the word "ago" send it off for relative date parsing otherwise use dateFormat
private fun parseChapterDate(string: String): Long? {
return if ("ago" in string) {
parseRelativeDate(string) ?: 0
} else {
dateFormat.parse(string)?.time ?: 0
}
}
// Subtract relative date (e.g. posted 3 days ago)
private fun parseRelativeDate(date: String): Long? {
val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ")
val calendar = Calendar.getInstance()
when (trimmedDate[1]) {
"year" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) }
"month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) }
"week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) }
"day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }
"hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }
"minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }
"second" -> calendar.apply { add(Calendar.SECOND, 0) }
}
return calendar.timeInMillis
}
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
val allImages = document.select("div#pages-container + script").first().data()
.substringAfter("[").substringBefore("];")
.replace(Regex("""["\\]"""), "")
.split(",")
for (i in allImages.indices) {
pages.add(Page(i, "", allImages[i]))
}
return pages
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
override fun imageRequest(page: Page): Request {
return if (page.imageUrl!!.startsWith("http")) GET(page.imageUrl!!, headers) else GET(baseUrl + page.imageUrl!!, headers)
}
override fun getFilterList() = FilterList()
}
// For sites using the older Genkan CMS that didn't have a search function
abstract class GenkanOriginal(
override val name: String,
override val baseUrl: String,
override val lang: String
) : Genkan(name, baseUrl, lang) {
private var searchQuery = ""
private var searchPage = 1
private var nextPageSelectorElement = Elements()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (page == 1) searchPage = 1
searchQuery = query
return popularMangaRequest(page)
}
override fun searchMangaParse(response: Response): MangasPage {
val searchMatches = mutableListOf<SManga>()
val document = response.asJsoup()
searchMatches.addAll(getMatchesFrom(document))
/* call another function if there's more pages to search
not doing it this way can lead to a false "no results found"
if no matches are found on the first page but there are matches
on subsequent pages */
nextPageSelectorElement = document.select(searchMangaNextPageSelector())
while (nextPageSelectorElement.hasText()) {
searchMatches.addAll(searchMorePages())
}
return MangasPage(searchMatches, false)
}
// search the given document for matches
private fun getMatchesFrom(document: Document): MutableList<SManga> {
val searchMatches = mutableListOf<SManga>()
document.select(searchMangaSelector())
.filter { it.text().contains(searchQuery, ignoreCase = true) }
.map { searchMatches.add(searchMangaFromElement(it)) }
return searchMatches
}
// search additional pages if called
private fun searchMorePages(): MutableList<SManga> {
searchPage++
val nextPage = client.newCall(popularMangaRequest(searchPage)).execute().asJsoup()
val searchMatches = mutableListOf<SManga>()
searchMatches.addAll(getMatchesFrom(nextPage))
nextPageSelectorElement = nextPage.select(searchMangaNextPageSelector())
return searchMatches
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
}

View File

@ -1,38 +0,0 @@
package eu.kanade.tachiyomi.extension.all.genkan
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class GenkanFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
LeviatanScans(),
LeviatanScansES(),
HunlightScans(),
ZeroScans(),
ReaperScans(),
TheNonamesScans(),
HatigarmScans(),
EdelgardeScans(),
SecretScans(),
MethodScans(),
SKScans(),
)
}
/* Genkan class is for the latest version of Genkan CMS
GenkanOriginal is for the initial version of the CMS that didn't have its own search function */
class LeviatanScans : Genkan("Leviatan Scans", "https://leviatanscans.com", "en")
class LeviatanScansES : GenkanOriginal("Leviatan Scans", "https://es.leviatanscans.com", "es")
class HunlightScans : Genkan("Hunlight Scans", "https://hunlight-scans.info", "en")
class ZeroScans : Genkan("ZeroScans", "https://zeroscans.com", "en")
// Search isn't working on Reaper's website, use GenkanOriginal for now
class ReaperScans : GenkanOriginal("Reaper Scans", "https://reaperscans.com", "en")
class TheNonamesScans : Genkan("The Nonames Scans", "https://the-nonames.com", "en")
class HatigarmScans : GenkanOriginal("Hatigarm Scans", "https://hatigarmscanz.net", "en") {
override val versionId = 2
}
class EdelgardeScans : Genkan("Edelgarde Scans", "https://edelgardescans.com", "en")
class SecretScans : GenkanOriginal("SecretScans", "https://secretscans.co", "en")
class MethodScans : Genkan("Method Scans", "https://methodscans.com", "en")
class SKScans : Genkan("Sleeping Knight Scans", "https://skscans.com", "en")