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
|
@ -46,6 +46,26 @@ jobs:
|
||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
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
|
- name: Build extensions
|
||||||
uses: eskatos/gradle-command-action@v1
|
uses: eskatos/gradle-command-action@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -8,4 +8,5 @@ build/
|
||||||
*.iml
|
*.iml
|
||||||
repo/
|
repo/
|
||||||
apk/
|
apk/
|
||||||
gen
|
gen
|
||||||
|
generated-src/
|
|
@ -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')
|
||||||
|
}
|
|
@ -57,23 +57,10 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":annotations")
|
|
||||||
implementation project(":core")
|
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)
|
preBuild.dependsOn(lintKotlin)
|
||||||
lintKotlin.dependsOn(formatKotlin)
|
lintKotlin.dependsOn(formatKotlin)
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 51 KiB |
|
@ -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"),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="eu.kanade.tachiyomi.lib.themesources" />
|
|
@ -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>()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.select.Elements
|
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 name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
override val lang: String
|
override val lang: String
|
||||||
|
@ -178,66 +178,3 @@ abstract class Genkan(
|
||||||
|
|
||||||
override fun getFilterList() = FilterList()
|
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()
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,9 @@ project(":duktape-stub").projectDir = File("lib/duktape-stub")
|
||||||
include(":lib-dataimage")
|
include(":lib-dataimage")
|
||||||
project(":lib-dataimage").projectDir = File("lib/dataimage")
|
project(":lib-dataimage").projectDir = File("lib/dataimage")
|
||||||
|
|
||||||
|
include(":multisrc")
|
||||||
|
project(":multisrc").projectDir = File("multisrc")
|
||||||
|
|
||||||
// Loads all extensions
|
// Loads all extensions
|
||||||
File(rootDir, "src").eachDir { dir ->
|
File(rootDir, "src").eachDir { dir ->
|
||||||
dir.eachDir { subdir ->
|
dir.eachDir { subdir ->
|
||||||
|
@ -18,6 +21,14 @@ File(rootDir, "src").eachDir { dir ->
|
||||||
project(name).projectDir = File("src/${dir.name}/${subdir.name}")
|
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
|
// Use this to load a single extension during development
|
||||||
// val lang = "all"
|
// val lang = "all"
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -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"
|
|
|
@ -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")
|
|