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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 512 additions and 136 deletions

View File

@ -46,6 +46,26 @@ jobs:
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build multi-source library
uses: eskatos/gradle-command-action@v1
with:
build-root-directory: master
wrapper-directory: master
arguments: :multisrc:assembleDebug
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Run multi-source generators
uses: eskatos/gradle-command-action@v1
with:
build-root-directory: master
wrapper-directory: master
arguments: :multisrc:runAllGenerators
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Build extensions
uses: eskatos/gradle-command-action@v1
with:

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ build/
repo/
apk/
gen
generated-src/

View File

@ -0,0 +1,18 @@
// used both in common.gradle and themesources library
dependencies {
// Lib 1.2, but using specific commit so we don't need to bump up the version
compileOnly "com.github.tachiyomiorg:extensions-lib:a596412"
// These are provided by the app itself
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
compileOnly 'io.reactivex:rxjava:1.3.6'
compileOnly 'org.jsoup:jsoup:1.10.2'
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
implementation project(":annotations")
compileOnly project(':duktape-stub')
}

View File

@ -57,23 +57,10 @@ repositories {
}
dependencies {
implementation project(":annotations")
implementation project(":core")
// Lib 1.2, but using specific commit so we don't need to bump up the version
compileOnly "com.github.tachiyomiorg:extensions-lib:a596412"
// These are provided by the app itself
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
compileOnly 'io.reactivex:rxjava:1.3.6'
compileOnly 'org.jsoup:jsoup:1.10.2'
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
compileOnly project(':duktape-stub')
}
apply from: "$rootDir/common-dependencies.gradle"
preBuild.dependsOn(lintKotlin)
lintKotlin.dependsOn(formatKotlin)

49
multisrc/build.gradle.kts Normal file
View File

@ -0,0 +1,49 @@
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdkVersion(Config.compileSdk)
buildToolsVersion(Config.buildTools)
defaultConfig {
minSdkVersion(29)
targetSdkVersion(Config.targetSdk)
}
}
repositories {
mavenCentral()
}
// dependencies
apply("$rootDir/common-dependencies.gradle")
tasks.register("runAllGenerators") {
doLast {
val isWindows = System.getProperty("os.name").toString().toLowerCase().contains("win")
val classPath = (configurations.debugCompileOnly.get().asFileTree.toList() +
listOf(
configurations.androidApis.get().asFileTree.first().absolutePath, // android.jar path
"$projectDir/build/intermediates/aar_main_jar/debug/classes.jar" // jar made from this module
))
.joinToString(if (isWindows) ";" else ":")
val javaPath = System.getProperty("java.home") + "/bin/java" // path of java
val mainClass = "eu.kanade.tachiyomi.multisrc.GeneratorMainKt" // Main class we want to execute
val javaCommand = if (isWindows) {
"\"$javaPath\" -classpath $classPath $mainClass".replace("/", "\\")
} else {
"$javaPath -classpath $classPath $mainClass"
}
val javaProcess = Runtime.getRuntime().exec(javaCommand)
val exitCode = javaProcess.waitFor()
if (exitCode != 0){
throw Exception("Running java failed with exit code: $exitCode")
}
}
}

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,13 @@
package eu.kanade.tachiyomi.extension.all.leviatanscans
import eu.kanade.tachiyomi.multisrc.genkan.Genkan
import eu.kanade.tachiyomi.multisrc.genkan.GenkanOriginal
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class LeviatanScansFactory : SourceFactory {
override fun createSources(): List<Source> = listOf(
Genkan("Leviatan Scans", "https://leviatanscans.com", "en"),
GenkanOriginal("Leviatan Scans", "https://es.leviatanscans.com", "es"),
)
}

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.lib.themesources" />

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.multisrc
import java.io.File
/**
* Finds and calls all `ThemeSourceGenerator`s
*/
fun main(args: Array<String>) {
val userDir = System.getProperty("user.dir")!!
val sourcesDirPath = "$userDir/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc"
val sourcesDir = File(sourcesDirPath)
val directories = sourcesDir.list()!!.filter {
File(sourcesDir, it).isDirectory
}
// find all theme packages
directories.forEach { themeSource ->
// find all XxxGenerator.kt files and invoke main from them
File("$sourcesDirPath/$themeSource").list()!!
.filter {
it.endsWith("Generator.kt")
}.map {// find java class and extract method lists
Class.forName("eu/kanade/tachiyomi/multisrc/$themeSource/$it".replace("/", ".").substringBefore(".kt")).methods.asList()
}
.flatten()
.filter { it.name == "main" }
.forEach { it.invoke(null, emptyArray<String>()) }
}
}

View File

@ -0,0 +1,225 @@
package eu.kanade.tachiyomi.multisrc
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.Locale
/**
* This is meant to be used in place of a factory extension, specifically for what would be a multi-source extension.
* A multi-lang (but not multi-source) extension should still be made as a factory extensiion.
* Use a generator for initial setup of a theme source or when all of the inheritors need a version bump.
* Source list (val sources) should be kept up to date.
*/
interface ThemeSourceGenerator {
/**
* The class that the sources inherit from.
*/
val themeClass: String
/**
* The package that contains themeClass.
*/
val themePkg: String
/**
* Base theme version, starts with 1 and should be increased when based theme class changes
*/
val baseVersionCode: Int
/**
* The list of sources to be created or updated.
*/
val sources: List<ThemeSourceData>
fun createAll() {
val userDir = System.getProperty("user.dir")!!
sources.forEach { source ->
createGradleProject(source, themePkg, themeClass, baseVersionCode, userDir)
}
}
companion object {
private fun pkgNameSuffix(source: ThemeSourceData, separator: String): String {
return if (source is ThemeSourceData.SingleLang)
listOf(source.lang.substringBefore("-"), source.pkgName).joinToString(separator)
else
listOf("all", source.pkgName).joinToString(separator)
}
private fun themeSuffix(themePkg: String, separator: String): String {
return listOf("eu", "kanade", "tachiyomi", "multisrc", themePkg).joinToString(separator)
}
private fun writeGradle(gradle: File, source: ThemeSourceData, baseVersionCode: Int) {
gradle.writeText("""apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = '${source.name}'
pkgNameSuffix = '${pkgNameSuffix(source, ".")}'
extClass = '.${source.className}'
extVersionCode = ${baseVersionCode + source.overrideVersionCode + multisrcLibraryVersion}
libVersion = '1.2'
${if (source.isNsfw) " containsNsfw = true\n" else ""}}
apply from: "${'$'}rootDir/common.gradle"
"""
)
}
private fun writeAndroidManifest(androidManifestFile: File) {
androidManifestFile.writeText(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<manifest package=\"eu.kanade.tachiyomi.extension\" />\n"
)
}
/**
* Clears directory recursively
*/
private fun purgeDirectory(dir: File) {
for (file in dir.listFiles()!!) {
if (file.isDirectory) purgeDirectory(file)
file.delete()
}
}
fun createGradleProject(source: ThemeSourceData, themePkg: String, themeClass: String, baseVersionCode: Int, userDir: String) {
val projectRootPath = "$userDir/generated-src/${pkgNameSuffix(source, "/")}"
val projectSrcPath = "$projectRootPath/src/eu/kanade/tachiyomi/extension/${pkgNameSuffix(source, "/")}"
val overridesPath = "$userDir/multisrc/overrides" // userDir = tachiyomi-extensions project root path
val resOverridesPath = "$overridesPath/res/$themePkg"
val srcOverridesPath = "$overridesPath/src/$themePkg"
val projectGradleFile = File("$projectRootPath/build.gradle")
val projectAndroidManifestFile = File("$projectRootPath/AndroidManifest.xml")
File(projectRootPath).let { projectRootFile ->
println("Working on $source")
projectRootFile.mkdirs()
// remove everything from past runs
purgeDirectory(projectRootFile)
writeGradle(projectGradleFile, source, baseVersionCode)
writeAndroidManifest(projectAndroidManifestFile)
writeSourceFiles(projectSrcPath, srcOverridesPath, source, themePkg, themeClass)
copyThemeClasses(userDir, themePkg, projectRootPath)
copyResFiles(resOverridesPath, source, projectRootPath)
}
}
private fun copyThemeClasses(userDir: String, themePkg: String, projectRootPath: String) {
val themeSrcPath = "$userDir/multisrc/src/main/java/${themeSuffix(themePkg, "/")}"
val themeSrcFile = File(themeSrcPath)
val themeDestPath = "$projectRootPath/src/${themeSuffix(themePkg, "/")}"
val themeDestFile = File(themeDestPath)
themeDestFile.mkdirs()
themeSrcFile.list()!!
.filter { it.endsWith(".kt") && !it.endsWith("Generator.kt") }
.forEach { Files.copy(File("$themeSrcPath/$it").toPath(), File("$themeDestPath/$it").toPath(), StandardCopyOption.REPLACE_EXISTING) }
}
private fun copyResFiles(resOverridesPath: String, source: ThemeSourceData, projectRootPath: String): Any {
// check if res override exists if not copy default res
val resOverride = File("$resOverridesPath/${source.pkgName}")
return if (resOverride.exists())
resOverride.copyRecursively(File("$projectRootPath/res"))
else
File("$resOverridesPath/default").let { res ->
if (res.exists()) res.copyRecursively(File("$projectRootPath/res"))
}
}
private fun writeSourceFiles(projectSrcPath: String, srcOverridePath: String, source: ThemeSourceData, themePkg: String, themeClass: String) {
val projectSrcFile = File(projectSrcPath)
projectSrcFile.mkdirs()
val srcOverride = File("$srcOverridePath/${source.pkgName}")
if (srcOverride.exists())
srcOverride.copyRecursively(projectSrcFile)
else
writeSourceClass(projectSrcFile, source, themePkg, themeClass)
}
private fun writeSourceClass(classPath: File, source: ThemeSourceData, themePkg: String, themeClass: String) {
fun factoryClassText(): String {
val sourceListString =
(source as ThemeSourceData.MultiLang).lang.map {
" $themeClass(\"${source.name}\", \"${source.baseUrl}\", \"$it\"),"
}.joinToString("\n")
return """class ${source.className} : SourceFactory {
override fun createSources(): List<Source> = listOf(
$sourceListString
)
}"""
}
File("$classPath/${source.className}.kt").writeText(
"""package eu.kanade.tachiyomi.extension.${pkgNameSuffix(source, ".")}
${if (source.isNsfw) "\nimport eu.kanade.tachiyomi.annotations.Nsfw" else ""}
import eu.kanade.tachiyomi.multisrc.$themePkg.$themeClass
${if (source is ThemeSourceData.MultiLang)
"""import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
"""
else ""}${if (source.isNsfw) "\n@Nsfw" else ""}
${if (source is ThemeSourceData.SingleLang) {
"class ${source.className} : $themeClass(\"${source.name}\", \"${source.baseUrl}\", \"${source.lang}\")\n"
} else
factoryClassText()
}
""")
}
sealed class ThemeSourceData {
abstract val name: String
abstract val baseUrl: String
abstract val isNsfw: Boolean
abstract val className: String
abstract val pkgName: String
/**
* overrideVersionCode defaults to 0, if a source changes their source override code or
* a previous existing source suddenly needs source code overrides, overrideVersionCode
* should be increased.
* When a new source is added with overrides, overrideVersionCode should still be set to 0
*
* Note: source code overrides are located in "multisrc/overrides/src/<themeName>/<sourceName>"
*/
abstract val overrideVersionCode: Int
data class SingleLang(
override val name: String,
override val baseUrl: String,
val lang: String,
override val isNsfw: Boolean = false,
override val className: String = name.replace(" ", ""),
override val pkgName: String = className.toLowerCase(Locale.ENGLISH),
override val overrideVersionCode: Int = 0,
) : ThemeSourceData()
data class MultiLang(
override val name: String,
override val baseUrl: String,
val lang: List<String>,
override val isNsfw: Boolean = false,
override val className: String = name.replace(" ", "") + "Factory",
override val pkgName: String = className.substringBefore("Factory").toLowerCase(Locale.ENGLISH),
override val overrideVersionCode: Int = 0,
) : ThemeSourceData()
}
}
}
/**
* This variable should be increased when the multisrc library changes in a way that prompts global extension upgrade
*/
const val multisrcLibraryVersion = 0

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.genkan
package eu.kanade.tachiyomi.multisrc.genkan
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
@ -8,17 +8,17 @@ 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 java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
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(
open class Genkan(
override val name: String,
override val baseUrl: String,
override val lang: String
@ -178,66 +178,3 @@ abstract class Genkan(
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

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.multisrc.genkan
import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator
import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator.Companion.ThemeSourceData
class GenkanGenerator : ThemeSourceGenerator {
override val themePkg = "genkan"
override val themeClass = "Genkan"
override val baseVersionCode: Int = 1
override val sources = listOf(
ThemeSourceData.MultiLang("Leviatan Scans", "https://leviatanscans.com", listOf("en", "es"),
className = "LeviatanScansFactory", pkgName = "leviatanscans", overrideVersionCode = 1),
ThemeSourceGenerator.Companion.ThemeSourceData.SingleLang("Hunlight Scans", "https://hunlight-scans.info", "en"),
ThemeSourceData.SingleLang("ZeroScans", "https://zeroscans.com", "en"),
ThemeSourceData.SingleLang("The Nonames Scans", "https://the-nonames.com", "en"),
ThemeSourceData.SingleLang("Edelgarde Scans", "https://edelgardescans.com", "en"),
ThemeSourceData.SingleLang("Method Scans", "https://methodscans.com", "en"),
ThemeSourceData.SingleLang("Sleeping Knight Scans", "https://skscans.com", "en")
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
GenkanGenerator().createAll()
}
}
}

View File

@ -0,0 +1,75 @@
package eu.kanade.tachiyomi.multisrc.genkan
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
/**
* For sites using the older Genkan CMS that didn't have a search function
*/
open 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

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.multisrc.genkan
import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator
import eu.kanade.tachiyomi.multisrc.ThemeSourceGenerator.Companion.ThemeSourceData
class GenkanOriginalGenerator : ThemeSourceGenerator {
override val themePkg = "genkan"
override val themeClass = "GenkanOriginal"
override val baseVersionCode: Int = 1
override val sources = listOf(
ThemeSourceData.SingleLang("Reaper Scans", "https://reaperscans.com", "en"),
ThemeSourceData.SingleLang("Hatigarm Scans", "https://hatigarmscanz.net", "en"),
ThemeSourceData.SingleLang("SecretScans", "https://secretscans.co", "en"),
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
GenkanOriginalGenerator().createAll()
}
}
}

View File

@ -10,6 +10,9 @@ project(":duktape-stub").projectDir = File("lib/duktape-stub")
include(":lib-dataimage")
project(":lib-dataimage").projectDir = File("lib/dataimage")
include(":multisrc")
project(":multisrc").projectDir = File("multisrc")
// Loads all extensions
File(rootDir, "src").eachDir { dir ->
dir.eachDir { subdir ->
@ -18,6 +21,14 @@ File(rootDir, "src").eachDir { dir ->
project(name).projectDir = File("src/${dir.name}/${subdir.name}")
}
}
// Loads generated extensions from multisrc
File(rootDir, "generated-src").eachDir { dir ->
dir.eachDir { subdir ->
val name = ":${dir.name}-${subdir.name}"
include(name)
project(name).projectDir = File("generated-src/${dir.name}/${subdir.name}")
}
}
// Use this to load a single extension during development
// val lang = "all"

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"

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")