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)