diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 01fd2c07e..000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,32 +0,0 @@
-**PLEASE READ THIS**
-
-I acknowledge that:
-
-- I have updated to the latest version of the app (stable is v0.14.7)
-- I have updated all extensions
-- If this is an issue with the app itself, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi
-- I have searched the existing issues for duplicates
-- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://tachiyomi.org/extensions/
-
-**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
-
----
-
-## Device information
-* Tachiyomi version: ?
-* Android version: ?
-* Device: ?
-
-## Source information
-* Source name: ?
-* Source extension version: ?
-
-## Steps to reproduce
-1. First step
-2. Second step
-
-## Issue/Request
-?
-
-## Other details
-Additional details and attachments.
diff --git a/.github/ISSUE_TEMPLATE/01_report_issue.yml b/.github/ISSUE_TEMPLATE/01_report_issue.yml
index 5b8edb655..8ded43a74 100644
--- a/.github/ISSUE_TEMPLATE/01_report_issue.yml
+++ b/.github/ISSUE_TEMPLATE/01_report_issue.yml
@@ -10,7 +10,7 @@ body:
description: |
You can find the extension name and version in **Browse → Extensions**.
placeholder: |
- Example: "Mangahere 1.3.18"
+ Example: "Not Real Scans 1.4.1"
validations:
required: true
@@ -59,11 +59,11 @@ body:
- type: input
id: tachiyomi-version
attributes:
- label: Tachiyomi version
+ label: Mihon/Tachiyomi version
description: |
- You can find your Tachiyomi version in **More → About**.
+ You can find your Mihon/Tachiyomi version in **More → About**.
placeholder: |
- Example: "0.14.7"
+ Example: "0.16.3"
validations:
required: true
@@ -95,7 +95,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
+ - label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
diff --git a/.github/ISSUE_TEMPLATE/02_request_source.yml b/.github/ISSUE_TEMPLATE/02_request_source.yml
index 589fcb46f..6bd07cdea 100644
--- a/.github/ISSUE_TEMPLATE/02_request_source.yml
+++ b/.github/ISSUE_TEMPLATE/02_request_source.yml
@@ -45,14 +45,10 @@ body:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- - label: I have checked that the extension does not already exist on the [website extensions list](https://tachiyomi.org/extensions/) or the app.
- required: true
- - label: I have checked that the extension is not on [the removed sources list](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md).
+ - label: I have checked that the extension does not already exist by searching the [extension listing](https://keiyoushi.github.io/extensions/) and verified it does not appear there.
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/tachiyomiorg/tachiyomi-extensions/) and verified it does not appear in the code base.
- required:
- label: I have written a meaningful title with the source name.
required: true
- label: I will fill out all of the requested information in this form.
diff --git a/.github/ISSUE_TEMPLATE/03_report_url_change.yml b/.github/ISSUE_TEMPLATE/03_report_url_change.yml
index 02e527fe9..a251aafdc 100644
--- a/.github/ISSUE_TEMPLATE/03_report_url_change.yml
+++ b/.github/ISSUE_TEMPLATE/03_report_url_change.yml
@@ -10,7 +10,7 @@ body:
description: |
You can find the extension name and version in **Browse → Extensions**.
placeholder: |
- Example: "NotRealScans 1.3.1"
+ Example: "NotRealScans 1.4.1"
validations:
required: true
@@ -47,7 +47,7 @@ body:
options:
- label: I have updated all installed extensions.
required: true
- - label: I have opened WebView and checked that the source URL is not updated yet.
+ - label: I have opened WebView and checked that the source URL has not changed yet.
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
diff --git a/.github/ISSUE_TEMPLATE/04_report_dead_source.yml b/.github/ISSUE_TEMPLATE/04_report_dead_source.yml
index 29ce49e6d..f31765606 100644
--- a/.github/ISSUE_TEMPLATE/04_report_dead_source.yml
+++ b/.github/ISSUE_TEMPLATE/04_report_dead_source.yml
@@ -16,7 +16,7 @@ body:
description: |
You can find the extension name in **Browse → Extensions**.
placeholder: |
- Example: "NotRealScans"
+ Example: "Not Real Scans"
validations:
required: true
diff --git a/.github/ISSUE_TEMPLATE/05_request_feature.yml b/.github/ISSUE_TEMPLATE/05_request_feature.yml
index 3dbdb8d9a..d22aa845a 100644
--- a/.github/ISSUE_TEMPLATE/05_request_feature.yml
+++ b/.github/ISSUE_TEMPLATE/05_request_feature.yml
@@ -10,7 +10,7 @@ body:
description: |
You can find the extension name in **Browse → Extensions**.
placeholder: |
- Example: "Mangahere"
+ Example: "Not Real Scans"
validations:
required: true
@@ -51,9 +51,9 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose).
+ - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/mihonapp/mihon/issues/new/choose).
required: true
- - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
+ - label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
diff --git a/.github/ISSUE_TEMPLATE/06_request_meta.yml b/.github/ISSUE_TEMPLATE/06_request_meta.yml
index 704690a32..acddd397d 100644
--- a/.github/ISSUE_TEMPLATE/06_request_meta.yml
+++ b/.github/ISSUE_TEMPLATE/06_request_meta.yml
@@ -31,9 +31,9 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose).
+ - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/mihonapp/mihon/issues/new/choose).
required: true
- - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
+ - label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
diff --git a/.github/ISSUE_TEMPLATE/07_request_removal.yml b/.github/ISSUE_TEMPLATE/07_request_removal.yml
deleted file mode 100644
index 73f251de3..000000000
--- a/.github/ISSUE_TEMPLATE/07_request_removal.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: 🗑 Source removal request
-description: Scanlators can request their site to be removed
-labels: [Meta request]
-body:
-
- - type: input
- id: link
- attributes:
- label: Source link
- placeholder: |
- Example: "https://notrealscans.org"
- validations:
- required: true
-
- - type: textarea
- id: other-details
- attributes:
- label: Other details
- placeholder: |
- Additional details and attachments.
-
- - type: checkboxes
- id: requirements
- attributes:
- label: Requirements
- description: Your request will be denied if you don't meet these requirements.
- options:
- - label: I've added a `TXT` DNS record for my domain with the value `tachiyomi-verification`
- required: true
- - label: Site only hosts content scanlated by the group and not stolen from other scanlators or official releases (i.e., not an aggregator site)
- required: true
- - label: Site is not infested with user-hostile features (e.g., invasive or malicious ads)
- required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 710673d3a..0129e3afb 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: ⚠️ Application issue
- url: https://github.com/tachiyomiorg/tachiyomi/issues/new/choose
- about: Issues and requests about the app itself should be opened in the tachiyomi repository instead
- - name: 📦 Tachiyomi extensions
- url: https://tachiyomi.org/extensions
+ url: https://github.com/mihonapp/mihon/issues/new/choose
+ about: Issues and requests about the app itself should be opened in Mihon's repository instead
+ - name: 📦 Extension list
+ url: https://keiyoushi.github.io/extensions
about: List of all available extensions with download links
- - name: 🖥️ Tachiyomi website
- url: https://tachiyomi.org/help/
+ - name: 🖥️ Mihon website
+ url: https://mihon.app/docs/guides/troubleshooting/
about: Guides, troubleshooting, and answers to common questions
diff --git a/.github/always_build.json b/.github/always_build.json
new file mode 100644
index 000000000..84b44fcbe
--- /dev/null
+++ b/.github/always_build.json
@@ -0,0 +1,4 @@
+[
+ "ko.newtoki",
+ "ko.wolfdotcom"
+]
diff --git a/.github/readme-images/app-icon.png b/.github/readme-images/app-icon.png
deleted file mode 100644
index 78804a17b..000000000
Binary files a/.github/readme-images/app-icon.png and /dev/null differ
diff --git a/.github/scripts/create-repo.py b/.github/scripts/create-repo.py
old mode 100755
new mode 100644
index d35ea9292..bbcd54c23
--- a/.github/scripts/create-repo.py
+++ b/.github/scripts/create-repo.py
@@ -1,4 +1,3 @@
-import html
import json
import os
import re
@@ -11,10 +10,8 @@ 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-([^\.]+)")
+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")
@@ -26,7 +23,6 @@ REPO_ICON_DIR.mkdir(parents=True, exist_ok=True)
with open("output.json", encoding="utf-8") as f:
inspector_data = json.load(f)
-index_data = []
index_min_data = []
for apk in REPO_APK_DIR.iterdir():
@@ -41,7 +37,7 @@ for apk in REPO_APK_DIR.iterdir():
).decode()
package_info = next(x for x in badging.splitlines() if x.startswith("package: "))
- package_name = PACKAGE_NAME_REGEX.search(package_info).group(1)
+ 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, (
@@ -87,31 +83,6 @@ for apk in REPO_APK_DIR.iterdir():
)
index_min_data.append(min_data)
- index_data.append(
- {
- **common_data,
- "hasReadme": 0,
- "hasChangelog": 0,
- "sources": sources,
- }
- )
-index_data.sort(key=lambda x: x["pkg"])
-index_min_data.sort(key=lambda x: x["pkg"])
-
-with (REPO_DIR / "index.json").open("w", encoding="utf-8") as f:
- index_data_str = json.dumps(index_data, ensure_ascii=False, indent=2)
-
- print(index_data_str)
- f.write(index_data_str)
-
-with (REPO_DIR / "index.min.json").open("w", encoding="utf-8") as f:
- json.dump(index_min_data, f, ensure_ascii=False, separators=(",", ":"))
-
-with (REPO_DIR / "index.html").open("w", encoding="utf-8") as f:
- f.write('\n\n
\n\napks\n\n\n\n')
- for entry in index_data:
- apk_escaped = 'apk/' + html.escape(entry["apk"])
- name_escaped = html.escape(entry["name"])
- f.write(f'{name_escaped}\n')
- f.write('
\n\n\n')
+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=(",", ":"))
diff --git a/.github/scripts/generate-build-matrices.py b/.github/scripts/generate-build-matrices.py
new file mode 100644
index 000000000..591f06253
--- /dev/null
+++ b/.github/scripts/generate-build-matrices.py
@@ -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\w+)/(?P\w+)")
+MULTISRC_LIB_REGEX = re.compile(r"^lib-multisrc/(?P\w+)")
+LIB_REGEX = re.compile(r"^lib/(?P\w+)")
+MODULE_REGEX = re.compile(r"^:src:(?P\w+):(?P\w+)$")
+CORE_FILES_REGEX = re.compile(
+ r"^(buildSrc/|core/|gradle/|build\.gradle\.kts|common\.gradle|gradle\.properties|settings\.gradle\.kts)"
+)
+
+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()
diff --git a/.github/scripts/merge-repo.py b/.github/scripts/merge-repo.py
new file mode 100644
index 000000000..351e24b55
--- /dev/null
+++ b/.github/scripts/merge-repo.py
@@ -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("main/repo")
+
+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('\n\n\n\napks\n\n\n\n')
+ for entry in index:
+ apk_escaped = 'apk/' + html.escape(entry["apk"])
+ name_escaped = html.escape(entry["name"])
+ index_html_file.write(f'{name_escaped}\n')
+ index_html_file.write('
\n\n\n')
diff --git a/.github/scripts/move-apks.py b/.github/scripts/move-built-apks.py
old mode 100755
new mode 100644
similarity index 50%
rename from .github/scripts/move-apks.py
rename to .github/scripts/move-built-apks.py
index 09622fc14..edc938ad1
--- a/.github/scripts/move-apks.py
+++ b/.github/scripts/move-built-apks.py
@@ -3,14 +3,10 @@ import shutil
REPO_APK_DIR = Path("repo/apk")
-try:
- shutil.rmtree(REPO_APK_DIR)
-except FileNotFoundError:
- pass
-
+shutil.rmtree(REPO_APK_DIR, ignore_errors=True)
REPO_APK_DIR.mkdir(parents=True, exist_ok=True)
-for apk in (Path.home() / "apk-artifacts").glob("**/*.apk"):
+for apk in Path.home().joinpath("apk-artifacts").glob("**/*.apk"):
apk_name = apk.name.replace("-release.apk", ".apk")
- shutil.move(apk, REPO_APK_DIR / apk_name)
\ No newline at end of file
+ shutil.move(apk, REPO_APK_DIR.joinpath(apk_name))
diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml
new file mode 100644
index 000000000..f4e907348
--- /dev/null
+++ b/.github/workflows/build_pull_request.yml
@@ -0,0 +1,71 @@
+name: PR check
+
+on:
+ pull_request:
+ paths:
+ - '**'
+ - '!**.md'
+ - '!.github/**'
+ - '.github/workflows/build_pull_request.yml'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
+env:
+ CI_CHUNK_SIZE: 65
+ IS_PR_CHECK: true
+
+jobs:
+ prepare:
+ name: Prepare job
+ runs-on: 'ubuntu-24.04'
+ outputs:
+ matrix: ${{ steps.generate-matrices.outputs.matrix }}
+ delete: ${{ steps.generate-matrices.outputs.delete }}
+ steps:
+ - name: Checkout PR
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Set up Java
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+ with:
+ java-version: 17
+ distribution: temurin
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
+ with:
+ cache-read-only: true
+
+ - id: generate-matrices
+ name: Generate build matrices
+ run: |
+ git fetch origin main
+ python ./.github/scripts/generate-build-matrices.py origin/main Debug
+
+ build:
+ name: Build extensions (${{ matrix.chunk.number }})
+ needs: prepare
+ runs-on: 'ubuntu-24.04'
+ if: ${{ toJson(fromJson(needs.prepare.outputs.matrix).chunk) != '[]' }}
+ strategy:
+ matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
+ steps:
+ - name: Checkout PR
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Set up Java
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+ with:
+ java-version: 17
+ distribution: temurin
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
+ with:
+ cache-read-only: true
+
+ - name: Build extensions (${{ matrix.chunk.number }})
+ run: |
+ ./gradlew $(echo '${{ toJson(matrix.chunk.modules) }}' | jq -r 'join(" ")')
diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml
index d4c1ed9a4..5a2767cc1 100644
--- a/.github/workflows/build_push.yml
+++ b/.github/workflows/build_push.yml
@@ -24,7 +24,7 @@ jobs:
CI_MODULE_GEN: true
steps:
- name: Clone repo
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
build_individual:
name: Build individual modules
@@ -32,10 +32,10 @@ jobs:
runs-on: arch
steps:
- name: Checkout master branch
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
java-version: 17
distribution: temurin
@@ -45,7 +45,7 @@ jobs:
echo ${{ secrets.SIGNING_KEY }} | base64 -d > signingkey.jks
- name: Set up Gradle
- uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
+ uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
- name: Build extensions
env:
@@ -56,7 +56,7 @@ jobs:
- name: Upload APKs
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: "individual-apks"
path: "**/*.apk"
@@ -72,18 +72,18 @@ jobs:
runs-on: arch
steps:
- name: Download APK artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: ~/apk-artifacts
- name: Set up JDK
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
java-version: 17
distribution: temurin
- name: Checkout master branch
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: master
path: master
@@ -91,14 +91,14 @@ jobs:
- name: Create repo artifacts
run: |
cd master
- python ./.github/scripts/move-apks.py
+ python ./.github/scripts/move-built-apks.py
INSPECTOR_LINK="$(curl -s "https://api.github.com/repos/keiyoushi/extensions-inspector/releases/latest" | jq -r '.assets[0].browser_download_url')"
curl -L "$INSPECTOR_LINK" -o ./Inspector.jar
java -jar ./Inspector.jar "repo/apk" "output.json" "tmp"
python ./.github/scripts/create-repo.py
- name: Checkout repo branch
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: repo
path: repo
diff --git a/.github/workflows/codeberg_mirror.yml b/.github/workflows/codeberg_mirror.yml
new file mode 100644
index 000000000..6269f3ac9
--- /dev/null
+++ b/.github/workflows/codeberg_mirror.yml
@@ -0,0 +1,22 @@
+# Sync repo to the Codeberg mirror
+name: Mirror Sync
+
+on:
+ push:
+ branches: [ "main" ]
+ workflow_dispatch: # Manual dispatch
+ schedule:
+ - cron: "0 */8 * * *"
+
+jobs:
+ codeberg:
+ if: "github.repository == 'keiyoushi/extensions-source'"
+ 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:keiyoushi/extensions-source.git"
+ ssh_private_key: ${{ secrets.CODEBERG_SSH }}
diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml
new file mode 100644
index 000000000..a53e14963
--- /dev/null
+++ b/.github/workflows/issue_moderator.yml
@@ -0,0 +1,71 @@
+name: Issue moderator
+
+on:
+ issues:
+ types: [ opened, edited, reopened ]
+ issue_comment:
+ types: [ created ]
+
+jobs:
+ autoclose:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Moderate issues
+ uses: keiyoushi/issue-moderator-action@a017be83547db6e107431ce7575f53c1dfa3296a
+ with:
+ repo-token: ${{ secrets.ISSUE_MODERATOR_PAT }}
+ duplicate-label: Duplicate
+
+ blurbs: |
+ [
+ {
+ "keywords": ["cf", "cloudflare"],
+ "message": "Refer to the **Solving Cloudflare issues** section at https://keiyoushi.github.io/docs/guides/troubleshooting#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
+ },
+ {
+ "keywords": ["uninstall"],
+ "message": "Uninstall the extension before updating. If that does not work, uninstall it from your device's settings by navigating to your device's Settings -> Apps and uninstall it from there by looking for `Tachiyomi: `.\n\nThis is usually caused your previous extension having a different signature from the updated extension, making Android refuse to update it."
+ }
+ ]
+
+ duplicate-check-enabled: true
+ duplicate-check-labels: |
+ ["Source request", "Domain changed"]
+
+ existing-check-enabled: true
+ existing-check-labels: |
+ ["Source request", "Domain changed"]
+ existing-check-repo-url: https://raw.githubusercontent.com/keiyoushi/extensions/repo/index.min.json
+
+ auto-close-rules: |
+ [
+ {
+ "type": "both",
+ "regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(? {
+ val dependentProjects = mutableSetOf()
+
+ 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()
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/lib-android.gradle.kts b/buildSrc/src/main/kotlin/lib-android.gradle.kts
index ee9eb1c22..e3618980b 100644
--- a/buildSrc/src/main/kotlin/lib-android.gradle.kts
+++ b/buildSrc/src/main/kotlin/lib-android.gradle.kts
@@ -21,3 +21,9 @@ android {
dependencies {
compileOnly(versionCatalogs.named("libs").findBundle("common").get())
}
+
+tasks.register("printDependentExtensions") {
+ doLast {
+ project.printDependentExtensions()
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ab0ee69b0..9b77d054f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,3 +1,12 @@
+/**
+ * Add or remove modules to load as needed for local development here.
+ */
+loadAllIndividualExtensions()
+// loadIndividualExtension("all", "mangadex")
+
+/**
+ * ===================================== COMMON CONFIGURATION ======================================
+ */
include(":core")
include(":utils")
@@ -7,11 +16,9 @@ File(rootDir, "lib").eachDir { include("lib:${it.name}") }
// Load all modules under /lib-multisrc
File(rootDir, "lib-multisrc").eachDir { include("lib-multisrc:${it.name}") }
-
-
-loadAllIndividualExtensions()
-
-
+/**
+ * ======================================== HELPER FUNCTION ========================================
+ */
fun loadAllIndividualExtensions() {
File(rootDir, "src").eachDir { dir ->
dir.eachDir { subdir ->
@@ -23,18 +30,6 @@ fun loadIndividualExtension(lang: String, name: String) {
include("src:${lang}:${name}")
}
-fun File.getChunk(chunk: Int, chunkSize: Int): List? {
- return listFiles()
- // Lang folder
- ?.filter { it.isDirectory }
- // Extension subfolders
- ?.mapNotNull { dir -> dir.listFiles()?.filter { it.isDirectory } }
- ?.flatten()
- ?.sortedBy { it.name }
- ?.chunked(chunkSize)
- ?.get(chunk)
-}
-
fun File.eachDir(block: (File) -> Unit) {
val files = listFiles() ?: return
for (file in files) {
@@ -42,4 +37,4 @@ fun File.eachDir(block: (File) -> Unit) {
block(file)
}
}
-}
\ No newline at end of file
+}
diff --git a/src/all/bilibilicomics/res/mipmap-hdpi/ic_launcher.png b/src/all/bilibilicomics/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index e5ef6094c..000000000
Binary files a/src/all/bilibilicomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/bilibilicomics/res/mipmap-mdpi/ic_launcher.png b/src/all/bilibilicomics/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index cdd2c53a7..000000000
Binary files a/src/all/bilibilicomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/bilibilicomics/res/mipmap-xhdpi/ic_launcher.png b/src/all/bilibilicomics/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index a19a967d7..000000000
Binary files a/src/all/bilibilicomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/bilibilicomics/res/mipmap-xxhdpi/ic_launcher.png b/src/all/bilibilicomics/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 86ec87fde..000000000
Binary files a/src/all/bilibilicomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/bilibilicomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/bilibilicomics/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index c0a5e8c93..000000000
Binary files a/src/all/bilibilicomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/lanraragi/res/web_hi_res_512.png b/src/all/lanraragi/res/web_hi_res_512.png
deleted file mode 100644
index f6555cd95..000000000
Binary files a/src/all/lanraragi/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/all/mangaplus/res/web_hi_res_512.png b/src/all/mangaplus/res/web_hi_res_512.png
deleted file mode 100644
index 8382e05ea..000000000
Binary files a/src/all/mangaplus/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/ar/remanga/build.gradle b/src/ar/remanga/build.gradle
deleted file mode 100644
index 2a01c4e98..000000000
--- a/src/ar/remanga/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-ext {
- extName = 'RE Manga'
- extClass = '.REManga'
- extVersionCode = 2
-}
-
-apply from: "$rootDir/common.gradle"
diff --git a/src/ar/remanga/res/mipmap-hdpi/ic_launcher.png b/src/ar/remanga/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 3b247e293..000000000
Binary files a/src/ar/remanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ar/remanga/res/mipmap-mdpi/ic_launcher.png b/src/ar/remanga/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 86924c36b..000000000
Binary files a/src/ar/remanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ar/remanga/res/mipmap-xhdpi/ic_launcher.png b/src/ar/remanga/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 94ccac65b..000000000
Binary files a/src/ar/remanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ar/remanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/remanga/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d2deb581f..000000000
Binary files a/src/ar/remanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ar/remanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/remanga/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 95a9e2f79..000000000
Binary files a/src/ar/remanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ar/remanga/src/eu/kanade/tachiyomi/extension/ar/remanga/REManga.kt b/src/ar/remanga/src/eu/kanade/tachiyomi/extension/ar/remanga/REManga.kt
deleted file mode 100644
index 348072b35..000000000
--- a/src/ar/remanga/src/eu/kanade/tachiyomi/extension/ar/remanga/REManga.kt
+++ /dev/null
@@ -1,258 +0,0 @@
-package eu.kanade.tachiyomi.extension.ar.remanga
-
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.source.model.Filter
-import eu.kanade.tachiyomi.source.model.FilterList
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.source.online.ParsedHttpSource
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.Request
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-class REManga : ParsedHttpSource() {
-
- override val name = "RE Manga"
-
- override val baseUrl = "https://re-manga.com"
-
- override val lang = "ar"
-
- override val supportsLatest = true
-
- // Popular
-
- override fun popularMangaRequest(page: Int): Request =
- GET("$baseUrl/manga-list/?title=&order=popular&status=&type=")
-
- override fun popularMangaSelector() = "article.animpost"
-
- override fun popularMangaFromElement(element: Element): SManga =
- SManga.create().apply {
- setUrlWithoutDomain(element.select("a").attr("abs:href"))
- element.select("img").let {
- thumbnail_url = it.attr("abs:src")
- title = it.attr("title")
- }
- }
-
- override fun popularMangaNextPageSelector(): String? = null
-
- // Latest
-
- override fun latestUpdatesRequest(page: Int): Request =
- GET("$baseUrl/manga-list/?title=&order=update&status=&type=")
-
- override fun latestUpdatesSelector() = popularMangaSelector()
-
- override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
-
- override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
-
- // Search
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val url = "$baseUrl/manga-list/?".toHttpUrl().newBuilder()
- .addQueryParameter("title", query)
- filters.forEach { filter ->
- when (filter) {
- is SortFilter -> url.addQueryParameter("order", filter.toUriPart())
-
- is StatusFilter -> url.addQueryParameter("status", filter.toUriPart())
-
- is TypeFilter -> url.addQueryParameter("type", filter.toUriPart())
-
- is GenreFilter -> {
- filter.state
- .filter { it.state != Filter.TriState.STATE_IGNORE }
- .forEach { url.addQueryParameter("genre[]", it.id) }
- }
-
- is YearFilter -> {
- filter.state
- .filter { it.state != Filter.TriState.STATE_IGNORE }
- .forEach { url.addQueryParameter("years[]", it.id) }
- }
- else -> {}
- }
- }
- return GET(url.build(), headers)
- }
-
- override fun searchMangaSelector() = popularMangaSelector()
-
- override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
-
- override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
-
- // Details
-
- override fun mangaDetailsParse(document: Document): SManga {
- return SManga.create().apply {
- document.select("div.infox").first()!!.let { info ->
- title = info.select("h1").text()
- }
- description = document.select("div.desc > div > p").text()
- genre = document.select("div.spe > span:contains(نوع), div.genre-info > a").joinToString { it.text() }
- document.select("div.spe > span:contains(الحالة)").first()?.text()?.also { statusText ->
- when {
- statusText.contains("مستمر", true) -> status = SManga.ONGOING
- else -> status = SManga.COMPLETED
- }
- }
- }
- }
-
- // Chapters
-
- override fun chapterListSelector() = ".lsteps li"
-
- override fun chapterFromElement(element: Element): SChapter {
- return SChapter.create().apply {
- setUrlWithoutDomain(element.select("a").first()!!.attr("abs:href"))
-
- val chNum = element.select(".eps > a").first()!!.text()
- val chTitle = element.select(".lchx > a").first()!!.text()
-
- name = when {
- chTitle.startsWith("الفصل ") -> chTitle
- else -> "الفصل $chNum - $chTitle"
- }
-
- element.select(".date").first()?.text()?.let { date ->
- date_upload = DATE_FORMATTER.parse(date)?.time ?: 0L
- }
- }
- }
-
- // Pages
-
- override fun pageListParse(document: Document): List {
- return document.select("div.reader-area img").mapIndexed { i, img ->
- Page(i, "", img.attr("abs:src"))
- }
- }
-
- override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
-
- // Filters
-
- override fun getFilterList() = FilterList(
- SortFilter(getSortFilters()),
- StatusFilter(getStatusFilters()),
- TypeFilter(getTypeFilter()),
- Filter.Separator(),
- Filter.Header("exclusion not available for This source"),
- GenreFilter(getGenreFilters()),
- YearFilter(getYearFilters()),
- )
-
- private class SortFilter(vals: Array>) : UriPartFilter("Sort by", vals)
-
- private class TypeFilter(vals: Array>) : UriPartFilter("Type", vals)
-
- private class StatusFilter(vals: Array>) : UriPartFilter("Status", vals)
-
- class Genre(name: String, val id: String = name) : Filter.TriState(name)
- private class GenreFilter(genres: List) : Filter.Group("Genre", genres)
-
- class Year(name: String, val id: String = name) : Filter.TriState(name)
- private class YearFilter(years: List) : Filter.Group("Year", years)
-
- private fun getSortFilters(): Array> = arrayOf(
- Pair("title", "A-Z"),
- Pair("titlereverse", "Z-A"),
- Pair("update", "Latest Update"),
- Pair("latest", "Latest Added"),
- Pair("popular", "Popular"),
- )
-
- private fun getStatusFilters(): Array> = arrayOf(
- Pair("", "All"),
- Pair("Publishing", "مستمر"),
- Pair("Finished", "تاريخ انتهي"),
- )
-
- private fun getTypeFilter(): Array> = arrayOf(
- Pair("", "All"),
- Pair("Manga", "Manga"),
- Pair("Manhwa", "Manhwa"),
- Pair("Manhua", "Manhua"),
- )
-
- private fun getGenreFilters(): List = listOf(
- Genre("Action", "action"),
- Genre("Adventure", "adventure"),
- Genre("Comedy", "comedy"),
- Genre("Dementia", "dementia"),
- Genre("Demons", "demons"),
- Genre("Drama", "drama"),
- Genre("Ecchi", "ecchi"),
- Genre("Fantasy", "fantasy"),
- Genre("Harem", "harem"),
- Genre("Historical", "historical"),
- Genre("Horror", "horror"),
- Genre("Josei", "josei"),
- Genre("Magic", "magic"),
- Genre("Martial Arts", "martial-arts"),
- Genre("Military", "military"),
- Genre("Mystery", "mystery"),
- Genre("Parody", "parody"),
- Genre("Psychological", "psychological"),
- Genre("Romance", "romance"),
- Genre("Samurai", "samurai"),
- Genre("School", "school"),
- Genre("Sci-Fi", "sci-fi"),
- Genre("Seinen", "seinen"),
- Genre("Shounen", "shounen"),
- Genre("Slice of Life", "slice-of-life"),
- Genre("Sports", "sports"),
- Genre("Super Power", "super-power"),
- Genre("Supernatural", "supernatural"),
- Genre("Vampire", "vampire"),
- )
-
- private fun getYearFilters(): List = listOf(
- Year("1970", "1970"),
- Year("1986", "1986"),
- Year("1989", "1989"),
- Year("1995", "1995"),
- Year("1997", "1997"),
- Year("1998", "1998"),
- Year("1999", "1999"),
- Year("2000", "2000"),
- Year("2002", "2002"),
- Year("2003", "2003"),
- Year("2004", "2004"),
- Year("2005", "2005"),
- Year("2006", "2006"),
- Year("2007", "2007"),
- Year("2008", "2008"),
- Year("2009", "2009"),
- Year("2010", "2010"),
- Year("2011", "2011"),
- Year("2012", "2012"),
- Year("2013", "2013"),
- Year("2014", "2014"),
- Year("2016", "2016"),
- Year("2017", "2017"),
- Year("2018", "2018"),
- Year("2019", "2019"),
- Year("2020", "2020"),
- )
-
- open class UriPartFilter(displayName: String, private val vals: Array>) :
- Filter.Select(displayName, vals.map { it.second }.toTypedArray()) {
- fun toUriPart() = vals[state].first
- }
-
- companion object {
- private val DATE_FORMATTER by lazy {
- SimpleDateFormat("MMM d, yyy", Locale("ar"))
- }
- }
-}
diff --git a/src/en/webtoonstop/res/mipmap-hdpi/ic_launcher.png b/src/en/webtoonstop/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index bf9859276..000000000
Binary files a/src/en/webtoonstop/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/webtoonstop/res/mipmap-mdpi/ic_launcher.png b/src/en/webtoonstop/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index ff3592eb3..000000000
Binary files a/src/en/webtoonstop/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/webtoonstop/res/mipmap-xhdpi/ic_launcher.png b/src/en/webtoonstop/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 66b1b0e08..000000000
Binary files a/src/en/webtoonstop/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/webtoonstop/res/mipmap-xxhdpi/ic_launcher.png b/src/en/webtoonstop/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 57b325d93..000000000
Binary files a/src/en/webtoonstop/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/webtoonstop/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/webtoonstop/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 79ec14ed9..000000000
Binary files a/src/en/webtoonstop/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/zuttomanga/res/mipmap-hdpi/ic_launcher.png b/src/en/zuttomanga/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 5cd506225..000000000
Binary files a/src/en/zuttomanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/zuttomanga/res/mipmap-mdpi/ic_launcher.png b/src/en/zuttomanga/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index f3fb710e2..000000000
Binary files a/src/en/zuttomanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/zuttomanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/zuttomanga/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 8113a7ef7..000000000
Binary files a/src/en/zuttomanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/zuttomanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/zuttomanga/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 6c889bf00..000000000
Binary files a/src/en/zuttomanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/en/zuttomanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/zuttomanga/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 2cc3f1bbd..000000000
Binary files a/src/en/zuttomanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/es/vcpvmp/res/ic_launcher-web.png b/src/es/vcpvmp/res/ic_launcher-web.png
deleted file mode 100644
index 0c5311d14..000000000
Binary files a/src/es/vcpvmp/res/ic_launcher-web.png and /dev/null differ
diff --git a/src/fr/sushiscan/src/eu/kanade/tachiyomi/extension/fr/sushiscan/SushiScan.kt b/src/fr/sushiscan/src/eu/kanade/tachiyomi/extension/fr/sushiscan/SushiScan.kt
index c25827756..4226a7555 100644
--- a/src/fr/sushiscan/src/eu/kanade/tachiyomi/extension/fr/sushiscan/SushiScan.kt
+++ b/src/fr/sushiscan/src/eu/kanade/tachiyomi/extension/fr/sushiscan/SushiScan.kt
@@ -55,12 +55,14 @@ class SushiScan :
.set("Referer", "$baseUrl$mangaUrlDirectory")
override val altNamePrefix = "Nom alternatif : "
- override val seriesAuthorSelector = ".imptdt:contains(Auteur) i, .fmed b:contains(Auteur)+span"
- override val seriesStatusSelector = ".imptdt:contains(Statut) i"
+ override val seriesAuthorSelector = ".infotable tr:contains(Auteur) td:last-child"
+ override val seriesStatusSelector = ".infotable tr:contains(Statut) td:last-child"
override fun String?.parseStatus(): Int = when {
this == null -> SManga.UNKNOWN
this.contains("En Cours", ignoreCase = true) -> SManga.ONGOING
this.contains("Terminé", ignoreCase = true) -> SManga.COMPLETED
+ this.contains("Abandonné", ignoreCase = true) -> SManga.CANCELLED
+ this.contains("En Pause", ignoreCase = true) -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
diff --git a/src/ru/remanga/AndroidManifest.xml b/src/ru/remanga/AndroidManifest.xml
deleted file mode 100644
index 1cb45bc3c..000000000
--- a/src/ru/remanga/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ru/remanga/build.gradle b/src/ru/remanga/build.gradle
deleted file mode 100644
index a3fa0412f..000000000
--- a/src/ru/remanga/build.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-ext {
- extName = 'Remanga'
- extClass = '.Remanga'
- extVersionCode = 86
-}
-
-apply from: "$rootDir/common.gradle"
-
-dependencies {
- implementation project(':lib:dataimage')
-}
diff --git a/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index bed2374f3..000000000
Binary files a/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 70bc4e01c..000000000
Binary files a/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 7f8fb8a44..000000000
Binary files a/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index be344a2e2..000000000
Binary files a/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index d6ef556c5..000000000
Binary files a/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt
deleted file mode 100644
index d7af9b13e..000000000
--- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt
+++ /dev/null
@@ -1,1053 +0,0 @@
-package eu.kanade.tachiyomi.extension.ru.remanga
-
-import android.annotation.TargetApi
-import android.app.Application
-import android.content.SharedPreferences
-import android.os.Build
-import android.widget.Toast
-import androidx.preference.ListPreference
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.BookDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.BranchesDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.ChunksPageDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExBookDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExLibraryDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExWrapperDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.LibraryDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.MangaDetDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.MyLibraryDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.PageDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.PageWrapperDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.PagesDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.SeriesWrapperDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.TagsDto
-import eu.kanade.tachiyomi.extension.ru.remanga.dto.UserDto
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.asObservable
-import eu.kanade.tachiyomi.network.asObservableSuccess
-import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
-import eu.kanade.tachiyomi.source.ConfigurableSource
-import eu.kanade.tachiyomi.source.model.Filter
-import eu.kanade.tachiyomi.source.model.FilterList
-import eu.kanade.tachiyomi.source.model.MangasPage
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.source.online.HttpSource
-import kotlinx.serialization.SerializationException
-import kotlinx.serialization.decodeFromString
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.decodeFromJsonElement
-import okhttp3.CacheControl
-import okhttp3.FormBody
-import okhttp3.Headers
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.Interceptor
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.RequestBody
-import okhttp3.RequestBody.Companion.toRequestBody
-import okhttp3.Response
-import okhttp3.ResponseBody.Companion.toResponseBody
-import org.jsoup.Jsoup
-import rx.Observable
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import java.io.IOException
-import java.net.URLDecoder
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.concurrent.TimeUnit.MINUTES
-import kotlin.math.absoluteValue
-import kotlin.random.Random
-
-class Remanga : ConfigurableSource, HttpSource() {
-
- override val name = "Remanga"
-
- override val id: Long = 8983242087533137528
-
- override val lang = "ru"
-
- private val preferences: SharedPreferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- private fun PUT(
- url: String,
- headers: Headers = Headers.Builder().build(),
- body: RequestBody = FormBody.Builder().build(),
- cache: CacheControl = CacheControl.Builder().maxAge(10, MINUTES).build(),
- ): Request {
- return Request.Builder()
- .url(url)
- .put(body)
- .headers(headers)
- .cacheControl(cache)
- .build()
- }
-
- private val baseOrig: String = "https://api.remanga.org"
- private val baseMirr: String = "https://api.xn--80aaig9ahr.xn--c1avg" // https://реманга.орг
- private val domain: String? = preferences.getString(DOMAIN_PREF, baseOrig)
-
- private val baseRuss: String = "https://exmanga.ru"
- private val baseUkr: String = "https://ex.euromc.com.ua"
- private val exManga: String = preferences.getString(exDOMAIN_PREF, baseRuss) ?: baseRuss
-
- override val baseUrl = domain.toString()
-
- override val supportsLatest = true
-
- private val userAgentRandomizer = "${Random.nextInt().absoluteValue}"
-
- override fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
- // Magic User-Agent, no change/update, does not cause 403
- if (!preferences.getBoolean(userAgent_PREF, false)) { add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.$userAgentRandomizer") }
- add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/jxl,image/webp,*/*;q=0.8")
- add("Referer", baseUrl.replace("api.", ""))
- }
-
- private fun exHeaders() = Headers.Builder()
- .set("User-Agent", "Tachiyomi")
- .set("Accept", "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
- .set("Referer", baseUrl.replace("api.", ""))
- .build()
- private fun authIntercept(chain: Interceptor.Chain): Response {
- val request = chain.request()
-
- // authorization breaks exManga
- if (request.url.toString().contains(exManga)) {
- return chain.proceed(request)
- }
-
- val cookies = client.cookieJar.loadForRequest(baseUrl.replace("api.", "").toHttpUrl())
- val authCookie = cookies
- .firstOrNull { cookie -> cookie.name == "user" }
- ?.let { cookie -> URLDecoder.decode(cookie.value, "UTF-8") }
- ?.let { jsonString -> json.decodeFromString(jsonString) }
- ?: return chain.proceed(request)
-
- val access_token = cookies
- .firstOrNull { cookie -> cookie.name == "token" }
- ?.let { cookie -> URLDecoder.decode(cookie.value, "UTF-8") }
- ?: return chain.proceed(request)
-
- USER_ID = authCookie.id.toString()
-
- val authRequest = request.newBuilder()
- .addHeader("Authorization", "bearer $access_token")
- .build()
- return chain.proceed(authRequest)
- }
- private fun imageContentTypeIntercept(chain: Interceptor.Chain): Response {
- val originalRequest = chain.request()
- val response = chain.proceed(originalRequest)
- val urlRequest = originalRequest.url.toString()
- val possibleType = urlRequest.substringAfterLast("/").substringBefore("?").split(".")
- return if (urlRequest.contains("/images/") and (possibleType.size == 2)) {
- val realType = possibleType[1]
- val image = response.body.byteString().toResponseBody("image/$realType".toMediaType())
- response.newBuilder().body(image).build()
- } else {
- response
- }
- }
-
- private val loadLimit = if (!preferences.getBoolean(bLoad_PREF, false)) 1 else 3
-
- override val client: OkHttpClient =
- network.cloudflareClient.newBuilder()
- .rateLimitHost("https://img3.reimg.org".toHttpUrl(), loadLimit, 2)
- .rateLimitHost("https://img5.reimg.org".toHttpUrl(), loadLimit, 2)
- .rateLimitHost(exManga.toHttpUrl(), 3)
- .addInterceptor { imageContentTypeIntercept(it) }
- .addInterceptor { authIntercept(it) }
- .addInterceptor { chain ->
- val originalRequest = chain.request()
- val response = chain.proceed(originalRequest)
- if (originalRequest.url.toString().contains(exManga) and !response.isSuccessful) {
- val errorText = json.decodeFromString>(response.body.string()).data
- if (errorText.isEmpty()) {
- throw IOException("HTTP error ${response.code}. Домен ${exManga.substringAfter("//")} сервиса ExManga недоступен, выберите другой в настройках ⚙️ расширения")
- } else {
- throw IOException("HTTP error ${response.code}. ExManga: $errorText")
- }
- }
- response
- }
- .addNetworkInterceptor { chain ->
- val originalRequest = chain.request()
- val response = chain.proceed(originalRequest)
- if (originalRequest.url.toString().contains(baseUrl) and ((response.code == 403) or (response.code == 500))) {
- val indicateUAgant = if (headers["User-Agent"].orEmpty().contains(userAgentRandomizer)) "☒" else "☑"
- throw IOException("HTTP error ${response.code}. Попробуйте сменить Домен ${baseUrl.replace(baseMirr.substringAfter("api."), "реманга.орг").substringAfter("//")} и/или User-Agent$indicateUAgant в настройках ⚙️ расширения.")
- }
- response
- }
- .build()
-
- private val count = 30
-
- private var branches = mutableMapOf>()
-
- private var mangaIDs = mutableMapOf()
-
- override fun popularMangaRequest(page: Int): Request {
- val url = "$baseUrl/api/search/catalog/?ordering=-rating&count=$count&page=$page&count_chapters_gte=1".toHttpUrl().newBuilder()
- if (preferences.getBoolean(isLib_PREF, false)) {
- url.addQueryParameter("exclude_bookmarks", "1")
- }
- return GET(url.build(), headers)
- }
-
- override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
-
- override fun latestUpdatesRequest(page: Int): Request {
- val url = "$baseUrl/api/search/catalog/?ordering=-chapter_date&count=$count&page=$page&count_chapters_gte=1".toHttpUrl().newBuilder()
- if (preferences.getBoolean(isLib_PREF, false)) {
- url.addQueryParameter("exclude_bookmarks", "1")
- }
- return GET(url.build(), headers)
- }
-
- override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
-
- override fun searchMangaParse(response: Response): MangasPage {
- if (response.request.url.toString().contains(exManga)) {
- val page = json.decodeFromString>>(response.body.string())
- val mangas = page.data.map {
- it.toSManga()
- }
-
- return MangasPage(mangas, true)
- } else if (response.request.url.toString().contains("/bookmarks/")) {
- val page = json.decodeFromString>(response.body.string())
- val mangas = page.content.map {
- it.title.toSManga()
- }
-
- return MangasPage(mangas, true)
- } else {
- val page = json.decodeFromString>(response.body.string())
-
- val mangas = page.content.map {
- it.toSManga()
- }
-
- return MangasPage(mangas, page.props.page < page.props.total_pages!!)
- }
- }
- private fun ExLibraryDto.toSManga(): SManga =
- SManga.create().apply {
- // Do not change the title name to ensure work with a multilingual catalog!
- title = name
- url = "/api/titles/$dir/"
- thumbnail_url = baseUrl + img
- }
-
- private fun LibraryDto.toSManga(): SManga =
- SManga.create().apply {
- // Do not change the title name to ensure work with a multilingual catalog!
- title = if (isEng.equals("rus")) rus_name else en_name
- url = "/api/titles/$dir/"
- thumbnail_url = baseUrl + img.mid
- }
-
- private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US) }
-
- private fun parseDate(date: String?): Long {
- date ?: return Date().time
- return try {
- simpleDateFormat.parse(date)!!.time
- } catch (_: Exception) {
- Date().time
- }
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- var url = "$baseUrl/api/search/catalog/?page=$page&count_chapters_gte=1".toHttpUrl().newBuilder()
- if (query.isNotEmpty()) {
- url = "$baseUrl/api/search/?page=$page".toHttpUrl().newBuilder()
- url.addQueryParameter("query", query)
- }
- (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
- when (filter) {
- is OrderBy -> {
- val ord = arrayOf("id", "chapter_date", "rating", "votes", "views", "count_chapters", "random")[filter.state!!.index]
- url.addQueryParameter("ordering", if (filter.state!!.ascending) ord else "-$ord")
- }
- is CategoryList -> filter.state.forEach { category ->
- if (category.state != Filter.TriState.STATE_IGNORE) {
- url.addQueryParameter(if (category.isIncluded()) "categories" else "exclude_categories", category.id)
- }
- }
- is TypeList -> filter.state.forEach { type ->
- if (type.state != Filter.TriState.STATE_IGNORE) {
- url.addQueryParameter(if (type.isIncluded()) "types" else "exclude_types", type.id)
- }
- }
- is StatusList -> filter.state.forEach { status ->
- if (status.state) {
- url.addQueryParameter("status", status.id)
- }
- }
- is AgeList -> filter.state.forEach { age ->
- if (age.state) {
- url.addQueryParameter("age_limit", age.id)
- }
- }
- is GenreList -> filter.state.forEach { genre ->
- if (genre.state != Filter.TriState.STATE_IGNORE) {
- url.addQueryParameter(if (genre.isIncluded()) "genres" else "exclude_genres", genre.id)
- }
- }
- is MyList -> {
- if (filter.state > 0) {
- if (USER_ID == "") {
- throw Exception("Пользователь не найден, необходима авторизация через WebView\uD83C\uDF0E")
- }
- val TypeQ = getMyList()[filter.state].id
- return GET("$baseUrl/api/users/$USER_ID/bookmarks/?type=$TypeQ&page=$page", headers)
- }
- }
- is RequireChapters -> {
- if (filter.state == 1) {
- url.setQueryParameter("count_chapters_gte", "0")
- }
- }
- is RequireEX -> {
- if (filter.state == 1) {
- return GET("$exManga/manga?take=20&skip=${10 * (page - 1)}&name=$query", exHeaders())
- }
- }
- else -> {}
- }
- }
-
- if (preferences.getBoolean(isLib_PREF, false)) {
- url.addQueryParameter("exclude_bookmarks", "1")
- }
-
- return GET(url.build(), headers)
- }
-
- private fun parseStatus(status: Int): Int {
- return when (status) {
- 0 -> SManga.COMPLETED // Закончен
- 1 -> SManga.ONGOING // Продолжается
- 2 -> SManga.ON_HIATUS // Заморожен
- 3 -> SManga.ON_HIATUS // Нет переводчика
- 4 -> SManga.ONGOING // Анонс
- // 5 -> SManga.LICENSED // Лицензировано // Hides available chapters!
- else -> SManga.UNKNOWN
- }
- }
-
- private fun parseType(type: TagsDto): String {
- return when (type.name) {
- "Западный комикс" -> "Комикс"
- else -> type.name
- }
- }
- private fun parseAge(age_limit: Int): String {
- return when (age_limit) {
- 2 -> "18+"
- 1 -> "16+"
- else -> ""
- }
- }
-
- private fun MangaDetDto.toSManga(): SManga {
- val ratingValue = avg_rating.toFloat()
- val ratingStar = when {
- ratingValue > 9.5 -> "★★★★★"
- ratingValue > 8.5 -> "★★★★✬"
- ratingValue > 7.5 -> "★★★★☆"
- ratingValue > 6.5 -> "★★★✬☆"
- ratingValue > 5.5 -> "★★★☆☆"
- ratingValue > 4.5 -> "★★✬☆☆"
- ratingValue > 3.5 -> "★★☆☆☆"
- ratingValue > 2.5 -> "★✬☆☆☆"
- ratingValue > 1.5 -> "★☆☆☆☆"
- ratingValue > 0.5 -> "✬☆☆☆☆"
- else -> "☆☆☆☆☆"
- }
- val o = this
- return SManga.create().apply {
- // Do not change the title name to ensure work with a multilingual catalog!
- title = if (isEng.equals("rus")) rus_name else en_name
- url = "/api/titles/$dir/"
- thumbnail_url = baseUrl + img.high
- var altName = ""
- if (another_name.isNotEmpty()) {
- altName = "Альтернативные названия:\n" + another_name + "\n"
- }
- val mediaNameLanguage = if (isEng.equals("rus")) en_name else rus_name
- this.description = "$mediaNameLanguage\n$ratingStar $ratingValue (голосов: $count_rating)\n$altName" +
- o.description?.let { Jsoup.parse(it) }
- ?.select("body:not(:has(p)),p,br")
- ?.prepend("\\n")?.text()?.replace("\\n", "\n")?.replace("\n ", "\n")
- .orEmpty()
- genre = (parseType(type) + ", " + parseAge(age_limit) + ", " + (genres + categories).joinToString { it.name }).split(", ").filter { it.isNotEmpty() }.joinToString { it.trim() }
- status = parseStatus(o.status.id)
- }
- }
- private fun titleDetailsRequest(manga: SManga): Request {
- return GET(baseUrl + manga.url, headers)
- }
-
- // Workaround to allow "Open in browser" use the real URL.
- override fun fetchMangaDetails(manga: SManga): Observable {
- var warnLogin = false
- return client.newCall(titleDetailsRequest(manga))
- .asObservable().doOnNext { response ->
- if (!response.isSuccessful) {
- response.close()
- if (USER_ID == "") warnLogin = true else throw Exception("HTTP error ${response.code}")
- }
- }
- .map { response ->
- (if (warnLogin) manga.apply { description = "Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E︎" } else mangaDetailsParse(response))
- .apply {
- initialized = true
- }
- }
- }
- override fun mangaDetailsRequest(manga: SManga): Request {
- return GET(baseUrl.replace("api.", "") + "/manga/" + manga.url.substringAfter("/api/titles/", "/"), headers)
- }
- override fun mangaDetailsParse(response: Response): SManga {
- val series = json.decodeFromString>(response.body.string())
- branches[series.content.dir] = series.content.branches
- mangaIDs[series.content.dir] = series.content.id
- return series.content.toSManga()
- }
-
- private fun mangaBranches(manga: SManga): List {
- val requestString = client.newCall(GET(baseUrl + manga.url, headers)).execute()
- if (!requestString.isSuccessful) {
- if (USER_ID == "") {
- throw Exception("HTTP error ${requestString.code}. Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E")
- }
- throw Exception("HTTP error ${requestString.code}")
- }
- val responseString = requestString.body.string()
- // manga requiring login return "content" as a JsonArray instead of the JsonObject we expect
- // callback request for update outside the library
- val content = json.decodeFromString(responseString)["content"]
- return if (content is JsonObject) {
- val series = json.decodeFromJsonElement(content)
- branches[series.dir] = series.branches
- mangaIDs[series.dir] = series.id
- if (series.status.id == 5 && series.branches.maxByOrNull { selector(it) }!!.count_chapters == 0) {
- throw Exception("Лицензировано - Нет глав")
- }
- series.branches
- } else {
- emptyList()
- }
- }
-
- private fun filterPaid(tempChaptersList: MutableList): MutableList {
- return if (!preferences.getBoolean(PAID_PREF, false)) {
- val lastEx = tempChaptersList.find { !it.name.contains("\uD83D\uDCB2") }
- tempChaptersList.filterNot {
- it.name.contains("\uD83D\uDCB2") && if (lastEx != null) {
- val volCor = it.name.substringBefore(
- ". Глава",
- ).toIntOrNull()!!
- val volLast = lastEx.name.substringBefore(". Глава").toIntOrNull()!!
- (volCor > volLast) ||
- ((volCor == volLast) && (it.chapter_number > lastEx.chapter_number))
- } else {
- false
- }
- } as MutableList
- } else {
- tempChaptersList
- }
- }
-
- private fun selector(b: BranchesDto): Int = b.count_chapters
- override fun fetchChapterList(manga: SManga): Observable> {
- val branch = branches.getOrElse(manga.url.substringAfter("/api/titles/").substringBefore("/").substringBefore("?")) { mangaBranches(manga) }
- return when {
- branch.maxByOrNull { selector(it) }!!.count_chapters == 0 -> {
- Observable.error(Exception("Лицензировано - Нет глав"))
- }
- branch.isEmpty() -> {
- if (USER_ID == "") {
- Observable.error(Exception("Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E"))
- } else {
- return Observable.just(listOf())
- }
- }
- else -> {
- val mangaID = mangaIDs[manga.url.substringAfter("/api/titles/").substringBefore("/").substringBefore("?")]
- val exChapters = if (preferences.getBoolean(exPAID_PREF, true)) {
- json.decodeFromString>>(client.newCall(GET("$exManga/chapter/history/$mangaID", exHeaders())).execute().body.string()).data
- } else {
- emptyList()
- }
- val selectedBranch = branch.maxByOrNull { selector(it) }!!
- val tempChaptersList = mutableListOf()
- (1..(selectedBranch.count_chapters / 300 + 1)).map {
- val response = chapterListRequest(selectedBranch.id, it)
- chapterListParse(response, manga, exChapters)
- }.let { tempChaptersList.addAll(it.flatten()) }
- if (branch.size > 1) {
- val selectedBranch2 =
- branch.filter { it.id != selectedBranch.id }.maxByOrNull { selector(it) }!!
- if (selectedBranch2.count_chapters > 0) {
- if (selectedBranch.count_chapters < (
- json.decodeFromString>>(
- chapterListRequest(selectedBranch2.id, 1).body.string(),
- ).content.firstOrNull()?.chapter?.toFloatOrNull() ?: -2F
- )
- ) {
- (1..(selectedBranch2.count_chapters / 300 + 1)).map {
- val response = chapterListRequest(selectedBranch2.id, it)
- chapterListParse(response, manga, exChapters)
- }.let { tempChaptersList.addAll(0, it.flatten()) }
- return filterPaid(tempChaptersList).distinctBy { it.name.substringBefore(". Глава") + "--" + it.chapter_number }.sortedWith(compareBy({ it.name.substringBefore(". Глава").toIntOrNull()!! }, { it.chapter_number })).reversed().let { Observable.just(it) }
- }
- }
- }
-
- return filterPaid(tempChaptersList).let { Observable.just(it) }
- }
- }
- }
-
- private fun chapterListRequest(branch: Long, page: Number): Response =
- client.newCall(
- GET(
- "$baseUrl/api/titles/chapters/?branch_id=$branch&page=$page&count=300",
- headers,
- ),
- ).execute().run {
- if (!isSuccessful) {
- close()
- throw Exception("HTTP error $code")
- }
- this
- }
-
- override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
-
- private fun chapterListParse(response: Response, manga: SManga, exChapters: List): List {
- val chapters = json.decodeFromString>>(response.body.string()).content
-
- val chaptersList = chapters.map { chapter ->
- SChapter.create().apply {
- chapter_number = chapter.chapter.split(".").take(2).joinToString(".").toFloat()
- url = "/manga/${manga.url.substringAfterLast("/api/titles/")}ch${chapter.id}"
- date_upload = parseDate(chapter.upload_date)
- scanlator = if (chapter.publishers.isNotEmpty()) {
- chapter.publishers.joinToString { it.name }
- } else {
- null
- }
-
- var exChID = exChapters.find { (it.id == chapter.id) || ((it.tome == chapter.tome) && (it.chapter == chapter.chapter)) }
- if (preferences.getBoolean(exPAID_PREF, true)) {
- if (chapter.is_paid and (chapter.is_bought != true)) {
- if (exChID != null) {
- url = "/chapter?id=${exChID.id}"
- scanlator = "exmanga"
- }
- }
-
- if (chapter.is_paid and (chapter.is_bought == true)) {
- url = "$url#is_bought"
- }
- } else {
- exChID = null
- }
-
- var chapterName = "${chapter.tome}. Глава ${chapter.chapter}"
- if (chapter.is_paid and (chapter.is_bought != true) and (exChID == null)) {
- chapterName += " \uD83D\uDCB2 "
- }
- if (chapter.name.isNotBlank()) {
- chapterName += " ${chapter.name.capitalize()}"
- }
-
- name = chapterName
- }
- }
- return chaptersList
- }
-
- private fun fixLink(link: String): String {
- if (!link.startsWith("http")) {
- return baseUrl.replace("api.", "") + link
- }
- return link
- }
-
- @TargetApi(Build.VERSION_CODES.N)
- private fun pageListParse(response: Response, chapter: SChapter): List {
- val body = response.body.string()
- val heightEmptyChunks = 10
- if (chapter.scanlator.equals("exmanga")) {
- try {
- val exPage = json.decodeFromString>>>(body)
- val result = mutableListOf()
- exPage.data.forEach {
- it.filter { page -> page.height > heightEmptyChunks }.forEach { page ->
- result.add(Page(result.size, "", page.link))
- }
- }
- return result
- } catch (e: SerializationException) {
- throw IOException("Главы больше нет на ExManga. Попробуйте обновить список глав (свайп сверху).")
- }
- } else {
- if (chapter.url.contains("#is_bought") and (preferences.getBoolean(exPAID_PREF, true))) {
- val newHeaders = exHeaders().newBuilder()
- .add("Content-Type", "application/json")
- .build()
- client.newCall(
- PUT(
- "$exManga/chapter",
- newHeaders,
- body.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
- ),
- ).execute()
- }
- return try {
- val page = json.decodeFromString>(body)
- page.content.pages.filter { it.height > heightEmptyChunks }.mapIndexed { index, it ->
- Page(index, "", fixLink(it.link))
- }
- } catch (e: SerializationException) {
- val page = json.decodeFromString>(body)
- val result = mutableListOf()
- page.content.pages.forEach {
- it.filter { page -> page.height > heightEmptyChunks }.forEach { page ->
- result.add(Page(result.size, "", fixLink(page.link)))
- }
- }
- return result
- }
- }
- }
-
- override fun pageListParse(response: Response): List = throw UnsupportedOperationException()
-
- override fun pageListRequest(chapter: SChapter): Request {
- return if (chapter.scanlator.equals("exmanga")) {
- GET(exManga + chapter.url, exHeaders())
- } else {
- if (chapter.name.contains("\uD83D\uDCB2")) {
- val noEX = if (preferences.getBoolean(exPAID_PREF, true)) {
- "Расширение отправляет данные на удаленный сервер ExManga только при открытии глав покупаемой манги."
- } else { "Функции ExManga отключены." }
- throw IOException("Глава платная. $noEX")
- }
- GET(baseUrl + "/api/titles/chapters/" + chapter.url.substringAfterLast("/ch").substringBefore("#is_bought") + "/", headers)
- }
- }
-
- override fun fetchPageList(chapter: SChapter): Observable> {
- return client.newCall(pageListRequest(chapter))
- .asObservableSuccess()
- .map { response ->
- pageListParse(response, chapter)
- }
- }
-
- override fun getChapterUrl(chapter: SChapter): String {
- return if (chapter.scanlator.equals("exmanga")) exManga + chapter.url else baseUrl.replace("api.", "") + chapter.url.substringBefore("#is_bought")
- }
-
- override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!)
-
- override fun imageUrlRequest(page: Page): Request = throw NotImplementedError("Unused")
-
- override fun imageUrlParse(response: Response): String = throw NotImplementedError("Unused")
-
- private fun searchMangaByIdRequest(id: String): Request {
- return GET("$baseUrl/api/titles/$id/", headers)
- }
-
- override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
- return if (query.startsWith(PREFIX_SLUG_SEARCH)) {
- val realQuery = query.removePrefix(PREFIX_SLUG_SEARCH)
- client.newCall(searchMangaByIdRequest(realQuery))
- .asObservableSuccess()
- .map { response ->
- val details = mangaDetailsParse(response)
- details.url = "/api/titles/$realQuery/"
- MangasPage(listOf(details), false)
- }
- } else {
- client.newCall(searchMangaRequest(page, query, filters))
- .asObservableSuccess()
- .map { response ->
- searchMangaParse(response)
- }
- }
- }
-
- override fun imageRequest(page: Page): Request {
- val refererHeaders = headersBuilder().build()
- return if (page.imageUrl!!.contains(exManga)) {
- GET(page.imageUrl!!, exHeaders())
- } else {
- GET(page.imageUrl!!, refererHeaders)
- }
- }
-
- private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
- private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name)
-
- private class CategoryList(categories: List) : Filter.Group("Категории", categories)
- private class TypeList(types: List) : Filter.Group("Типы", types)
- private class StatusList(statuses: List) : Filter.Group("Статус", statuses)
- private class GenreList(genres: List) : Filter.Group("Жанры", genres)
- private class AgeList(ages: List) : Filter.Group("Возрастное ограничение", ages)
-
- override fun getFilterList() = FilterList(
- RequireEX(),
- OrderBy(),
- GenreList(getGenreList()),
- CategoryList(getCategoryList()),
- TypeList(getTypeList()),
- StatusList(getStatusList()),
- AgeList(getAgeList()),
- MyList(MyStatus),
- RequireChapters(),
- )
-
- private class OrderBy : Filter.Sort(
- "Сортировка",
- arrayOf("Новизне", "Последним обновлениям", "Популярности", "Лайкам", "Просмотрам", "По кол-ву глав", "Мне повезет"),
- Selection(2, false),
- )
-
- private fun getAgeList() = listOf(
- CheckFilter("Для всех", "0"),
- CheckFilter("16+", "1"),
- CheckFilter("18+", "2"),
- )
-
- private fun getTypeList() = listOf(
- SearchFilter("Манга", "0"),
- SearchFilter("Манхва", "1"),
- SearchFilter("Маньхуа", "2"),
- SearchFilter("Западный комикс", "3"),
- SearchFilter("Рукомикс", "4"),
- SearchFilter("Индонезийский комикс", "5"),
- SearchFilter("Другое", "6"),
- )
-
- private fun getStatusList() = listOf(
- CheckFilter("Закончен", "0"),
- CheckFilter("Продолжается", "1"),
- CheckFilter("Заморожен", "2"),
- CheckFilter("Нет переводчика", "3"),
- CheckFilter("Анонс", "4"),
- CheckFilter("Лицензировано", "5"),
- )
-
- private fun getCategoryList() = listOf(
- SearchFilter("веб", "5"),
- SearchFilter("в цвете", "6"),
- SearchFilter("ёнкома", "8"),
- SearchFilter("сборник", "10"),
- SearchFilter("сингл", "11"),
- SearchFilter("алхимия", "47"),
- SearchFilter("ангелы", "48"),
- SearchFilter("антигерой", "26"),
- SearchFilter("антиутопия", "49"),
- SearchFilter("апокалипсис", "50"),
- SearchFilter("аристократия", "117"),
- SearchFilter("армия", "51"),
- SearchFilter("артефакты", "52"),
- SearchFilter("амнезия / потеря памяти", "123"),
- SearchFilter("боги", "45"),
- SearchFilter("борьба за власть", "52"),
- SearchFilter("будущее", "55"),
- SearchFilter("бои на мечах", "122"),
- SearchFilter("вампиры", "112"),
- SearchFilter("вестерн", "56"),
- SearchFilter("видеоигры", "35"),
- SearchFilter("виртуальная реальность", "44"),
- SearchFilter("владыка демонов", "57"),
- SearchFilter("военные", "29"),
- SearchFilter("волшебные существа", "59"),
- SearchFilter("воспоминания из другого мира", "60"),
- SearchFilter("врачи / доктора", "116"),
- SearchFilter("выживание", "41"),
- SearchFilter("горничные", "23"),
- SearchFilter("гяру", "28"),
- SearchFilter("гг женщина", "63"),
- SearchFilter("гг мужчина", "64"),
- SearchFilter("умный гг", "111"),
- SearchFilter("тупой гг", "109"),
- SearchFilter("гг имба", "110"),
- SearchFilter("гг не человек", "123"),
- SearchFilter("грузовик-сан", "125"),
- SearchFilter("геймеры", "61"),
- SearchFilter("гильдии", "62"),
- SearchFilter("гоблины", "65"),
- SearchFilter("девушки-монстры", "37"),
- SearchFilter("демоны", "15"),
- SearchFilter("драконы", "66"),
- SearchFilter("дружба", "67"),
- SearchFilter("жестокий мир", "69"),
- SearchFilter("животные компаньоны", "70"),
- SearchFilter("завоевание мира", "71"),
- SearchFilter("зверолюди", "19"),
- SearchFilter("зомби", "14"),
- SearchFilter("игровые элементы", "73"),
- SearchFilter("исекай", "115"),
- SearchFilter("квесты", "75"),
- SearchFilter("космос", "76"),
- SearchFilter("кулинария", "16"),
- SearchFilter("культивация", "18"),
- SearchFilter("лоли", "108"),
- SearchFilter("магическая академия", "78"),
- SearchFilter("магия", "22"),
- SearchFilter("мафия", "24"),
- SearchFilter("медицина", "17"),
- SearchFilter("месть", "79"),
- SearchFilter("монстры", "38"),
- SearchFilter("музыка", "39"),
- SearchFilter("навыки / способности", "80"),
- SearchFilter("наёмники", "81"),
- SearchFilter("насилие / жестокость", "82"),
- SearchFilter("нежить", "83"),
- SearchFilter("ниндзя", "30"),
- SearchFilter("офисные работники", "40"),
- SearchFilter("обратный гарем", "40"),
- SearchFilter("оборотни", "113"),
- SearchFilter("пародия", "85"),
- SearchFilter("подземелья", "86"),
- SearchFilter("политика", "87"),
- SearchFilter("полиция", "32"),
- SearchFilter("преступники / криминал", "36"),
- SearchFilter("призраки / духи", "27"),
- SearchFilter("прокачка", "118"),
- SearchFilter("путешествия во времени", "43"),
- SearchFilter("разумные расы", "88"),
- SearchFilter("ранги силы", "68"),
- SearchFilter("реинкарнация", "13"),
- SearchFilter("роботы", "89"),
- SearchFilter("рыцари", "90"),
- SearchFilter("средневековье", "25"),
- SearchFilter("самураи", "33"),
- SearchFilter("система", "91"),
- SearchFilter("скрытие личности", "93"),
- SearchFilter("спасение мира", "94"),
- SearchFilter("стимпанк", "92"),
- SearchFilter("супергерои", "95"),
- SearchFilter("традиционные игры", "34"),
- SearchFilter("учитель / ученик", "96"),
- SearchFilter("управление территорией", "114"),
- SearchFilter("философия", "97"),
- SearchFilter("хентай", "12"),
- SearchFilter("хикикомори", "21"),
- SearchFilter("шантаж", "99"),
- SearchFilter("эльфы", "46"),
- )
-
- private fun getGenreList() = listOf(
- SearchFilter("боевые искусства", "3"),
- SearchFilter("гарем", "5"),
- SearchFilter("гендерная интрига", "6"),
- SearchFilter("героическое фэнтези", "7"),
- SearchFilter("детектив", "8"),
- SearchFilter("дзёсэй", "9"),
- SearchFilter("додзинси", "10"),
- SearchFilter("драма", "11"),
- SearchFilter("история", "13"),
- SearchFilter("киберпанк", "14"),
- SearchFilter("кодомо", "15"),
- SearchFilter("комедия", "50"),
- SearchFilter("махо-сёдзё", "17"),
- SearchFilter("меха", "18"),
- SearchFilter("мистика", "19"),
- SearchFilter("мурим", "51"),
- SearchFilter("научная фантастика", "20"),
- SearchFilter("повседневность", "21"),
- SearchFilter("постапокалиптика", "22"),
- SearchFilter("приключения", "23"),
- SearchFilter("психология", "24"),
- SearchFilter("психодел-упоротость-треш", "124"),
- SearchFilter("романтика", "25"),
- SearchFilter("сверхъестественное", "27"),
- SearchFilter("сёдзё", "28"),
- SearchFilter("сёдзё-ай", "29"),
- SearchFilter("сёнэн", "30"),
- SearchFilter("сёнэн-ай", "31"),
- SearchFilter("спорт", "32"),
- SearchFilter("сэйнэн", "33"),
- SearchFilter("трагедия", "34"),
- SearchFilter("триллер", "35"),
- SearchFilter("ужасы", "36"),
- SearchFilter("фантастика", "37"),
- SearchFilter("фэнтези", "38"),
- SearchFilter("школьная жизнь", "39"),
- SearchFilter("экшен", "2"),
- SearchFilter("элементы юмора", "16"),
- SearchFilter("эротика", "42"),
- SearchFilter("этти", "40"),
- SearchFilter("юри", "41"),
- )
- private class MyList(favorites: Array) : Filter.Select("Закладки (только)", favorites)
- private data class MyListUnit(val name: String, val id: String)
- private val MyStatus = getMyList().map {
- it.name
- }.toTypedArray()
-
- private fun getMyList() = listOf(
- MyListUnit("Каталог", "-"),
- MyListUnit("Все закладки", "all"),
- MyListUnit("Читаю", "1"),
- MyListUnit("Буду читать", "2"),
- MyListUnit("Прочитано", "3"),
- MyListUnit("Брошено ", "4"),
- MyListUnit("Отложено", "5"),
- MyListUnit("Не интересно ", "6"),
- )
-
- private class RequireChapters : Filter.Select(
- "Только проекты с главами",
- arrayOf("Да", "Все"),
- )
-
- private class RequireEX : Filter.Select(
- "Использовать поиск",
- arrayOf("Remanga", "ExManga(без фильтров)"),
- )
- private var isEng: String? = preferences.getString(LANGUAGE_PREF, "eng")
- override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
- val userAgentSystem = androidx.preference.CheckBoxPreference(screen.context).apply {
- key = userAgent_PREF
- title = "User-Agent приложения"
- summary = "Использует User-Agent приложения, прописанный в настройках приложения (Настройки -> Дополнительно)"
- setDefaultValue(false)
-
- setOnPreferenceChangeListener { _, newValue ->
- val warning = "Для смены User-Agent(а) необходимо перезапустить приложение с полной остановкой."
- Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
- true
- }
- }
-
- val domainPref = ListPreference(screen.context).apply {
- key = DOMAIN_PREF
- title = "Выбор домена"
- entries = arrayOf("Основной (remanga.org)", "Основной (api.remanga.org)", "Зеркало (реманга.орг)", "Зеркало (api.реманга.орг)")
- entryValues = arrayOf(baseOrig.replace("api.", ""), baseOrig, baseMirr.replace("api.", ""), baseMirr)
- summary = "%s"
- setDefaultValue(baseOrig.replace("api.", ""))
- setOnPreferenceChangeListener { _, newValue ->
- val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
- Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
- true
- }
- }
- val titleLanguagePref = ListPreference(screen.context).apply {
- key = LANGUAGE_PREF
- title = "Выбор языка на обложке"
- entries = arrayOf("Английский", "Русский")
- entryValues = arrayOf("eng", "rus")
- summary = "%s"
- setDefaultValue("eng")
- setOnPreferenceChangeListener { _, newValue ->
- val warning = "Если язык обложки не изменился очистите базу данных в приложении (Настройки -> Дополнительно -> Очистить базу данных)"
- Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
- true
- }
- }
- val paidChapterShow = androidx.preference.CheckBoxPreference(screen.context).apply {
- key = PAID_PREF
- title = "Показывать все платные главы"
- summary = "Показывает не купленные\uD83D\uDCB2 главы(может вызвать ошибки при обновлении/автозагрузке)"
- setDefaultValue(false)
- }
- val exChapterShow = androidx.preference.CheckBoxPreference(screen.context).apply {
- key = exPAID_PREF
- title = "Показывать главы из ExManga"
- summary = "Показывает главы купленные другими людьми и поделившиеся ими через браузерное расширение ExManga. \n\n" +
- "ⓘЧастично отображает не купленные\uD83D\uDCB2 главы для соблюдения порядка глав. \n\n" +
- "ⓘТакже отправляет купленные главы из Tachiyomi в ExManga."
- setDefaultValue(true)
- }
- val domainExPref = ListPreference(screen.context).apply {
- key = exDOMAIN_PREF
- title = "Выбор домена для ExManga"
- entries = arrayOf("Россия (exmanga.ru)", "Украина (ex.euromc.com.ua)")
- entryValues = arrayOf(baseRuss, baseUkr)
- summary = "%s"
- setDefaultValue(baseRuss)
- setOnPreferenceChangeListener { _, newValue ->
- val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
- Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
- true
- }
- }
- val bookmarksHide = androidx.preference.CheckBoxPreference(screen.context).apply {
- key = isLib_PREF
- title = "Скрыть «Закладки»"
- summary = "Скрывает мангу находящуюся в закладках пользователя на сайте."
- setDefaultValue(false)
- }
-
- val boostLoad = androidx.preference.CheckBoxPreference(screen.context).apply {
- key = bLoad_PREF
- title = "Ускорить скачивание глав"
- summary = "Увеличивает количество скачиваемых страниц в секунду, но Remanga быстрее ограничит скорость скачивания."
- setDefaultValue(false)
-
- setOnPreferenceChangeListener { _, newValue ->
- val warning = "Для применения настройки необходимо перезапустить приложение с полной остановкой."
- Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
- true
- }
- }
-
- screen.addPreference(userAgentSystem)
- screen.addPreference(domainPref)
- screen.addPreference(titleLanguagePref)
- screen.addPreference(paidChapterShow)
- screen.addPreference(exChapterShow)
- screen.addPreference(domainExPref)
- screen.addPreference(bookmarksHide)
- screen.addPreference(boostLoad)
- }
-
- private val json: Json by injectLazy()
-
- companion object {
-
- private var USER_ID = ""
-
- const val PREFIX_SLUG_SEARCH = "slug:"
-
- private const val userAgent_PREF = "UAgent"
-
- private const val bLoad_PREF = "boostLoad_PREF"
-
- private const val DOMAIN_PREF = "REMangaDomain"
-
- private const val exDOMAIN_PREF = "EXMangaDomain"
-
- private const val LANGUAGE_PREF = "ReMangaTitleLanguage"
-
- private const val PAID_PREF = "PaidChapter"
-
- private const val exPAID_PREF = "ExChapter"
-
- private const val isLib_PREF = "LibBookmarks"
- }
-}
diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/RemangaActivity.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/RemangaActivity.kt
deleted file mode 100644
index 05775348e..000000000
--- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/RemangaActivity.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package eu.kanade.tachiyomi.extension.ru.remanga
-
-import android.app.Activity
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.os.Bundle
-import android.util.Log
-import kotlin.system.exitProcess
-/**
- * Springboard that accepts https://remanga.org/manga/xxx intents and redirects them to
- * the main tachiyomi process. The idea is to not install the intent filter unless
- * you have this extension installed, but still let the main tachiyomi app control
- * things.
- */
-class RemangaActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val pathSegments = intent?.data?.pathSegments
- if (pathSegments != null && pathSegments.size > 1) {
- val titleid = pathSegments[1]
- val mainIntent = Intent().apply {
- action = "eu.kanade.tachiyomi.SEARCH"
- putExtra("query", "${Remanga.PREFIX_SLUG_SEARCH}$titleid")
- putExtra("filter", packageName)
- }
-
- try {
- startActivity(mainIntent)
- } catch (e: ActivityNotFoundException) {
- Log.e("RemangaActivity", e.toString())
- }
- } else {
- Log.e("RemangaActivity", "could not parse uri from intent $intent")
- }
-
- finish()
- exitProcess(0)
- }
-}
diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt
deleted file mode 100644
index 82cf60185..000000000
--- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-package eu.kanade.tachiyomi.extension.ru.remanga.dto
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class TagsDto(
- val id: Int,
- val name: String,
-)
-
-@Serializable
-data class BranchesDto(
- val id: Long,
- val count_chapters: Int,
-)
-
-@Serializable
-data class ImgDto(
- val high: String? = null,
- val mid: String? = null,
- val low: String? = null,
-)
-
-@Serializable
-data class LibraryDto(
- val id: Long,
- val en_name: String,
- val rus_name: String,
- val dir: String,
- val img: ImgDto,
- val bookmark_type: String? = null,
-)
-
-@Serializable
-data class MyLibraryDto(
- val title: LibraryDto,
-)
-
-@Serializable
-data class StatusDto(
- val id: Int,
- val name: String,
-)
-
-@Serializable
-data class MangaDetDto(
- val id: Long,
- val en_name: String,
- val rus_name: String,
- val another_name: String,
- val dir: String,
- val description: String?,
- val issue_year: Int?,
- val img: ImgDto,
- val type: TagsDto,
- val genres: List,
- val categories: List,
- val branches: List,
- val status: StatusDto,
- val avg_rating: String,
- val count_rating: Int,
- val age_limit: Int,
-)
-
-@Serializable
-data class PropsDto(
- val total_pages: Int? = 0,
- val page: Int,
-)
-
-@Serializable
-data class PageWrapperDto(
- val content: List,
- val props: PropsDto,
-)
-
-@Serializable
-data class SeriesWrapperDto(
- val content: T,
-)
-
-@Serializable
-data class PublisherDto(
- val name: String,
-)
-
-@Serializable
-data class BookDto(
- val id: Long,
- val tome: Int,
- val chapter: String,
- val name: String,
- val upload_date: String,
- val is_paid: Boolean,
- val is_bought: Boolean?,
- val publishers: List,
-)
-
-@Serializable
-data class ExWrapperDto(
- val data: T,
-)
-
-@Serializable
-data class ExBookDto(
- val id: Long,
- val tome: Int,
- val chapter: String,
-)
-
-@Serializable
-data class ExLibraryDto(
- val id: Long,
- val dir: String,
- val name: String = "Без названия",
- val img: String?,
-)
-
-@Serializable
-data class PagesDto(
- val link: String,
- val height: Int,
-)
-
-@Serializable
-data class PageDto(
- val pages: List,
-)
-
-@Serializable
-data class ChunksPageDto(
- val pages: List>,
-)
-
-@Serializable
-data class UserDto(
- val id: Long,
-)
diff --git a/src/zh/iqiyi/build.gradle b/src/zh/iqiyi/build.gradle
index 660aed9e5..76cf30951 100644
--- a/src/zh/iqiyi/build.gradle
+++ b/src/zh/iqiyi/build.gradle
@@ -1,7 +1,7 @@
ext {
extName = 'Iqiyi'
extClass = '.Iqiyi'
- extVersionCode = 1
+ extVersionCode = 2
}
apply from: "$rootDir/common.gradle"
diff --git a/src/zh/iqiyi/src/eu/kanade/tachiyomi/extension/zh/iqiyi/Iqiyi.kt b/src/zh/iqiyi/src/eu/kanade/tachiyomi/extension/zh/iqiyi/Iqiyi.kt
index b0408c613..61c91f701 100644
--- a/src/zh/iqiyi/src/eu/kanade/tachiyomi/extension/zh/iqiyi/Iqiyi.kt
+++ b/src/zh/iqiyi/src/eu/kanade/tachiyomi/extension/zh/iqiyi/Iqiyi.kt
@@ -27,7 +27,7 @@ class Iqiyi : ParsedHttpSource() {
// Popular
- override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/全部_0_9_$page/", headers)
+ override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/全部_-1_-1_9_$page/", headers)
override fun popularMangaNextPageSelector(): String = "div.mod-page > a.a1:contains(下一页)"
override fun popularMangaSelector(): String = "ul.cartoon-hot-ul > li.cartoon-hot-list"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
@@ -38,7 +38,7 @@ class Iqiyi : ParsedHttpSource() {
// Latest
- override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/全部_0_4_$page/", headers)
+ override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/全部_-1_-1_4_$page/", headers)
override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)