Maybe fix? please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please

This commit is contained in:
Draff 2025-02-23 21:52:08 +00:00
parent 2cfdda0bcf
commit 1f3fff9efa
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
62 changed files with 462 additions and 1700 deletions

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

39
.github/scripts/create-repo.py vendored Executable file → Normal file
View File

@ -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('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
for entry in index_data:
apk_escaped = 'apk/' + html.escape(entry["apk"])
name_escaped = html.escape(entry["name"])
f.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
f.write('</pre>\n</body>\n</html>\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=(",", ":"))

View File

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

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

@ -0,0 +1,50 @@
import html
import sys
import json
from pathlib import Path
import shutil
REMOTE_REPO: Path = Path.cwd()
LOCAL_REPO: Path = REMOTE_REPO.parent.joinpath("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('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
for entry in index:
apk_escaped = 'apk/' + html.escape(entry["apk"])
name_escaped = html.escape(entry["name"])
index_html_file.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
index_html_file.write('</pre>\n</body>\n</html>\n')

10
.github/scripts/move-apks.py → .github/scripts/move-built-apks.py vendored Executable file → Normal file
View File

@ -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)
shutil.move(apk, REPO_APK_DIR.joinpath(apk_name))

View File

@ -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(" ")')

View File

@ -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

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

@ -0,0 +1,22 @@
# Sync repo to the Codeberg mirror
name: Mirror Sync
on:
push:
branches: [ "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 }}

71
.github/workflows/issue_moderator.yml vendored Normal file
View File

@ -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: <extension name>`.\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|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cl[oa]ud ?fl?[ai]re.*",
"ignoreCase": true,
"labels": ["Cloudflare protected"],
"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."
},
{
"type": "both",
"regex": "remanga\\.org",
"ignoreCase": true,
"labels": ["invalid"],
"message": "ReManga (Russian) will not be added back as it has been removed [due to legal reasons](https://github.com/github/dmca)."
},
{
"type": "both",
"regex": "(kumanga\\.com)",
"ignoreCase": true,
"labels": ["invalid"],
"message": "{match} will not be added back as it is too difficult to maintain."
},
{
"type": "both",
"regex": "(centralnovel\\.com)",
"ignoreCase": true,
"labels": ["invalid"],
"message": "Novels aren't supported"
}
]
auto-close-ignore-label: do-not-autoclose

20
.github/workflows/lock.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Lock threads
on:
# Daily
schedule:
- cron: '0 0 * * *'
# Manual trigger
workflow_dispatch:
inputs:
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
with:
github-token: ${{ github.token }}
issue-inactive-days: '2'
pr-inactive-days: '2'

View File

@ -1,13 +1,24 @@
### Please give the repo a :star:
| Build | Support Server |
|-------|---------|
| Build | Support Server |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [![CI](https://github.com/keiyoushi/extensions-source/actions/workflows/build_push.yml/badge.svg)](https://github.com/keiyoushi/extensions-source/actions/workflows/build_push.yml) | [![Discord](https://img.shields.io/discord/1193460528052453448.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/3FbCpdKbdY) |
# Usage
[Getting started](https://keiyoushi.github.io/docs/guides/getting-started#adding-the-extension-repo)
# Requests
To request a new source or bug fix, [create an issue](https://github.com/keiyoushi/extensions-source/issues/new/choose).
Please note that creating an issue does not mean that the source will be added or fixed in a timely
fashion, because the work is volunteer-based. Some sources may also be impossible to do or prohibitively
difficult to maintain.
If you would like to see a request fulfilled and have the necessary skills to do so, consider contributing!
Issues are up-for-grabs for any developer if there is no assigned user already.
# Contributing
Contributions are welcome!
@ -32,5 +43,8 @@ Check out the repo's [issue backlog](https://github.com/keiyoushi/extensions-sou
## Disclaimer
This project is not affiliated with Tachiyomi. Don't ask for help about these extensions at the official support means of Tachiyomi. All credits to the codebase goes to the original contributors.
This project does not have any affiliation with the content providers available.
This project is not affiliated with Mihon/Tachiyomi. Don't ask for help about these extensions at the
official support means of Mihon/Tachiyomi. All credits to the codebase goes to the original contributors.

View File

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

View File

@ -21,3 +21,9 @@ android {
dependencies {
compileOnly(versionCatalogs.named("libs").findBundle("common").get())
}
tasks.register("printDependentExtensions") {
doLast {
project.printDependentExtensions()
}
}

View File

@ -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<File>? {
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)
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,7 +0,0 @@
ext {
extName = 'RE Manga'
extClass = '.REManga'
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -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<Page> {
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<Pair<String?, String>>) : UriPartFilter("Sort by", vals)
private class TypeFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Type", vals)
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
class Genre(name: String, val id: String = name) : Filter.TriState(name)
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres)
class Year(name: String, val id: String = name) : Filter.TriState(name)
private class YearFilter(years: List<Year>) : Filter.Group<Year>("Year", years)
private fun getSortFilters(): Array<Pair<String?, String>> = arrayOf(
Pair("title", "A-Z"),
Pair("titlereverse", "Z-A"),
Pair("update", "Latest Update"),
Pair("latest", "Latest Added"),
Pair("popular", "Popular"),
)
private fun getStatusFilters(): Array<Pair<String?, String>> = arrayOf(
Pair("", "All"),
Pair("Publishing", "مستمر"),
Pair("Finished", "تاريخ انتهي"),
)
private fun getTypeFilter(): Array<Pair<String?, String>> = arrayOf(
Pair("", "All"),
Pair("Manga", "Manga"),
Pair("Manhwa", "Manhwa"),
Pair("Manhua", "Manhua"),
)
private fun getGenreFilters(): List<Genre> = 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<Year> = 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<Pair<String?, String>>) :
Filter.Select<String>(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"))
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

View File

@ -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
}

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".ru.remanga.RemangaActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- RemangaActivity sites can be added here. -->
<data
android:host="remanga.org"
android:pathPattern="/manga/..*"
android:scheme="https" />
<data
android:host="xn--80aaig9ahr.xn--c1avg"
android:pathPattern="/manga/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,11 +0,0 @@
ext {
extName = 'Remanga'
extClass = '.Remanga'
extVersionCode = 86
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation project(':lib:dataimage')
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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)
}
}

View File

@ -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<TagsDto>,
val categories: List<TagsDto>,
val branches: List<BranchesDto>,
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<T>(
val content: List<T>,
val props: PropsDto,
)
@Serializable
data class SeriesWrapperDto<T>(
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<PublisherDto>,
)
@Serializable
data class ExWrapperDto<T>(
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<PagesDto>,
)
@Serializable
data class ChunksPageDto(
val pages: List<List<PagesDto>>,
)
@Serializable
data class UserDto(
val id: Long,
)

View File

@ -1,7 +1,7 @@
ext {
extName = 'Iqiyi'
extClass = '.Iqiyi'
extVersionCode = 1
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

View File

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