Removed other sources

This commit is contained in:
Mio.
2025-06-28 00:06:48 +02:00
parent 965c2a4f43
commit 8ddfe82fd6
5479 changed files with 789 additions and 22912 deletions

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
/gradlew linguist-generated
/gradlew.bat linguist-generated
* text=auto eol=lf
*.bat text eol=crlf

4
.github/always_build.json vendored Normal file
View File

@@ -0,0 +1,4 @@
[
"ko.newtoki",
"ko.wolfdotcom"
]

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

88
.github/scripts/create-repo.py vendored Normal file
View File

@@ -0,0 +1,88 @@
import json
import os
import re
import subprocess
from pathlib import Path
from zipfile import ZipFile
PACKAGE_NAME_REGEX = re.compile(r"package: name='([^']+)'")
VERSION_CODE_REGEX = re.compile(r"versionCode='([^']+)'")
VERSION_NAME_REGEX = re.compile(r"versionName='([^']+)'")
IS_NSFW_REGEX = re.compile(r"'tachiyomi.extension.nsfw' value='([^']+)'")
APPLICATION_LABEL_REGEX = re.compile(r"^application-label:'([^']+)'", re.MULTILINE)
APPLICATION_ICON_320_REGEX = re.compile(r"^application-icon-320:'([^']+)'", re.MULTILINE)
LANGUAGE_REGEX = re.compile(r"tachiyomi-([^.]+)")
*_, ANDROID_BUILD_TOOLS = (Path(os.environ["ANDROID_HOME"]) / "build-tools").iterdir()
REPO_DIR = Path("repo")
REPO_APK_DIR = REPO_DIR / "apk"
REPO_ICON_DIR = REPO_DIR / "icon"
REPO_ICON_DIR.mkdir(parents=True, exist_ok=True)
with open("output.json", encoding="utf-8") as f:
inspector_data = json.load(f)
index_min_data = []
for apk in REPO_APK_DIR.iterdir():
badging = subprocess.check_output(
[
ANDROID_BUILD_TOOLS / "aapt",
"dump",
"--include-meta-data",
"badging",
apk,
]
).decode()
package_info = next(x for x in badging.splitlines() if x.startswith("package: "))
package_name = PACKAGE_NAME_REGEX.search(package_info).group(1)
application_icon = APPLICATION_ICON_320_REGEX.search(badging).group(1)
with ZipFile(apk) as z, z.open(application_icon) as i, (
REPO_ICON_DIR / f"{package_name}.png"
).open("wb") as f:
f.write(i.read())
language = LANGUAGE_REGEX.search(apk.name).group(1)
sources = inspector_data[package_name]
if len(sources) == 1:
source_language = sources[0]["lang"]
if (
source_language != language
and source_language not in {"all", "other"}
and language not in {"all", "other"}
):
language = source_language
common_data = {
"name": APPLICATION_LABEL_REGEX.search(badging).group(1),
"pkg": package_name,
"apk": apk.name,
"lang": language,
"code": int(VERSION_CODE_REGEX.search(package_info).group(1)),
"version": VERSION_NAME_REGEX.search(package_info).group(1),
"nsfw": int(IS_NSFW_REGEX.search(badging).group(1)),
}
min_data = {
**common_data,
"sources": [],
}
for source in sources:
min_data["sources"].append(
{
"name": source["name"],
"lang": source["lang"],
"id": source["id"],
"baseUrl": source["baseUrl"],
}
)
index_min_data.append(min_data)
with REPO_DIR.joinpath("index.min.json").open("w", encoding="utf-8") as index_file:
json.dump(index_min_data, index_file, ensure_ascii=False, separators=(",", ":"))

View File

@@ -0,0 +1,113 @@
import itertools
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import NoReturn
EXTENSION_REGEX = re.compile(r"^src/(?P<lang>\w+)/(?P<extension>\w+)")
MULTISRC_LIB_REGEX = re.compile(r"^lib-multisrc/(?P<multisrc>\w+)")
LIB_REGEX = re.compile(r"^lib/(?P<lib>\w+)")
MODULE_REGEX = re.compile(r"^:src:(?P<lang>\w+):(?P<extension>\w+)$")
CORE_FILES_REGEX = re.compile(
r"^(buildSrc/|core/|gradle/|build\.gradle\.kts|common\.gradle|gradle\.properties|settings\.gradle\.kts|utils/)"
)
def run_command(command: str) -> str:
result = subprocess.run(command, capture_output=True, text=True, shell=True)
if result.returncode != 0:
print(result.stderr.strip())
sys.exit(result.returncode)
return result.stdout.strip()
def get_module_list(ref: str) -> tuple[list[str], list[str]]:
changed_files = run_command(f"git diff --name-only {ref}").splitlines()
modules = set()
libs = set()
deleted = set()
core_files_changed = False
for file in map(lambda x: Path(x).as_posix(), changed_files):
if CORE_FILES_REGEX.search(file):
core_files_changed = True
elif match := EXTENSION_REGEX.search(file):
lang = match.group("lang")
extension = match.group("extension")
if Path("src", lang, extension).is_dir():
modules.add(f':src:{lang}:{extension}')
deleted.add(f"{lang}.{extension}")
elif match := MULTISRC_LIB_REGEX.search(file):
multisrc = match.group("multisrc")
if Path("lib-multisrc", multisrc).is_dir():
libs.add(f":lib-multisrc:{multisrc}:printDependentExtensions")
elif match := LIB_REGEX.search(file):
lib = match.group("lib")
if Path("lib", lib).is_dir():
libs.add(f":lib:{lib}:printDependentExtensions")
def is_extension_module(module: str) -> bool:
if not (match := MODULE_REGEX.search(module)):
return False
lang = match.group("lang")
extension = match.group("extension")
deleted.add(f"{lang}.{extension}")
return True
if len(libs) != 0 and not core_files_changed:
modules.update([
module for module in
run_command("./gradlew -q " + " ".join(libs)).splitlines()
if is_extension_module(module)
])
if os.getenv("IS_PR_CHECK") != "true" and not core_files_changed:
with Path(".github/always_build.json").open() as always_build_file:
always_build = json.load(always_build_file)
for extension in always_build:
modules.add(":src:" + extension.replace(".", ":"))
deleted.add(extension)
if core_files_changed:
(all_modules, all_deleted) = get_all_modules()
modules.update(all_modules)
deleted.update(all_deleted)
return list(modules), list(deleted)
def get_all_modules() -> tuple[list[str], list[str]]:
modules = []
deleted = []
for lang in Path("src").iterdir():
for extension in lang.iterdir():
modules.append(f":src:{lang.name}:{extension.name}")
deleted.append(f"{lang.name}.{extension.name}")
return modules, deleted
def main() -> NoReturn:
_, ref, build_type = sys.argv
modules, deleted = get_module_list(ref)
chunked = {
"chunk": [
{"number": i + 1, "modules": modules}
for i, modules in
enumerate(itertools.batched(
map(lambda x: f"{x}:assemble{build_type}", modules),
int(os.getenv("CI_CHUNK_SIZE", 65))
))
]
}
print(f"Module chunks to build:\n{json.dumps(chunked, indent=2)}\n\nModule to delete:\n{json.dumps(deleted, indent=2)}")
if os.getenv("CI") == "true":
with open(os.getenv("GITHUB_OUTPUT"), 'a') as out_file:
out_file.write(f"matrix={json.dumps(chunked)}\n")
out_file.write(f"delete={json.dumps(deleted)}\n")
if __name__ == '__main__':
main()

50
.github/scripts/merge-repo.py vendored Normal file
View File

@@ -0,0 +1,50 @@
import html
import sys
import json
from pathlib import Path
import shutil
REMOTE_REPO: Path = Path.cwd()
LOCAL_REPO: Path = REMOTE_REPO.parent.joinpath(sys.argv[2])
to_delete: list[str] = json.loads(sys.argv[1])
for module in to_delete:
apk_name = f"tachiyomi-{module}-v*.*.*.apk"
icon_name = f"eu.kanade.tachiyomi.extension.{module}.png"
for file in REMOTE_REPO.joinpath("apk").glob(apk_name):
print(file.name)
file.unlink(missing_ok=True)
for file in REMOTE_REPO.joinpath("icon").glob(icon_name):
print(file.name)
file.unlink(missing_ok=True)
shutil.copytree(src=LOCAL_REPO.joinpath("apk"), dst=REMOTE_REPO.joinpath("apk"), dirs_exist_ok = True)
shutil.copytree(src=LOCAL_REPO.joinpath("icon"), dst=REMOTE_REPO.joinpath("icon"), dirs_exist_ok = True)
with REMOTE_REPO.joinpath("index.min.json").open() as remote_index_file:
remote_index = json.load(remote_index_file)
with LOCAL_REPO.joinpath("index.min.json").open() as local_index_file:
local_index = json.load(local_index_file)
index = [
item for item in remote_index
if not any([item["pkg"].endswith(f".{module}") for module in to_delete])
]
index.extend(local_index)
index.sort(key=lambda x: x["pkg"])
with REMOTE_REPO.joinpath("index.json").open("w", encoding="utf-8") as index_file:
json.dump(index, index_file, ensure_ascii=False, indent=2)
with REMOTE_REPO.joinpath("index.min.json").open("w", encoding="utf-8") as index_min_file:
json.dump(index, index_min_file, ensure_ascii=False, separators=(",", ":"))
with REMOTE_REPO.joinpath("index.html").open("w", encoding="utf-8") as index_html_file:
index_html_file.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
for entry in index:
apk_escaped = 'apk/' + html.escape(entry["apk"])
name_escaped = html.escape(entry["name"])
index_html_file.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
index_html_file.write('</pre>\n</body>\n</html>\n')

12
.github/scripts/move-built-apks.py vendored Normal file
View File

@@ -0,0 +1,12 @@
from pathlib import Path
import shutil
REPO_APK_DIR = Path("repo/apk")
shutil.rmtree(REPO_APK_DIR, ignore_errors=True)
REPO_APK_DIR.mkdir(parents=True, exist_ok=True)
for apk in Path.home().joinpath("apk-artifacts").glob("**/*.apk"):
apk_name = apk.name.replace("-release.apk", ".apk")
shutil.move(apk, REPO_APK_DIR.joinpath(apk_name))

22
.github/workflows/codeberg_mirror.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
# Sync repo to the Codeberg mirror
name: Mirror Sync
on:
push:
branches: [ "master" ]
workflow_dispatch: # Manual dispatch
schedule:
- cron: "0 */8 * * *"
jobs:
codeberg:
if: github.repository == 'yuzono/tachiyomi-extensions'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
with:
target_repo_url: "git@codeberg.org:cuong-tran/komikku-extensions.git"
ssh_private_key: ${{ secrets.CODEBERG_SSH }}

6
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 # Use the latest stable version
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace

View File

@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@@ -0,0 +1,47 @@
import groovy.lang.MissingPropertyException
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.extra
var ExtensionAware.baseVersionCode: Int
get() = (extra.get("baseVersionCode") as Int) + kmkBaseVersionCode
set(value) = extra.set("baseVersionCode", value)
fun Project.getDependents(): Set<Project> {
val dependentProjects = mutableSetOf<Project>()
rootProject.allprojects.forEach { project ->
project.configurations.forEach { configuration ->
configuration.dependencies.forEach { dependency ->
if (dependency is ProjectDependency && dependency.path == path) {
dependentProjects.add(project)
}
}
}
}
return dependentProjects
}
fun Project.printDependentExtensions() {
getDependents().forEach { project ->
if (project.path.startsWith(":src:")) {
println(project.path)
} else if (project.path.startsWith(":lib-multisrc:")) {
project.getDependents().forEach { println(it.path) }
} else if (project.path.startsWith(":lib:")) {
project.printDependentExtensions()
}
}
}
var ExtensionAware.kmkBaseVersionCode: Int
get() {
return try {
extra.get("kmkBaseVersionCode") as Int
} catch (e: MissingPropertyException) {
0
}
}
set(value) = extra.set("kmkBaseVersionCode", value)

View File

@@ -0,0 +1,29 @@
plugins {
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.${project.name}"
buildFeatures {
androidResources = false
}
}
dependencies {
compileOnly(versionCatalogs.named("libs").findBundle("common").get())
}
tasks.register("printDependentExtensions") {
doLast {
project.printDependentExtensions()
}
}

View File

@@ -0,0 +1,60 @@
plugins {
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
id("org.jmailen.kotlinter")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.multisrc.${project.name}"
sourceSets {
named("main") {
manifest.srcFile("AndroidManifest.xml")
java.setSrcDirs(listOf("src"))
res.setSrcDirs(listOf("res"))
assets.setSrcDirs(listOf("assets"))
}
}
kotlinOptions {
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
}
}
kotlinter {
experimentalRules = true
disabledRules = arrayOf(
"experimental:argument-list-wrapping", // Doesn't play well with Android Studio
"experimental:comment-wrapping",
)
}
dependencies {
compileOnly(versionCatalogs.named("libs").findBundle("common").get())
implementation(project(":utils"))
}
tasks {
preBuild {
dependsOn(lintKotlin)
}
if (System.getenv("CI") != "true") {
lintKotlin {
dependsOn(formatKotlin)
}
}
}
tasks.register("printDependentExtensions") {
doLast {
project.printDependentExtensions()
}
}

View File

@@ -1,45 +0,0 @@
package eu.kanade.tachiyomi.multisrc.gmanga
import android.util.Base64
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
fun decrypt(responseData: String): String {
val enc = responseData.split("|")
val secretKey = enc[3].sha256().hexStringToByteArray()
return enc[0].aesDecrypt(secretKey, enc[2])
}
private fun String.hexStringToByteArray(): ByteArray {
val len = length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = (
(Character.digit(this[i], 16) shl 4) +
Character.digit(this[i + 1], 16)
).toByte()
i += 2
}
return data
}
private fun String.sha256(): String {
return MessageDigest
.getInstance("SHA-256")
.digest(toByteArray())
.fold("") { str, it -> str + "%02x".format(it) }
}
private fun String.aesDecrypt(secretKey: ByteArray, ivString: String): String {
val c = Cipher.getInstance("AES/CBC/PKCS5Padding")
val sk = SecretKeySpec(secretKey, "AES")
val iv = IvParameterSpec(Base64.decode(ivString.toByteArray(Charsets.UTF_8), Base64.DEFAULT))
c.init(Cipher.DECRYPT_MODE, sk, iv)
val byteStr = Base64.decode(toByteArray(Charsets.UTF_8), Base64.DEFAULT)
return String(c.doFinal(byteStr))
}

View File

@@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.multisrc.mangaworld
import eu.kanade.tachiyomi.network.GET
import okhttp3.Cookie
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
class CookieRedirectInterceptor(private val client: OkHttpClient) : Interceptor {
private val cookieRegex = Regex("""document\.cookie="(MWCookie[^"]+)""")
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val contentType = response.header("content-type")
if (contentType != null && contentType.startsWith("image/", ignoreCase = true)) {
return response
}
// ignore requests that already have completed the JS challenge
if (response.headers["vary"] != null) return response
val content = response.body.string()
val results = cookieRegex.find(content)
?: return response.newBuilder().body(content.toResponseBody(response.body.contentType())).build()
val (cookieString) = results.destructured
return chain.proceed(loadCookie(request, cookieString))
}
private fun loadCookie(request: Request, cookieString: String): Request {
val cookie = Cookie.parse(request.url, cookieString)!!
client.cookieJar.saveFromResponse(request.url, listOf(cookie))
val headers = request.headers.newBuilder()
.add("Cookie", cookie.toString())
.build()
return GET(request.url, headers)
}
}

View File

@@ -0,0 +1,218 @@
package eu.kanade.tachiyomi.lib.zipinterceptor
import android.app.ActivityManager
import android.app.Application
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.util.Base64
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import tachiyomi.decoder.ImageDecoder
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.lang.reflect.Method
import java.util.zip.ZipInputStream
object ImageDecoderWrapper {
private var decodeMethod: Method
private var newInstanceMethod: Method
private var classSignature = ClassSignature.Newest
private enum class ClassSignature {
Old, New, Newest
}
init {
val rectClass = Rect::class.java
val booleanClass = Boolean::class.java
val intClass = Int::class.java
val byteArrayClass = ByteArray::class.java
val inputStreamClass = InputStream::class.java
try {
classSignature = ClassSignature.Newest
// decode(region, sampleSize)
decodeMethod = ImageDecoder::class.java.getMethod(
"decode",
rectClass,
intClass,
)
// newInstance(stream, cropBorders, displayProfile)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
byteArrayClass,
)
} catch (_: NoSuchMethodException) {
try {
classSignature = ClassSignature.New
// decode(region, rgb565, sampleSize, applyColorManagement, displayProfile)
decodeMethod = ImageDecoder::class.java.getMethod(
"decode",
rectClass,
booleanClass,
intClass,
booleanClass,
byteArrayClass,
)
// newInstance(stream, cropBorders)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
)
} catch (_: NoSuchMethodException) {
classSignature = ClassSignature.Old
// decode(region, rgb565, sampleSize)
decodeMethod =
ImageDecoder::class.java.getMethod(
"decode",
rectClass,
booleanClass,
intClass,
)
// newInstance(stream, cropBorders)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
)
}
}
}
fun decodeImage(data: ByteArray, rgb565: Boolean, filename: String, entryName: String): Bitmap {
val decoder = when (classSignature) {
ClassSignature.Newest -> newInstanceMethod.invoke(ImageDecoder.Companion, ByteArrayInputStream(data), false, null)
else -> newInstanceMethod.invoke(ImageDecoder.Companion, ByteArrayInputStream(data), false)
} as ImageDecoder?
if (decoder == null || decoder.width <= 0 || decoder.height <= 0) {
throw IOException("Falha ao inicializar o decodificador de imagem")
}
val rect = Rect(0, 0, decoder.width, decoder.height)
val bitmap = when (classSignature) {
ClassSignature.Newest -> decodeMethod.invoke(decoder, rect, 1)
ClassSignature.New -> decodeMethod.invoke(decoder, rect, rgb565, 1, false, null)
else -> decodeMethod.invoke(decoder, rect, rgb565, 1)
} as Bitmap?
decoder.recycle()
if (bitmap == null) {
throw IOException("Não foi possível decodificar a imagem $filename#$entryName")
}
return bitmap
}
}
open class ZipInterceptor {
private val dataUriRegex = Regex("""base64,([0-9a-zA-Z/+=\s]+)""")
open fun zipGetByteStream(request: Request, response: Response): InputStream {
return response.body.byteStream()
}
open fun requestIsZipImage(request: Request): Boolean {
return request.url.fragment == "page" && request.url.pathSegments.last().contains(".zip")
}
fun zipImageInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val filename = request.url.pathSegments.last()
if (requestIsZipImage(request).not()) {
return response
}
val zis = ZipInputStream(zipGetByteStream(request, response))
val images = generateSequence { zis.nextEntry }
.mapNotNull {
val entryName = it.name
val splitEntryName = entryName.split('.')
val entryIndex = splitEntryName.first().toInt()
val entryType = splitEntryName.last()
val imageData = if (entryType == "avif" || splitEntryName.size == 1) {
zis.readBytes()
} else {
val svgBytes = zis.readBytes()
val svgContent = svgBytes.toString(Charsets.UTF_8)
val b64 = dataUriRegex.find(svgContent)?.groupValues?.get(1)
?: return@mapNotNull null
Base64.decode(b64, Base64.DEFAULT)
}
entryIndex to ImageDecoderWrapper.decodeImage(imageData, isLowRamDevice, filename, entryName)
}
.sortedBy { it.first }
.toList()
zis.closeEntry()
zis.close()
val totalWidth = images.maxOf { it.second.width }
val totalHeight = images.sumOf { it.second.height }
val result = Bitmap.createBitmap(totalWidth, totalHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
var dy = 0
images.forEach {
val srcRect = Rect(0, 0, it.second.width, it.second.height)
val dstRect = Rect(0, dy, it.second.width, dy + it.second.height)
canvas.drawBitmap(it.second, srcRect, dstRect, null)
dy += it.second.height
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 90, output)
val image = output.toByteArray()
val body = image.toResponseBody("image/jpeg".toMediaType())
return response.newBuilder()
.body(body)
.build()
}
/**
* ActivityManager#isLowRamDevice is based on a system property, which isn't
* necessarily trustworthy. 1GB is supposedly the regular threshold.
*
* Instead, we consider anything with less than 3GB of RAM as low memory
* considering how heavy image processing can be.
*/
private val isLowRamDevice by lazy {
val ctx = Injekt.get<Application>()
val activityManager = ctx.getSystemService("activity") as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
memInfo.totalMem < 3L * 1024 * 1024 * 1024
}
}

View File

@@ -1,78 +0,0 @@
plugins {
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = 29
}
namespace = "eu.kanade.tachiyomi.lib.themesources"
kotlinOptions {
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
}
}
repositories {
mavenCentral()
}
configurations {
compileOnly {
isCanBeResolved = true
}
}
dependencies {
compileOnly(libs.bundles.common)
// Implements all :lib libraries on the multisrc generator
// Note that this does not mean that generated sources are going to
// implement them too; this is just to be able to compile and generate sources.
rootProject.subprojects
.filter { it.path.startsWith(":lib") }
.forEach(::implementation)
}
tasks {
register<JavaExec>("generateExtensions") {
val buildDir = layout.buildDirectory.asFile.get()
classpath = configurations.compileOnly.get() +
configurations.androidApis.get() + // android.jar path
files("$buildDir/intermediates/aar_main_jar/debug/classes.jar") // jar made from this module
mainClass.set("generator.GeneratorMainKt")
workingDir = workingDir.parentFile // project root
errorOutput = System.out // for GitHub workflow commands
if (!logger.isInfoEnabled) {
standardOutput = org.gradle.internal.io.NullOutputStream.INSTANCE
}
dependsOn("ktLint", "assembleDebug")
}
register<org.jmailen.gradle.kotlinter.tasks.LintTask>("ktLint") {
if (project.hasProperty("theme")) {
val theme = project.property("theme")
source(files("src/main/java/eu/kanade/tachiyomi/multisrc/$theme", "overrides/$theme"))
return@register
}
source(files("src", "overrides"))
}
register<org.jmailen.gradle.kotlinter.tasks.FormatTask>("ktFormat") {
if (project.hasProperty("theme")) {
val theme = project.property("theme")
source(files("src/main/java/eu/kanade/tachiyomi/multisrc/$theme", "overrides/$theme"))
return@register
}
source(files("src", "overrides"))
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.a3manga.A3MangaUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="${SOURCEHOST}" />
<data android:host="*.${SOURCEHOST}" />
<data android:pathPattern="/truyen-tranh/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.vi.ngonphong
import eu.kanade.tachiyomi.multisrc.a3manga.A3Manga
class NgonPhong : A3Manga("Ngôn Phong", "https://www.ngonphong.com", "vi") {
override val id: Long = 7268977637085631557
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

@@ -1,68 +0,0 @@
package eu.kanade.tachiyomi.extension.en.manhwaxxl
import eu.kanade.tachiyomi.multisrc.bakamanga.BakaManga
class ManhwaXXL : BakaManga(
"Manhwa XXL",
"https://manhwaxxl.com",
"en",
) {
override fun getGenreList() = arrayOf(
Pair("All", ""),
Pair("Action", "action"),
Pair("Adaptation", "adaptation"),
Pair("Adult", "adult"),
Pair("Adventure", "adventure"),
Pair("BL", "bl"),
Pair("Comedy", "comedy"),
Pair("Cooking", "cooking"),
Pair("Demons", "demons"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Fantasy", "fantasy"),
Pair("Full color", "full-color"),
Pair("Game", "game"),
Pair("Gender Bender", "gender-bender"),
Pair("GL", "gl"),
Pair("Harem", "harem"),
Pair("Historical", "historical"),
Pair("Horror", "horror"),
Pair("Isekai", "isekai"),
Pair("Josei", "josei"),
Pair("Live action", "live-action"),
Pair("Love & Romance", "love-romance"),
Pair("Magic", "magic"),
Pair("Manga", "manga"),
Pair("Manhua", "manhua"),
Pair("Manhwa", "manhwa"),
Pair("Martial Arts", "martial-arts"),
Pair("Mature", "mature"),
Pair("Mecha", "mecha"),
Pair("Mystery", "mystery"),
Pair("Omegaverse", "omegaverse"),
Pair("Psychological", "psychological"),
Pair("Raw", "raw"),
Pair("Reincarnation", "reincarnation"),
Pair("Romance", "romance"),
Pair("RPG", "rpg"),
Pair("School Life", "school-life"),
Pair("Sci-fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Slice of Life", "slice-of-life"),
Pair("Smut", "smut"),
Pair("Sports", "sports"),
Pair("Supernatural", "supernatural"),
Pair("Thriller", "thriller"),
Pair("Tragedy", "tragedy"),
Pair("Vampire", "vampire"),
Pair("Vanilla", "vanilla"),
Pair("Webtoon", "webtoon"),
Pair("Webtoons", "webtoons"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
Pair("Zombie", "zombie"),
)
}

View File

@@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.extension.en.bakkinselfhosted
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.bakkin.BakkinReaderX
class BakkinSelfHosted : BakkinReaderX("Bakkin Self-hosted", "", "en") {
override val baseUrl by lazy {
preferences.getString("baseUrl", "http://127.0.0.1/")!!
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen)
EditTextPreference(screen.context).apply {
key = "baseUrl"
title = "Custom URL"
summary = "Connect to a self-hosted Bakkin Reader X server"
setDefaultValue("http://127.0.0.1/")
setOnPreferenceChangeListener { _, newValue ->
// Make sure the URL ends with one slash
val url = (newValue as String).trimEnd('/') + '/'
preferences.edit().putString("baseUrl", url).commit()
}
}.let(screen::addPreference)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="bilibilicomics.com" />
<data android:host="m.bilibilicomics.com" />
<data android:host="www.bilibilicomics.com" />
<data android:pathPattern="/detail/mc..*" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,411 +0,0 @@
package eu.kanade.tachiyomi.extension.all.bilibilicomics
import eu.kanade.tachiyomi.multisrc.bilibili.Bilibili
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliAccessToken
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliAccessTokenCookie
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliComicDto
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliCredential
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliGetCredential
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliIntl
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliSearchDto
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliTag
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUnlockedEpisode
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUserEpisodes
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.SourceFactory
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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.Buffer
import java.io.IOException
import java.net.URLDecoder
import java.util.Calendar
class BilibiliComicsFactory : SourceFactory {
override fun createSources() = listOf(
BilibiliComicsEn(),
BilibiliComicsCn(),
BilibiliComicsId(),
BilibiliComicsEs(),
BilibiliComicsFr(),
)
}
abstract class BilibiliComics(lang: String) : Bilibili(
"BILIBILI COMICS",
"https://www.bilibilicomics.com",
lang,
) {
override val client: OkHttpClient = super.client.newBuilder()
.apply { interceptors().add(0, Interceptor { chain -> signedInIntercept(chain) }) }
.build()
init {
setAccessTokenCookie(baseUrl.toHttpUrl())
}
override val signedIn: Boolean
get() = accessTokenCookie != null
private val globalApiSubDomain: String
get() = GLOBAL_API_SUBDOMAINS[(accessTokenCookie?.area?.toIntOrNull() ?: 1) - 1]
private val globalApiBaseUrl: String
get() = "https://$globalApiSubDomain.bilibilicomics.com"
private var accessTokenCookie: BilibiliAccessTokenCookie? = null
private val dayOfWeek: Int
get() = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1
override fun latestUpdatesRequest(page: Int): Request {
val jsonPayload = buildJsonObject { put("day", dayOfWeek) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
.add("Content-Type", requestBody.contentType().toString())
.set("Referer", "$baseUrl/schedule")
.build()
val apiUrl = "$baseUrl/$API_COMIC_V1_COMIC_ENDPOINT/GetSchedule".toHttpUrl().newBuilder()
.addCommonParameters()
.toString()
return POST(apiUrl, newHeaders, requestBody)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val result = response.parseAs<BilibiliSearchDto>()
if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false)
}
val comicList = result.data!!.list.map(::latestMangaFromObject)
return MangasPage(comicList, hasNextPage = false)
}
protected open fun latestMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = comic.title
thumbnail_url = comic.verticalCover + THUMBNAIL_RESOLUTION
url = "/detail/mc${comic.comicId}"
}
override fun chapterListParse(response: Response): List<SChapter> {
if (!signedIn) {
return super.chapterListParse(response)
}
val result = response.parseAs<BilibiliComicDto>()
if (result.code != 0) {
return emptyList()
}
val comic = result.data!!
val userEpisodesRequest = userEpisodesRequest(comic.id)
val userEpisodesResponse = client.newCall(userEpisodesRequest).execute()
val unlockedEpisodes = userEpisodesParse(userEpisodesResponse)
return comic.episodeList.map { ep -> chapterFromObject(ep, comic.id, isUnlocked = ep.id in unlockedEpisodes) }
}
private fun userEpisodesRequest(comicId: Int): Request {
val jsonPayload = buildJsonObject { put("comic_id", comicId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.set("Referer", baseUrl)
.build()
val apiUrl = "$globalApiBaseUrl/$API_COMIC_V1_USER_ENDPOINT/GetUserEpisodes".toHttpUrl()
.newBuilder()
.addCommonParameters()
.toString()
return POST(apiUrl, newHeaders, requestBody)
}
private fun userEpisodesParse(response: Response): List<Int> {
if (!response.isSuccessful) {
return emptyList()
}
val result = response.parseAs<BilibiliUserEpisodes>()
if (result.code != 0) {
return emptyList()
}
return result.data!!.unlockedEpisodes.orEmpty()
.map(BilibiliUnlockedEpisode::id)
}
override fun pageListRequest(chapter: SChapter): Request {
if (!signedIn) {
return super.pageListRequest(chapter)
}
val chapterPaths = (baseUrl + chapter.url).toHttpUrl().pathSegments
val comicId = chapterPaths[0].removePrefix("mc").toInt()
val episodeId = chapterPaths[1].toInt()
val jsonPayload = BilibiliGetCredential(comicId, episodeId, 1)
val requestBody = json.encodeToString(jsonPayload).toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.set("Referer", baseUrl + chapter.url)
.build()
val apiUrl = "$globalApiBaseUrl/$API_GLOBAL_V1_USER_ENDPOINT/GetCredential".toHttpUrl()
.newBuilder()
.addCommonParameters()
.toString()
return POST(apiUrl, newHeaders, requestBody)
}
override fun pageListParse(response: Response): List<Page> {
if (!signedIn) {
return super.pageListParse(response)
}
if (!response.isSuccessful) {
throw Exception(intl.failedToGetCredential)
}
val result = response.parseAs<BilibiliCredential>()
val credential = result.data?.credential ?: ""
val requestPayload = response.request.bodyString
val credentialInfo = json.decodeFromString<BilibiliGetCredential>(requestPayload)
val chapterUrl = "/mc${credentialInfo.comicId}/${credentialInfo.episodeId}"
val imageIndexRequest = imageIndexRequest(chapterUrl, credential)
val imageIndexResponse = client.newCall(imageIndexRequest).execute()
return super.pageListParse(imageIndexResponse)
}
private fun setAccessTokenCookie(url: HttpUrl) {
val authCookie = client.cookieJar.loadForRequest(url)
.firstOrNull { cookie -> cookie.name == ACCESS_TOKEN_COOKIE_NAME }
?.let { cookie -> URLDecoder.decode(cookie.value, "UTF-8") }
?.let { jsonString -> json.decodeFromString<BilibiliAccessTokenCookie>(jsonString) }
if (accessTokenCookie == null) {
accessTokenCookie = authCookie
} else if (authCookie == null) {
accessTokenCookie = null
}
}
private fun signedInIntercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val requestUrl = request.url.toString()
if (!requestUrl.contains("bilibilicomics.com")) {
return chain.proceed(request)
}
setAccessTokenCookie(request.url)
if (!accessTokenCookie?.accessToken.isNullOrEmpty()) {
request = request.newBuilder()
.addHeader("Authorization", "Bearer ${accessTokenCookie!!.accessToken}")
.build()
}
val response = chain.proceed(request)
// Try to refresh the token if it expired.
if (response.code == 401 && !accessTokenCookie?.refreshToken.isNullOrEmpty()) {
response.close()
val refreshTokenRequest = refreshTokenRequest(
accessTokenCookie!!.accessToken,
accessTokenCookie!!.refreshToken,
)
val refreshTokenResponse = chain.proceed(refreshTokenRequest)
accessTokenCookie = refreshTokenParse(refreshTokenResponse)
refreshTokenResponse.close()
request = request.newBuilder()
.header("Authorization", "Bearer ${accessTokenCookie!!.accessToken}")
.build()
return chain.proceed(request)
}
return response
}
private fun refreshTokenRequest(accessToken: String, refreshToken: String): Request {
val jsonPayload = buildJsonObject { put("refresh_token", refreshToken) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Authorization", "Bearer $accessToken")
.set("Referer", baseUrl)
.build()
val apiUrl = "$globalApiBaseUrl/$API_GLOBAL_V1_USER_ENDPOINT/RefreshToken".toHttpUrl()
.newBuilder()
.addCommonParameters()
.toString()
return POST(apiUrl, newHeaders, requestBody)
}
private fun refreshTokenParse(response: Response): BilibiliAccessTokenCookie {
if (!response.isSuccessful) {
throw IOException(intl.failedToRefreshToken)
}
val result = response.parseAs<BilibiliAccessToken>()
if (result.code != 0) {
throw IOException(intl.failedToRefreshToken)
}
val accessToken = result.data!!
return BilibiliAccessTokenCookie(
accessToken.accessToken,
accessToken.refreshToken,
accessTokenCookie!!.area,
)
}
private val Request.bodyString: String
get() {
val requestCopy = newBuilder().build()
val buffer = Buffer()
return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() }
.getOrNull() ?: ""
}
companion object {
private const val ACCESS_TOKEN_COOKIE_NAME = "access_token"
private val GLOBAL_API_SUBDOMAINS = arrayOf("us-user", "sg-user")
private const val API_GLOBAL_V1_USER_ENDPOINT = "twirp/global.v1.User"
private const val API_COMIC_V1_USER_ENDPOINT = "twirp/comic.v1.User"
}
}
class BilibiliComicsEn : BilibiliComics(BilibiliIntl.ENGLISH) {
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("All", -1),
BilibiliTag("Action", 19),
BilibiliTag("Adventure", 22),
BilibiliTag("BL", 3),
BilibiliTag("Comedy", 14),
BilibiliTag("Eastern", 30),
BilibiliTag("Fantasy", 11),
BilibiliTag("GL", 16),
BilibiliTag("Harem", 15),
BilibiliTag("Historical", 12),
BilibiliTag("Horror", 23),
BilibiliTag("Mistery", 17),
BilibiliTag("Romance", 13),
BilibiliTag("Slice of Life", 21),
BilibiliTag("Suspense", 41),
BilibiliTag("Teen", 20),
)
}
class BilibiliComicsCn : BilibiliComics(BilibiliIntl.SIMPLIFIED_CHINESE) {
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("全部", -1),
BilibiliTag("校园", 18),
BilibiliTag("都市", 9),
BilibiliTag("耽美", 3),
BilibiliTag("少女", 20),
BilibiliTag("恋爱", 13),
BilibiliTag("奇幻", 11),
BilibiliTag("热血", 19),
BilibiliTag("冒险", 22),
BilibiliTag("古风", 12),
BilibiliTag("百合", 16),
BilibiliTag("玄幻", 30),
BilibiliTag("悬疑", 41),
BilibiliTag("科幻", 8),
)
}
class BilibiliComicsId : BilibiliComics(BilibiliIntl.INDONESIAN) {
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("Semua", -1),
BilibiliTag("Aksi", 19),
BilibiliTag("Fantasi Timur", 30),
BilibiliTag("Fantasi", 11),
BilibiliTag("Historis", 12),
BilibiliTag("Horror", 23),
BilibiliTag("Kampus", 18),
BilibiliTag("Komedi", 14),
BilibiliTag("Menegangkan", 41),
BilibiliTag("Remaja", 20),
BilibiliTag("Romantis", 13),
)
}
class BilibiliComicsEs : BilibiliComics(BilibiliIntl.SPANISH) {
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("Todos", -1),
BilibiliTag("Adolescencia", 105),
BilibiliTag("BL", 3),
BilibiliTag("Ciberdeportes", 104),
BilibiliTag("Ciencia ficción", 8),
BilibiliTag("Comedia", 14),
BilibiliTag("Fantasía occidental", 106),
BilibiliTag("Fantasía", 11),
BilibiliTag("Ficción Realista", 116),
BilibiliTag("GL", 16),
BilibiliTag("Histórico", 12),
BilibiliTag("Horror", 23),
BilibiliTag("Juvenil", 20),
BilibiliTag("Moderno", 111),
BilibiliTag("Oriental", 30),
BilibiliTag("Romance", 13),
BilibiliTag("Suspenso", 41),
BilibiliTag("Urbano", 9),
BilibiliTag("Wuxia", 103),
)
}
class BilibiliComicsFr : BilibiliComics(BilibiliIntl.FRENCH) {
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("Tout", -1),
BilibiliTag("BL", 3),
BilibiliTag("Science Fiction", 8),
BilibiliTag("Historique", 12),
BilibiliTag("Romance", 13),
BilibiliTag("GL", 16),
BilibiliTag("Fantasy Orientale", 30),
BilibiliTag("Suspense", 41),
BilibiliTag("Moderne", 111),
)
}

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="manga.bilibili.com" />
<data android:pathPattern="/detail/mc..*" />
<data android:pathPattern="/m/detail/mc..*" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -1,83 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.bilibilimanga
import eu.kanade.tachiyomi.multisrc.bilibili.Bilibili
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliComicDto
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliIntl
import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliTag
import eu.kanade.tachiyomi.source.model.SChapter
import okhttp3.Headers
import okhttp3.Response
class BilibiliManga : Bilibili(
"哔哩哔哩漫画",
"https://manga.bilibili.com",
BilibiliIntl.SIMPLIFIED_CHINESE,
) {
override val id: Long = 3561131545129718586
override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", DEFAULT_USER_AGENT)
}
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<BilibiliComicDto>()
if (result.code != 0) {
return emptyList()
}
return result.data!!.episodeList
.filter { episode -> episode.isInFree || !episode.isLocked }
.map { ep -> chapterFromObject(ep, result.data.id) }
}
override val defaultPopularSort: Int = 0
override val defaultLatestSort: Int = 1
override fun getAllSortOptions(): Array<BilibiliTag> = arrayOf(
BilibiliTag(intl.sortPopular, 0),
BilibiliTag(intl.sortUpdated, 1),
BilibiliTag(intl.sortFollowers, 2),
BilibiliTag(intl.sortAdded, 3),
)
override fun getAllPrices(): Array<String> =
arrayOf(intl.priceAll, intl.priceFree, intl.pricePaid, intl.priceWaitForFree)
override fun getAllGenres(): Array<BilibiliTag> = arrayOf(
BilibiliTag("全部", -1),
BilibiliTag("竞技", 1034),
BilibiliTag("冒险", 1013),
BilibiliTag("热血", 999),
BilibiliTag("搞笑", 994),
BilibiliTag("恋爱", 995),
BilibiliTag("少女", 1026),
BilibiliTag("日常", 1020),
BilibiliTag("校园", 1001),
BilibiliTag("治愈", 1007),
BilibiliTag("古风", 997),
BilibiliTag("玄幻", 1016),
BilibiliTag("奇幻", 998),
BilibiliTag("惊奇", 996),
BilibiliTag("悬疑", 1023),
BilibiliTag("都市", 1002),
BilibiliTag("剧情", 1030),
BilibiliTag("总裁", 1004),
BilibiliTag("科幻", 1015),
BilibiliTag("正能量", 1028),
)
override fun getAllAreas(): Array<BilibiliTag> = arrayOf(
BilibiliTag("全部", -1),
BilibiliTag("大陆", 1),
BilibiliTag("日本", 2),
BilibiliTag("韩国", 6),
BilibiliTag("其他", 5),
)
companion object {
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
}
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="${SOURCEHOST}"
android:pathPattern="/detail/mc..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files have changed in this diff Show More