From 0f7df7a71c5c0a27ad94d518c40de438159f5072 Mon Sep 17 00:00:00 2001 From: jmir1 Date: Sat, 6 Aug 2022 20:17:53 +0200 Subject: [PATCH] merge upstream --- .editorconfig | 1 - .github/ISSUE_TEMPLATE.md | 6 +- .github/ISSUE_TEMPLATE/config.yml | 20 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .github/ISSUE_TEMPLATE/meta_request.md | 4 +- .github/ISSUE_TEMPLATE/report_issue.yml | 98 ++++++ .github/ISSUE_TEMPLATE/request_feature.yml | 50 +++ .github/ISSUE_TEMPLATE/request_meta.yml | 41 +++ .github/ISSUE_TEMPLATE/request_removal.yml | 33 ++ .github/ISSUE_TEMPLATE/request_source.yml | 55 +++ .github/ISSUE_TEMPLATE/source_bug.md | 6 +- .github/ISSUE_TEMPLATE/source_request.md | 4 +- .github/pull_request_template.md | 19 +- .github/runner-files/ci-gradle.properties | 7 - .github/scripts/commit-repo.sh | 3 + .github/scripts/create-repo.sh | 16 +- .github/scripts/move-apks.sh | 26 ++ .github/workflows/batch_close_issues.yml | 26 ++ .github/workflows/build_pull_request.yml | 146 ++++++-- .github/workflows/build_push.yml | 203 ++++++++--- .github/workflows/cancel_pull_request.yml | 3 +- .github/workflows/issue_closer.yml | 14 +- .github/workflows/issue_moderator.yml | 25 +- .github/workflows/lock.yml | 2 +- CODE_OF_CONDUCT.md | 148 +++++--- CONTRIBUTING.md | 332 +++++++++++++++--- README.md | 2 +- annotations/src/main/kotlin/Nsfw.kt | 10 - build.gradle | 27 -- build.gradle.kts | 25 ++ buildSrc/src/main/kotlin/AndroidConfig.kt | 5 +- buildSrc/src/main/kotlin/Deps.kt | 9 - common-dependencies.gradle | 18 - common.gradle | 30 +- core/AndroidManifest.xml | 2 + core/build.gradle.kts | 5 +- gradle.properties | 3 +- gradle/libs.versions.toml | 32 ++ gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 ++++++++------ lib/dataimage/build.gradle.kts | 13 +- lib/duktape-stub/build.gradle.kts | 16 - .../src/com/squareup/duktape/Duktape.java | 26 -- lib/ratelimit/build.gradle.kts | 23 -- lib/ratelimit/src/main/AndroidManifest.xml | 3 - .../lib/ratelimit/RateLimitInterceptor.kt | 58 --- .../SpecificHostRateLimitInterceptor.kt | 65 ---- settings.gradle.kts | 67 ++-- src/de/fireanime/build.gradle | 3 - .../animeextension/de/fireanime/FireAnime.kt | 4 +- src/pt/animefire/build.gradle | 4 - src/pt/animesvision/build.gradle | 1 - src/pt/animeyabu/build.gradle | 4 +- src/pt/betteranime/build.gradle | 1 - src/pt/hentaiyabu/build.gradle | 4 +- src/pt/puraymoe/build.gradle | 4 +- template/README-REMOVED-TEMPLATE.md | 11 + template/README-TEMPLATE.md | 12 + 59 files changed, 1376 insertions(+), 662 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/report_issue.yml create mode 100644 .github/ISSUE_TEMPLATE/request_feature.yml create mode 100644 .github/ISSUE_TEMPLATE/request_meta.yml create mode 100644 .github/ISSUE_TEMPLATE/request_removal.yml create mode 100644 .github/ISSUE_TEMPLATE/request_source.yml delete mode 100644 .github/runner-files/ci-gradle.properties create mode 100644 .github/scripts/move-apks.sh create mode 100644 .github/workflows/batch_close_issues.yml delete mode 100644 annotations/src/main/kotlin/Nsfw.kt delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/Deps.kt delete mode 100644 common-dependencies.gradle create mode 100644 gradle/libs.versions.toml delete mode 100644 lib/duktape-stub/build.gradle.kts delete mode 100644 lib/duktape-stub/src/com/squareup/duktape/Duktape.java delete mode 100644 lib/ratelimit/build.gradle.kts delete mode 100644 lib/ratelimit/src/main/AndroidManifest.xml delete mode 100644 lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt delete mode 100644 lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/SpecificHostRateLimitInterceptor.kt create mode 100644 template/README-REMOVED-TEMPLATE.md create mode 100644 template/README-TEMPLATE.md diff --git a/.editorconfig b/.editorconfig index 444772e95e..1296f8944f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,4 +8,3 @@ indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true - diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c8dd4c4b13..b531d3fdae 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,18 +2,18 @@ I acknowledge that: -- I have updated to the latest version of the app (stable is v0.10.12) +- I have updated to the latest version of the app (stable is v0.12.3.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/jmir1/aniyomi - 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/ +- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://aniyomi.jmir.xyz/extensions/ **DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT** --- ## Device information -* Tachiyomi version: ? +* Aniyomi version: ? * Android version: ? * Device: ? diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 487409da49..799d5364a1 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: false contact_links: - - name: Extensions list - url: https://tachiyomi.org/extensions - about: List of all available extensions with download links. - - name: Multi-source extensions spreadsheet - url: https://tachiyomi.org/extensions-spreadsheet - about: The sources inside multi-source extensions can be found in this spreadsheet. - - name: Tachiyomi help website - url: https://tachiyomi.org/help/ + - name: ⚠️ Application issue + url: https://github.com/jmir1/aniyomi/issues/new/choose + about: Issues and requests about the app itself should be opened in the tachiyomi repository instead + - name: 📦 Aniyomi extensions + url: https://aniyomi.jmir.xyz/extensions + about: List of all available extensions with download links + - name: 🖥️ Aniyomi website + url: https://aniyomi.jmir.xyz/help/ about: Common questions are answered here. - - name: Tachiyomi app GitHub repository + - name: Aniyomi app GitHub repository url: https://github.com/jmir1/aniyomi - about: Issues about the app itself should be opened here instead. \ No newline at end of file + about: Issues about the app itself should be opened here instead. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2cbc1ba272..1b84af7ec9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -12,10 +12,10 @@ I acknowledge that: - I have updated: - To the latest version of the app (stable is v0.10.12) - All extensions -- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ +- I have tried the troubleshooting guide: https://aniyomi.jmir.xyz/help/guides/troubleshooting-problems/ - If this is an issue with the app itself, that I should be opening an issue in https://github.com/jmir1/aniyomi - I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue -- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://tachiyomi.org/extensions/ +- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://aniyomi.jmir.xyz/extensions/ - I will fill out the title and the information in this template Note that the issue will be automatically closed if you do not fill out the title or requested information. diff --git a/.github/ISSUE_TEMPLATE/meta_request.md b/.github/ISSUE_TEMPLATE/meta_request.md index ef7b099aa8..42e3499e6c 100644 --- a/.github/ISSUE_TEMPLATE/meta_request.md +++ b/.github/ISSUE_TEMPLATE/meta_request.md @@ -12,10 +12,10 @@ I acknowledge that: - I have updated: - To the latest version of the app (stable is v0.10.12) - All extensions -- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ +- I have tried the troubleshooting guide: https://aniyomi.jmir.xyz/help/guides/troubleshooting-problems/ - If this is an issue with the app itself, that I should be opening an issue in https://github.com/jmir1/aniyomi - I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue -- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://tachiyomi.org/extensions/ +- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://aniyomi.jmir.xyz/extensions/ - I will fill out the title and the information in this template Note that the issue will be automatically closed if you do not fill out the title or requested information. diff --git a/.github/ISSUE_TEMPLATE/report_issue.yml b/.github/ISSUE_TEMPLATE/report_issue.yml new file mode 100644 index 0000000000..33fa8674ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report_issue.yml @@ -0,0 +1,98 @@ +name: 🐞 Issue report +description: Report a source issue in Aniyomi +labels: [Bug] +body: + + - type: input + id: source + attributes: + label: Source information and language + description: | + You can find the extension name and version in **Browse → Extensions**. + placeholder: | + Example: "Mangahere 1.3.18 (English)" + validations: + required: true + + - type: textarea + id: reproduce-steps + attributes: + label: Steps to reproduce + description: Provide an example of the issue. + placeholder: | + Example: + 1. First step + 2. Second step + 3. Issue here + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + placeholder: | + Example: + "This should happen..." + validations: + required: true + + - type: textarea + id: actual-behavior + attributes: + label: Actual behavior + placeholder: | + Example: + "This happened instead..." + validations: + required: true + + - type: input + id: tachiyomi-version + attributes: + label: Aniyomi version + description: | + You can find your Aniyomi version in **More → About**. + placeholder: | + Example: "0.13.5" + validations: + required: true + + - type: input + id: android-version + attributes: + label: Android version + description: | + You can find this somewhere in your Android settings. + placeholder: | + Example: "Android 11" + validations: + required: true + + - type: textarea + id: other-details + attributes: + label: Other details + placeholder: | + Additional details and attachments. + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your issue will be closed if you haven't done these steps. + options: + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. + required: true + - label: I have written a short but informative title. + required: true + - label: I have updated the app to version **[0.13.5](https://github.com/jmir1/aniyomi/releases/latest)**. + required: true + - label: I have updated all installed extensions. + required: true + - label: I have tried the [troubleshooting guide](https://aniyomi.jmir.xyz/help/guides/troubleshooting/). + 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/jmir1/aniyomi/issues/new/choose). + required: true + - label: I will fill out all of the requested information in this form. + required: true diff --git a/.github/ISSUE_TEMPLATE/request_feature.yml b/.github/ISSUE_TEMPLATE/request_feature.yml new file mode 100644 index 0000000000..ba61006e87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request_feature.yml @@ -0,0 +1,50 @@ +name: ⭐ Feature request +description: Suggest a feature to improve a source +labels: [Feature request] +body: + + - type: input + id: source + attributes: + label: Source name and language + description: | + You can find the extension name in **Browse → Extensions**. + placeholder: | + Example: "Mangahere (English)" + validations: + required: true + + - type: textarea + id: feature-description + attributes: + label: Describe your suggested feature + description: How can an existing extension be improved? + placeholder: | + Example: + "It should work like this..." + validations: + required: true + + - type: textarea + id: other-details + attributes: + label: Other details + placeholder: | + Additional details and attachments. + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your issue will be closed if you haven't done these steps. + options: + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. + 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/jmir1/aniyomi/issues/new/choose). + required: true + - label: I have updated the app to version **[0.13.5](https://github.com/jmir1/aniyomi/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/request_meta.yml b/.github/ISSUE_TEMPLATE/request_meta.yml new file mode 100644 index 0000000000..f02aca47e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request_meta.yml @@ -0,0 +1,41 @@ +name: 🧠 Meta feature request +description: Suggest improvements to the project +labels: [Meta request] +body: + + - type: textarea + id: feature-description + attributes: + label: Describe why this should be added + description: How can the project be improved? + placeholder: | + Example: + "It should work like this..." + validations: + required: true + + - type: textarea + id: other-details + attributes: + label: Other details + placeholder: | + Additional details and attachments. + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your issue will be closed if you haven't done these steps. + options: + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. + 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/jmir1/aniyomi/issues/new/choose). + required: true + - label: I have updated the app to version **[0.13.5](https://github.com/jmir1/aniyomi/releases/latest)**. + required: true + - label: I have updated all installed extensions. + required: true + - label: I will fill out all of the requested information in this form. + required: true diff --git a/.github/ISSUE_TEMPLATE/request_removal.yml b/.github/ISSUE_TEMPLATE/request_removal.yml new file mode 100644 index 0000000000..7b3617bfd1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request_removal.yml @@ -0,0 +1,33 @@ +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: Proof of ownership/intent to remove sent to a Aniyomi Discord server mod via DM + 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/request_source.yml b/.github/ISSUE_TEMPLATE/request_source.yml new file mode 100644 index 0000000000..397702434c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request_source.yml @@ -0,0 +1,55 @@ +name: 🌐 Source request +description: Suggest a new source for Aniyomi +labels: [Source request] +body: + + - type: input + id: name + attributes: + label: Source name + placeholder: | + Example: "Not Real Scans" + validations: + required: true + + - type: input + id: link + attributes: + label: Source link + placeholder: | + Example: "https://notrealscans.org" + validations: + required: true + + - type: input + id: language + attributes: + label: Language + placeholder: | + Example: "English" + validations: + required: true + + - type: textarea + id: other-details + attributes: + label: Other details + placeholder: | + Additional details and attachments. + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your issue will be closed if you haven't done these steps. + options: + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. + required: true + - label: I have written a title with source name. + required: true + - label: I have checked that the extension does not already exist on the [website extensions list](https://aniyomi.jmir.xyz/extensions/) or the app. + required: true + - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/jmir1/aniyomi-extensions/) and verified it does not appear in the code base. + required: true + - label: I will fill out all of the requested information in this form. + required: true diff --git a/.github/ISSUE_TEMPLATE/source_bug.md b/.github/ISSUE_TEMPLATE/source_bug.md index 661c9c108e..3f791355b3 100644 --- a/.github/ISSUE_TEMPLATE/source_bug.md +++ b/.github/ISSUE_TEMPLATE/source_bug.md @@ -12,10 +12,10 @@ I acknowledge that: - I have updated: - To the latest version of the app (stable is v0.10.12) - All extensions -- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ +- I have tried the troubleshooting guide: https://aniyomi.jmir.xyz/help/guides/troubleshooting-problems/ - If this is an issue with the app itself, that I should be opening an issue in https://github.com/jmir1/aniyomi - I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue -- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://tachiyomi.org/extensions/ +- For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://aniyomi.jmir.xyz/extensions/ - I will fill out the title and the information in this template Note that the issue will be automatically closed if you do not fill out the title or requested information. @@ -25,7 +25,7 @@ Note that the issue will be automatically closed if you do not fill out the titl --- ## Device information -- Tachiyomi version: ? +- Aniyomi version: ? - Android version: ? ## Source information diff --git a/.github/ISSUE_TEMPLATE/source_request.md b/.github/ISSUE_TEMPLATE/source_request.md index e22178ba4d..0d817219fe 100644 --- a/.github/ISSUE_TEMPLATE/source_request.md +++ b/.github/ISSUE_TEMPLATE/source_request.md @@ -1,7 +1,7 @@ --- name: "🌐 Source Request" title: "[Source Request] " -about: "Suggest a new source for Tachiyomi" +about: "Suggest a new source for Aniyomi" labels: "Source Request" --- @@ -11,7 +11,7 @@ I acknowledge that: - I have checked that the extension does not already exist: - In the list of existing extensions in the application - - In the multi-source spreadsheet: https://tachiyomi.org/extensions + - In the multi-source spreadsheet: https://aniyomi.jmir.xyz/extensions - By searching the GitHub repository for the extension and verify it does not appear in the code base - I have searched the existing GitHub issues and this extension does **NOT** have an open request - I will fill out the title and the information in this template diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7f4da227ac..5e2efbb8fd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,9 @@ - +- [ ] Updated `extVersionCode` value in `build.gradle` for individual extensions +- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions +- [ ] Referenced all related issues in the PR body (e.g. "Closes #xyz") +- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate +- [ ] Have not changed source names +- [ ] Have explicitly kept the `id` if a source's name or language were changed +- [ ] Have tested the modifications by compiling and running the extension through Android Studio diff --git a/.github/runner-files/ci-gradle.properties b/.github/runner-files/ci-gradle.properties deleted file mode 100644 index 753351b1ba..0000000000 --- a/.github/runner-files/ci-gradle.properties +++ /dev/null @@ -1,7 +0,0 @@ -org.gradle.daemon=false -org.gradle.jvmargs=-Xmx5120m -org.gradle.workers.max=5 -org.gradle.parallel=true - -kotlin.incremental=false -kotlin.compiler.execution.strategy=in-process diff --git a/.github/scripts/commit-repo.sh b/.github/scripts/commit-repo.sh index ab6163b831..2cddbe25f9 100755 --- a/.github/scripts/commit-repo.sh +++ b/.github/scripts/commit-repo.sh @@ -9,6 +9,9 @@ if [ -n "$(git status --porcelain)" ]; then git add . git commit -m "Update extensions repo" git push + + # Purge cached index on jsDelivr + curl https://purge.jsdelivr.net/gh/jmir1/aniyomi-extensions@repo/index.min.json else echo "No changes to commit" fi diff --git a/.github/scripts/create-repo.sh b/.github/scripts/create-repo.sh index d3ea0259e4..677a7ae5cb 100755 --- a/.github/scripts/create-repo.sh +++ b/.github/scripts/create-repo.sh @@ -30,6 +30,19 @@ for APK in ${APKS[@]}; do ICON=$(echo "$BADGING" | grep -Po "application-icon-320.*'\K[^']+") unzip -p $APK $ICON > icon/${FILENAME%.*}.png + SOURCE_INFO=$(jq ".[\"$PKGNAME\"]" < ../output.json) + + # Fixes the language code without needing to update the packages. + SOURCE_LEN=$(echo $SOURCE_INFO | jq length) + + if [ $SOURCE_LEN = "1" ]; then + SOURCE_LANG=$(echo $SOURCE_INFO | jq -r '.[0].lang') + + if [ $SOURCE_LANG != $LANG ] && [ $SOURCE_LANG != "all" ] && [ $SOURCE_LANG != "other" ] && [ $LANG != "all" ] && [ $LANG != "other" ]; then + LANG=$SOURCE_LANG + fi + fi + jq -n \ --arg name "$LABEL" \ --arg pkg "$PKGNAME" \ @@ -45,7 +58,4 @@ done | jq -sr '[.[]]' > index.json # Alternate minified copy jq -c '.' < index.json > index.min.json -# Alternate gzipped copy -gzip -c index.json > index.json.gz - cat index.json diff --git a/.github/scripts/move-apks.sh b/.github/scripts/move-apks.sh new file mode 100644 index 0000000000..ba0d3cf3e8 --- /dev/null +++ b/.github/scripts/move-apks.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e +shopt -s globstar nullglob extglob + +# Get APKs from previous jobs' artifacts +cp -R ~/apk-artifacts/ $PWD +APKS=( **/*".apk" ) + +# Fail if too little extensions seem to have been built +if [ "${#APKS[@]}" -le "100" ]; then + echo "Insufficient amount of APKs found. Please check the project configuration." + exit 1 +else + echo "Moving ${#APKS[@]} APKs" +fi + +DEST=$PWD/apk +rm -rf $DEST && mkdir -p $DEST + +for APK in ${APKS[@]}; do + BASENAME=$(basename $APK) + APKNAME="${BASENAME%%+(-release*)}.apk" + APKDEST="$DEST/$APKNAME" + + cp $APK $APKDEST +done diff --git a/.github/workflows/batch_close_issues.yml b/.github/workflows/batch_close_issues.yml new file mode 100644 index 0000000000..661661da1b --- /dev/null +++ b/.github/workflows/batch_close_issues.yml @@ -0,0 +1,26 @@ +name: "Batch close stale issues" + +on: + # Monthly + schedule: + - cron: '0 0 1 * *' + # Manual trigger + workflow_dispatch: + inputs: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v5.1.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # Close everything older than ~6 months + days-before-issue-stale: 180 + days-before-issue-close: 0 + any-of-issue-labels: "Source request" + exempt-issue-labels: do-not-autoclose + close-issue-message: "In an effort to have a more manageable issue backlog, we're closing older requests that weren't addressed. If you think the source may still benefit others, please open a new request." + close-issue-reason: not_planned + ascending: true + operations-per-run: 250 diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 302a1fcba8..5640686c38 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -2,47 +2,147 @@ name: PR build check on: pull_request: + paths-ignore: + - '**.md' + +env: + CI_CHUNK_SIZE: 65 jobs: - check_wrapper: - name: Validate Gradle Wrapper + prepare: + name: Prepare job runs-on: ubuntu-latest + outputs: + individualMatrix: ${{ steps.generate-matrices.outputs.individualMatrix }} + #multisrcMatrix: ${{ steps.generate-matrices.outputs.multisrcMatrix }} + isIndividualChanged: ${{ steps.parse-changed-files.outputs.isIndividualChanged }} + #isMultisrcChanged: ${{ steps.parse-changed-files.outputs.isMultisrcChanged }} + env: + CI_MODULE_GEN: true steps: - name: Clone repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: adopt + + - id: get-changed-files + name: Get changed files + uses: Ana06/get-changed-files@v2.1.0 + + - id: parse-changed-files + name: Parse changed files + run: | + isIndividualChanged=0 + #isMultisrcChanged=0 + for changedFile in ${{ steps.get-changed-files.outputs.all }}; do + if [[ ${changedFile} == src/* ]]; then + isIndividualChanged=1 + # elif [[ ${changedFile} == multisrc/* ]]; then + # isMultisrcChanged=1 + else + isIndividualChanged=1 + # isMultisrcChanged=1 + break + fi + done + echo "::set-output name=isIndividualChanged::$isIndividualChanged" + #echo "::set-output name=isMultisrcChanged::$isMultisrcChanged" + +# - name: Generate multisrc sources +# if: ${{ steps.parse-changed-files.outputs.isMultisrcChanged == '1' }} +# uses: gradle/gradle-command-action@v2 +# with: +# arguments: :multisrc:generateExtensions + + - name: Get number of modules + run: | + set -x + ./gradlew -q projects | grep '.*extensions\:individual\:.*\:.*' > projects.txt + + echo "NUM_INDIVIDUAL_MODULES=$(cat projects.txt | grep '.*\:individual\:.*' | wc -l)" >> $GITHUB_ENV + #echo "NUM_MULTISRC_MODULES=$(cat projects.txt | grep '.*\:multisrc\:.*' | wc -l)" >> $GITHUB_ENV + + - id: generate-matrices + name: Create output matrices + uses: actions/github-script@v6 + with: + script: | + const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES; + //const numMultisrcModules = process.env.NUM_MULTISRC_MODULES; + const chunkSize = process.env.CI_CHUNK_SIZE; + + const numIndividualChunks = Math.ceil(numIndividualModules / chunkSize); + //const numMultisrcChunks = Math.ceil(numMultisrcModules / chunkSize); + + console.log(`Individual modules: ${numIndividualModules} (${numIndividualChunks} chunks of ${chunkSize})`); + //console.log(`Multi-source modules: ${numMultisrcModules} (${numMultisrcChunks} chunks of ${chunkSize})`); + + core.setOutput('individualMatrix', { 'chunk': [...Array(numIndividualChunks).keys()] }); + //core.setOutput('multisrcMatrix', { 'chunk': [...Array(numMultisrcChunks).keys()] }); + + #build_multisrc: + # name: Build multisrc modules + # needs: prepare + # if: ${{ needs.prepare.outputs.isMultisrcChanged == '1' }} + # runs-on: ubuntu-latest + # strategy: + # matrix: ${{ fromJSON(needs.prepare.outputs.multisrcMatrix) }} + # steps: + # - name: Checkout PR + # uses: actions/checkout@v3 + + # - name: Set up JDK + # uses: actions/setup-java@v3 + # with: + # java-version: 11 + # distribution: adopt + + # - name: Generate sources from the multi-source library + # uses: gradle/gradle-command-action@v2 + # env: + # CI_MODULE_GEN: "true" + # with: + # arguments: :multisrc:generateExtensions + # cache-read-only: true + + # - name: Build extensions (chunk ${{ matrix.chunk }}) + # uses: gradle/gradle-command-action@v2 + # env: + # CI_MULTISRC: "true" + # CI_CHUNK_NUM: ${{ matrix.chunk }} + # with: + # arguments: assembleDebug + # cache-read-only: true + build_individual: name: Build individual modules - needs: check_wrapper - if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')" + needs: prepare + if: ${{ needs.prepare.outputs.isIndividualChanged == '1' }} runs-on: ubuntu-latest strategy: - matrix: - lang: [all, ar, ca, de, en, es, fr, id, it, ja, ko, pt, ru, th, tr, vi, zh] + matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }} steps: - name: Checkout PR - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK + uses: actions/setup-java@v3 with: - java-version: 1.8 - - - name: Copy CI gradle.properties - run: | - mkdir -p ~/.gradle - cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties + java-version: 11 + distribution: adopt - - name: Build "${{ matrix.lang }}" extensions - uses: eskatos/gradle-command-action@v1 + - name: Build extensions (chunk ${{ matrix.chunk }}) + uses: gradle/gradle-command-action@v2 env: CI_MULTISRC: "false" - CI_MATRIX_LANG: ${{ matrix.lang }} + CI_CHUNK_NUM: ${{ matrix.chunk }} with: - arguments: assembleRelease - wrapper-cache-enabled: true - dependencies-cache-enabled: true - configuration-cache-enabled: true + arguments: assembleDebug + cache-read-only: true diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index a07eae3d67..701160921e 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -4,83 +4,198 @@ on: push: branches: - master + paths-ignore: + - '**.md' + +env: + CI_CHUNK_SIZE: 65 jobs: - check_wrapper: - name: Validate Gradle Wrapper + prepare: + name: Prepare job runs-on: ubuntu-latest + outputs: + individualMatrix: ${{ steps.generate-matrices.outputs.individualMatrix }} + #multisrcMatrix: ${{ steps.generate-matrices.outputs.multisrcMatrix }} + env: + CI_MODULE_GEN: true steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + all_but_latest: true + - name: Clone repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - build: - name: Build extension repo - needs: check_wrapper - if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')" - runs-on: ubuntu-latest - steps: - - name: Cancel previous runs - uses: styfle/cancel-workflow-action@0.8.0 + - name: Set up JDK + uses: actions/setup-java@v3 with: - access_token: ${{ github.token }} + java-version: 11 + distribution: adopt - - name: Checkout master branch - uses: actions/checkout@v2 +# - name: Generate multisrc sources +# uses: gradle/gradle-command-action@v2 +# with: +# arguments: :multisrc:generateExtensions + + - name: Get number of modules + run: | + set -x + ./gradlew -q projects | grep '.*extensions\:individual\:.*\:.*' > projects.txt + + echo "NUM_INDIVIDUAL_MODULES=$(cat projects.txt | grep '.*\:individual\:.*' | wc -l)" >> $GITHUB_ENV + #echo "NUM_MULTISRC_MODULES=$(cat projects.txt | grep '.*\:multisrc\:.*' | wc -l)" >> $GITHUB_ENV + + - id: generate-matrices + name: Create output matrices + uses: actions/github-script@v6 with: - ref: master - path: master + script: | + const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES; + //const numMultisrcModules = process.env.NUM_MULTISRC_MODULES; + const chunkSize = process.env.CI_CHUNK_SIZE; + + const numIndividualChunks = Math.ceil(numIndividualModules / chunkSize); + //const numMultisrcChunks = Math.ceil(numMultisrcModules / chunkSize); + + console.log(`Individual modules: ${numIndividualModules} (${numIndividualChunks} chunks of ${chunkSize})`); + //console.log(`Multi-source modules: ${numMultisrcModules} (${numMultisrcChunks} chunks of ${chunkSize})`); + + core.setOutput('individualMatrix', { 'chunk': [...Array(numIndividualChunks).keys()] }); + //core.setOutput('multisrcMatrix', { 'chunk': [...Array(numMultisrcChunks).keys()] }); + + #build_multisrc: + # name: Build multisrc modules + # needs: prepare + # runs-on: ubuntu-latest + # strategy: + # matrix: ${{ fromJSON(needs.prepare.outputs.multisrcMatrix) }} + # steps: + # - name: Checkout master branch + # uses: actions/checkout@v3 + + # - name: Set up JDK + # uses: actions/setup-java@v3 + # with: + # java-version: 11 + # distribution: adopt + + # - name: Prepare signing key + # run: | + # echo ${{ secrets.SIGNING_KEY }} | base64 -d > signingkey.jks + + # - name: Generate sources from the multi-source library + # uses: gradle/gradle-command-action@v2 + # env: + # CI_MODULE_GEN: "true" + # with: + # arguments: :multisrc:generateExtensions - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + # - name: Build extensions (chunk ${{ matrix.chunk }}) + # uses: gradle/gradle-command-action@v2 + # env: + # CI_MULTISRC: "true" + # CI_CHUNK_NUM: ${{ matrix.chunk }} + # ALIAS: ${{ secrets.ALIAS }} + # KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} + # KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + # with: + # arguments: assembleRelease + + # - name: Upload APKs (chunk ${{ matrix.chunk }}) + # uses: actions/upload-artifact@v2 + # if: "github.repository == 'jmir1/aniyomi-extensions'" + # with: + # name: "multisrc-apks-${{ matrix.chunk }}" + # path: "**/*.apk" + # retention-days: 1 + + # - name: Clean up CI files + # run: rm signingkey.jks + + build_individual: + name: Build individual modules + needs: prepare + runs-on: ubuntu-latest + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }} + steps: + - name: Checkout master branch + uses: actions/checkout@v3 + + - name: Set up JDK + uses: actions/setup-java@v3 with: - java-version: 1.8 + java-version: 11 + distribution: adopt - - name: Copy CI gradle.properties + - name: Prepare signing key run: | - cd master - mkdir -p ~/.gradle - cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties + echo ${{ secrets.SIGNING_KEY }} | base64 -d > signingkey.jks - - name: Build extensions - uses: eskatos/gradle-command-action@v1 + - name: Build extensions (chunk ${{ matrix.chunk }}) + uses: gradle/gradle-command-action@v2 env: - CI_PUSH: "true" + CI_MULTISRC: "false" + CI_CHUNK_NUM: ${{ matrix.chunk }} + ALIAS: ${{ secrets.ALIAS }} + KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} with: - build-root-directory: master - wrapper-directory: master arguments: assembleRelease - wrapper-cache-enabled: true - dependencies-cache-enabled: true - configuration-cache-enabled: true - - name: Sign APKs - if: github.repository == 'jmir1/aniyomi-extensions' - run: | - cd master - ./.github/scripts/sign-apks.sh \ - ${{ secrets.SIGNING_KEY }} \ - ${{ secrets.ALIAS }} \ - ${{ secrets.KEY_STORE_PASSWORD }} \ - ${{ secrets.KEY_PASSWORD }} + - name: Upload APKs (chunk ${{ matrix.chunk }}) + uses: actions/upload-artifact@v2 + if: "github.repository == 'jmir1/aniyomi-extensions'" + with: + name: "individual-apks-${{ matrix.chunk }}" + path: "**/*.apk" + retention-days: 1 + + - name: Clean up CI files + run: rm signingkey.jks + + publish_repo: + name: Publish repo + needs: + - build_individual + if: "github.repository == 'jmir1/aniyomi-extensions'" + runs-on: ubuntu-latest + steps: + - name: Download APK artifacts + uses: actions/download-artifact@v2 + with: + path: ~/apk-artifacts + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: adopt + + - name: Checkout master branch + uses: actions/checkout@v3 + with: + ref: master + path: master - name: Create repo artifacts - if: github.repository == 'jmir1/aniyomi-extensions' run: | cd master ./.github/scripts/create-repo.sh - name: Checkout repo branch - if: github.repository == 'jmir1/aniyomi-extensions' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: repo path: repo - name: Deploy repo - if: github.repository == 'jmir1/aniyomi-extensions' run: | cd repo ../master/.github/scripts/commit-repo.sh diff --git a/.github/workflows/cancel_pull_request.yml b/.github/workflows/cancel_pull_request.yml index 4d6b65f1ee..0458f4210f 100644 --- a/.github/workflows/cancel_pull_request.yml +++ b/.github/workflows/cancel_pull_request.yml @@ -10,6 +10,7 @@ jobs: cancel: runs-on: ubuntu-latest steps: - - uses: styfle/cancel-workflow-action@0.8.0 + - uses: styfle/cancel-workflow-action@0.9.1 with: workflow_id: ${{ github.event.workflow.id }} + all_but_latest: true diff --git a/.github/workflows/issue_closer.yml b/.github/workflows/issue_closer.yml index f5a2308ec9..039548998c 100644 --- a/.github/workflows/issue_closer.yml +++ b/.github/workflows/issue_closer.yml @@ -27,19 +27,7 @@ jobs: }, { "type": "body", - "regex": ".*\\* (Tachiyomi version|Android version|Device|Name|Link|Extension version): \\?.*", + "regex": ".*\\* (Aniyomi version|Android version|Device|Name|Link|Extension version): \\?.*", "message": "The requested information was not filled out" - }, - { - "type": "both", - "regex": ".*(mangago|mangafox|hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|heroscan|manhwahot).*", - "ignoreCase": true, - "message": "{match} will not be added back as it is too difficult to maintain. Read #3475 for more information" - }, - { - "type": "both", - "regex": ".*(teamx|tqneplus|manga\\s*disk|komiktap|gourmet\\s*scans|manga\\s*crimson|mangawow|voidscans|hikari\\s*scans|mangagegecesi|piedpiperfb).*", - "ignoreCase": true, - "message": "{match} will not be added back as the Scanlator team has requested it to be removed. Read #3475 for more information" } ] diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml index 97440971fc..f24824a236 100644 --- a/.github/workflows/issue_moderator.yml +++ b/.github/workflows/issue_moderator.yml @@ -1,6 +1,8 @@ name: Issue moderator on: + issues: + types: [opened, edited, reopened] issue_comment: types: [created] @@ -9,6 +11,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Moderate issues - uses: tachiyomiorg/issue-moderator-action@v1.0 + uses: tachiyomiorg/issue-moderator-action@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + duplicate-check-enabled: true + duplicate-check-label: Source request + auto-close-rules: | + [ + { + "type": "body", + "regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*", + "message": "The acknowledgment section was not removed." + }, + { + "type": "body", + "regex": ".*\\* (Aniyomi version|Android version|Device): \\?.*", + "message": "Requested information in the template was not filled out." + }, + { + "type": "title", + "regex": ".*(Source name|Short description).*", + "message": "You did not fill out the description in the title" + } + ] + auto-close-ignore-label: do-not-autoclose diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 7097c1c87c..223783df6a 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -3,7 +3,7 @@ name: Lock threads on: # Daily schedule: - - cron: '0 * * * *' + - cron: '0 0 * * *' # Manual trigger workflow_dispatch: inputs: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b3aeaab9fd..4f70071eec 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,76 +1,126 @@ -# Code of Conduct +# Contributor Covenant Code of Conduct ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email + address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community moderators are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community moderators have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at the Tachiyomi [Discord server](https://discord.gg/tachiyomi). All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community moderators responsible for enforcement at +the [Aniyomi Discord server](https://discord.gg/F32UjdJZrR). +All complaints will be reviewed and investigated promptly and fairly. + +All community moderators are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community moderators will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community moderators, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), +version 2.1, available at +[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). -[homepage]: https://www.contributor-covenant.org +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +For answers to common questions about this code of conduct, see the FAQ at +[FAQ](https://www.contributor-covenant.org/faq). Translations are available +at [translations](https://www.contributor-covenant.org/translations). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5faccbd3d9..cb2b889a5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,37 @@ # Contributing +This guide have some instructions and tips on how to create a new Aniyomi extension. Please **read it carefully** if you're a new contributor or don't have any experience on the required languages and knowledges. + +This guide is not definitive and it's being updated over time. If you find any issue on it, feel free to report it through a [Meta Issue](https://github.com/jmir1/aniyomi-extensions/issues/new?assignees=&labels=Meta+request&template=request_meta.yml) or fixing it directly by submitting a Pull Request. + +## Table of Contents + +1. [Prerequisites](#prerequisites) + 1. [Tools](#tools) + 2. [Cloning the repository](#cloning-the-repository) +2. [Getting help](#getting-help) +3. [Writing an extension](#writing-an-extension) + 1. [Setting up a new Gradle module](#setting-up-a-new-gradle-module) + 2. [Core dependencies](#core-dependencies) + 3. [Extension main class](#extension-main-class) + 4. [Extension call flow](#extension-call-flow) + 5. [Misc notes](#misc-notes) + 6. [Advanced extension features](#advanced-extension-features) +4. [Multi-source themes](#multi-source-themes) + 1. [The directory structure](#the-directory-structure) + 2. [Development workflow](#development-workflow) + 3. [Scaffolding overrides](#scaffolding-overrides) + 4. [Additional Notes](#additional-notes) +5. [Running](#running) +6. [Debugging](#debugging) + 1. [Android Debugger](#android-debugger) + 2. [Logs](#logs) + 3. [Inspecting network calls](#inspecting-network-calls) + 4. [Using external network inspecting tools](#using-external-network-inspecting-tools) +7. [Building](#building) +8. [Submitting the changes](#submitting-the-changes) + 1. [Pull Request checklist](#pull-request-checklist) + ## Prerequisites Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you. @@ -15,7 +47,73 @@ Before you start, please note that the ability to use following technologies is ### Tools - [Android Studio](https://developer.android.com/studio) -- Emulator or phone with developer options enabled and a recent version of Tachiyomi installed +- Emulator or phone with developer options enabled and a recent version of Aniyomi installed +- [Icon Generator](https://as280093.github.io/AndroidAssetStudio/icons-launcher.html) + +### Cloning the repository + +Some alternative steps can be followed to ignore "repo" branch and skip unrelated sources, which will make it faster to pull, navigate and build. This will also reduce disk usage and network traffic. + +
Steps + +1. Make sure to delete "repo" branch in your fork. You may also want to disable Actions in the repo settings. +2. Do a partial clone. + ```bash + git clone --filter=blob:none --no-checkout + cd aniyomi-extensions/ + ``` +3. Configure sparse checkout. + ```bash + # enable sparse checkout + git sparse-checkout set + # edit sparse checkout filter + vim .git/info/sparse-checkout + # alternatively, if you have VS Code installed + code .git/info/sparse-checkout + ``` + Here's an example: + ```bash + /* + !/src/* + !/multisrc/overrides/* + !/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/* + # allow a single source + /src// + # allow a multisrc theme + /multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/ + /multisrc/overrides/ + # or type the source name directly + + ``` +4. Configure remotes. + ```bash + # add upstream + git remote add upstream + # optionally disable push to upstream + git remote set-url --push upstream no_pushing + # ignore 'repo' branch of upstream + # option 1: use negative refspec + git config --add remote.upstream.fetch "^refs/heads/repo" + # option 2: fetch master only (ignore all other branches) + git config remote.upstream.fetch "+refs/heads/master:refs/remotes/upstream/master" + # update remotes + git remote update + # track master of upstream instead of fork + git branch master -u upstream/master + # checkout + git switch master + ``` +5. Useful configurations. (optional) + ```bash + # prune obsolete remote branches on fetch + git config remote.origin.prune true + # fast-forward only when pulling master branch + git config pull.ff only + ``` +6. Later, if you change the sparse checkout filter, run `git sparse-checkout reapply`. + +Read more on [partial clone](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/), [sparse checkout](https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/) and [negative refspecs](https://github.blog/2020-10-19-git-2-29-released/#user-content-negative-refspecs). +
## Getting help @@ -30,6 +128,8 @@ The quickest way to get started is to copy an existing extension's folder struct Each extension should reside in `src//`. Use `all` as `` if your target source supports multiple languages or if it could support multiple sources. +The `` used in the folder inside `src` should be the major `language` part. For example, if you will be creating a `pt-BR` source, use `` here as `pt` only. Inside the source class, use the full locale string instead. + #### Extension file structure The simplest extension structure looks like this: @@ -78,7 +178,7 @@ ext { pkgNameSuffix = '.' extClass = '.' extVersionCode = 1 - isNsfw = true + containsNsfw = true } apply from: "$rootDir/common.gradle" @@ -90,8 +190,8 @@ apply from: "$rootDir/common.gradle" | `pkgNameSuffix` | A unique suffix added to `eu.kanade.tachiyomi.animeextension`. The language and the site name should be enough. Remember your extension code implementation must be placed in this package. | | `extClass` | Points to the class that implements `AnimeSource`. You can use a relative path starting with a dot (the package name is the base path). This is used to find and instantiate the source(s). | | `extVersionCode` | The extension version code. This must be a positive integer and incremented with any change to the code. | -| `libVersion` | (Optional, defaults to `12`) The version of the [extensions library](https://github.com/jmir1/extensions-lib) used. | -| `isNsfw` | (Optional, defaults to `false`) Flag to indicate that a source contains NSFW content. | +| `libVersion` | (Optional, defaults to `13`) The version of the [extensions library](https://github.com/jmir1/extensions-lib) used. | +| `containsNsfw` | (Optional, defaults to `false`) Flag to indicate that a source contains NSFW content. | The extension's version name is generated automatically by concatenating `libVersion` and `extVersionCode`. With the example used above, the version would be `12.1`. @@ -113,22 +213,22 @@ dependencies { #### Additional dependencies -You may find yourself needing additional functionality and wanting to add more dependencies to your `build.gradle` file. Since extensions are run within the main Tachiyomi app, you can make use of [its dependencies](https://github.com/jmir1/aniyomi/blob/master/app/build.gradle.kts). +You may find yourself needing additional functionality and wanting to add more dependencies to your `build.gradle` file. Since extensions are run within the main Aniyomi app, you can make use of [its dependencies](https://github.com/jmir1/aniyomi/blob/master/app/build.gradle.kts). For example, an extension that needs coroutines, it could add the following: ```gradle dependencies { - compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' - compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' + compileOnly(libs.bundles.coroutines) } ``` -(Note that several dependencies are already exposed to all extensions via `common-dependencies.gradle`.) +> Note that several dependencies are already exposed to all extensions via Gradle version catalog. +> To view which are available view `libs.versions.toml` under the `gradle` folder Notice that we're using `compileOnly` instead of `implementation`, since the app already contains it. You could use `implementation` instead for a new dependency, or you prefer not to rely on whatever the main app has at the expense of app size. -Note that using `compileOnly` restricts you to versions that must be compatible with those used in [Tachiyomi v0.10.12+](https://github.com/tachiyomiorg/tachiyomi/blob/v0.10.12/app/build.gradle.kts) for proper backwards compatibility. +Note that using `compileOnly` restricts you to versions that must be compatible with those used in [Aniyomi v0.10.12+](https://github.com/jmir1/aniyomi/blob/v0.10.12/app/build.gradle.kts) for proper backwards compatibility. ### Extension main class @@ -144,12 +244,11 @@ The class which is referenced and defined by `extClass` in `build.gradle`. This | Field | Description | | ----- | ----------- | -| `name` | Name displayed in the "Sources" tab in Tachiyomi. | +| `name` | Name displayed in the "Sources" tab in Aniyomi. | | `baseUrl` | Base URL of the source without any trailing slashes. | | `lang` | An ISO 639-1 compliant language code (two letters in lower case). | | `id` | Identifier of your source, automatically set in `AnimeHttpSource`. It should only be manually overriden if you need to copy an existing autogenerated ID. | - ### Extension call flow #### Popular Anime @@ -174,6 +273,32 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late - If search functionality is not available, return `Observable.just(AnimesPage(emptyList(), false))` - `getFilterList` will be called to get all filters and filter types. **TODO: explain more about `Filter`** +##### Filters + +The search flow have support to filters that can be added to a `FilterList` inside the `getFilterList` method. When the user changes the filters' state, they will be passed to the `searchRequest`, and they can be iterated to create the request (by getting the `filter.state` value, where the type varies depending on the `Filter` used). You can check the filter types available [here](https://github.com/jmir1/aniyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt) and in the table below. + +| Filter | State type | Description | +| ------ | ---------- | ----------- | +| `Filter.Header` | None | A simple header. Useful for separating sections in the list or showing any note or warning to the user. | +| `Filter.Separator` | None | A line separator. Useful for visual distinction between sections. | +| `Filter.Select` | `Int` | A select control, similar to HTML's ``. | +| `Filter.CheckBox` | `Boolean` | A checkbox control, similar to HTML's ``. The state is `true` if it's checked. | +| `Filter.TriState` | `Int` | A enhanced checkbox control that supports an excluding state. The state can be compared with `STATE_IGNORE`, `STATE_INCLUDE` and `STATE_EXCLUDE` constants of the class. | +| `Filter.Group` | `List` | A group of filters (preferentially of the same type). The state will be a `List` with all the states. | +| `Filter.Sort` | `Selection` | A control for sorting, with support for the ordering. The state indicates which item index is selected and if the sorting is `ascending`. | + +All control filters can have a default state set. It's usually recommended if the source have filters to make the initial state match the popular manga list, so when the user open the filter sheet, the state is equal and represents the current manga showing. + +The `Filter` classes can also be extended, so you can create new custom filters like the `UriPartFilter`: + +```kotlin +open class UriPartFilter(displayName: String, private val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second +} +``` + #### Anime Details - When user taps on an anime, `fetchAnimeDetails` and `fetchEpisodeList` will be called and the results will be cached. @@ -210,7 +335,7 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late Make sure you make the `SimpleDateFormat` a class constant or variable so it doesn't get recreated for every episode. If you need to parse or format dates in anime description, create another instance since `SimpleDateFormat` is not thread-safe. - If the parsing have any problem, make sure to return `0L` so the app will use the default date instead. - The app will overwrite dates of existing old episodes **UNLESS** `0L` is returned. - - The default date has [changed](https://github.com/tachiyomiorg/tachiyomi/pull/7197) in Tachiyomi preview ≥ r4442 or stable > 0.13.4. + - The default date has [changed](https://github.com/jmir1/aniyomi/pull/7197) in Aniyomi preview ≥ r4442 or stable > 0.13.4. - In older versions, the default date is always the fetch date. - In newer versions, this is the same if every (new) episode has `0L` returned. - However, if the source only provides the upload date of the latest episode, you can now set it to the latest episode and leave other episodes default. The app will automatically set it (instead of fetch date) to every new episode and leave old episodes' dates untouched. @@ -231,8 +356,29 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late #### URL intent filter Extensions can define URL intent filters by defining it inside a custom `AndroidManifest.xml` file. -For an example, refer to [the NHentai module's `AndroidManifest.xml` file](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/src/all/nhentai/AndroidManifest.xml) and [its corresponding `NHUrlActivity` handler](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUrlActivity.kt). +For an example, refer to [the NHentai module's `AndroidManifest.xml` file](https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/nhentai/AndroidManifest.xml) and [its corresponding `NHUrlActivity` handler](https://github.com/jmir1/aniyomi-extensions/blob/master/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUrlActivity.kt). + +To test if the URL intent filter is working as expected, you can try opening the website in a browser and navigating to the endpoint that was added as a filter or clicking a hyperlink. Alternatively, you can use the `adb` command below. + +```console +$ adb shell am start -d "" -a android.intent.action.VIEW +``` + +#### Renaming existing sources + +There is some cases where existing sources changes their name on the website. To correctly reflect these changes in the extension, you need to explicity set the `id` to the same old value, otherwise it will get changed by the new `name` value and users will be forced to migrate back to the source. + +To get the current `id` value before the name change, you can search the source name in the [repository JSON file](https://github.com/jmir1/aniyomi-extensions/blob/repo/index.json) by looking into the `sources` attribute of the extension. When you have the `id` copied, you can override it in the source: + +```kotlin +override val id: Long = +``` + +Then the class name and the `name` attribute value can be changed. Also don't forget to update the extension name and class name in the individual Gradle file if it is not a multisrc extension. + +**Important:** the package name **needs** to be the same (even if it has the old name), otherwise users will not receive the extension update when it gets published in the repository. If you're changing the name of a multisrc source, you can manually set it in the generator class of the theme by using `pkgName = "oldpackagename"`. +The `id` also needs to be explicity set to the old value if you're changing the `lang` attribute. ## Multi-source themes The `multisrc` module houses source code for generating extensions for cases where multiple source sites use the same site generator tool(usually a CMS) for bootstraping their website and this makes them similar enough to prompt code reuse through inheritance/composition; which from now on we will use the general **theme** term to refer to. @@ -245,38 +391,38 @@ $ tree multisrc multisrc ├── build.gradle.kts ├── overrides -│ └── -│ ├── default -│ │ ├── additional.gradle.kts -│ │ └── res -│ │ ├── mipmap-hdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-mdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xhdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xxhdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xxxhdpi -│ │ │ └── ic_launcher.png -│ │ └── web_hi_res_512.png -│ └── -│ ├── additional.gradle.kts -│ ├── AndroidManifest.xml -│ ├── res -│ │ ├── mipmap-hdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-mdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xhdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xxhdpi -│ │ │ └── ic_launcher.png -│ │ ├── mipmap-xxxhdpi -│ │ │ └── ic_launcher.png -│ │ └── web_hi_res_512.png -│ └── src -│ └── .kt +│   └── +│   ├── default +│   │   ├── additional.gradle +│   │   └── res +│   │   ├── mipmap-hdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-mdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xhdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xxhdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xxxhdpi +│   │   │   └── ic_launcher.png +│   │   └── web_hi_res_512.png +│   └── +│   ├── additional.gradle +│   ├── AndroidManifest.xml +│   ├── res +│   │   ├── mipmap-hdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-mdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xhdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xxhdpi +│   │   │   └── ic_launcher.png +│   │   ├── mipmap-xxxhdpi +│   │   │   └── ic_launcher.png +│   │   └── web_hi_res_512.png +│   └── src +│   └── .kt └── src └── main ├── AndroidManifest.xml @@ -301,7 +447,7 @@ multisrc - `multisrc/overrides//` contains overrides for a source that is defined inside the `Generator.kt` class. - `multisrc/overrides///src` contains source overrides. - `multisrc/overrides///res` contains override for icons. -- `multisrc/overrides///additional.gradle.kts` defines additional gradle code, this will be copied at the end of the generated gradle file below the theme's `additional.gradle.kts`. +- `multisrc/overrides///additional.gradle` defines additional gradle code, this will be copied at the end of the generated gradle file below the theme's `additional.gradle`. - `multisrc/overrides///AndroidManifest.xml` is copied as an override to the default `AndroidManifest.xml` generation if it exists. ### Development workflow @@ -354,10 +500,13 @@ with open(f"{package}/src/{source}.kt", "w") as f: - For each time a source changes in a way that should the version increase, `overrideVersionCode` should be increased by one. - When a theme's default implementation changes, `baseVersionCode` should be increased, the initial value should be `1`. - For example, for a new theme with a new source, extention version code will be `0 + 0 + 1 = 1`. +- `IntelijConfigurationGeneratorMainKt` should be run on creating or removing a multisrc theme. + - On removing a theme, you can manually remove the corresponding configuration in the `.run` folder instead. + - Be careful if you're using sparse checkout. If other configurations are accidentally removed, `git add` the file you want and `git restore` the others. Another choice is to allow `/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/*` before running the generator. ## Running -To make local development more convenient, you can use the following run configuration to launch Tachiyomi directly at the Browse panel: +To make local development more convenient, you can use the following run configuration to launch Aniyomi directly at the Browse panel: ![](https://i.imgur.com/STy0UFY.png) @@ -383,7 +532,7 @@ You can leverage the Android Debugger to step through your extension while debug You *cannot* simply use Android Studio's `Debug 'module.name'` -> this will most likely result in an error while launching. -Instead, once you've built and installed your extension on the target device, use `Attach Debugger to Android Process` to start debugging Tachiyomi. +Instead, once you've built and installed your extension on the target device, use `Attach Debugger to Android Process` to start debugging Aniyomi. ![](https://i.imgur.com/muhXyfu.png) @@ -391,9 +540,94 @@ Instead, once you've built and installed your extension on the target device, us ### Logs You can also elect to simply rely on logs printed from your extension, which -show up in the [`Logcat`](https://developer.android.com/studio/debug/am-logcat) panel of Android Studio +show up in the [`Logcat`](https://developer.android.com/studio/debug/am-logcat) panel of Android Studio. +### Inspecting network calls +One of the easiest way to inspect network issues (such as HTTP errors 404, 429, no chapter found etc.) is to use the [`Logcat`](https://developer.android.com/studio/debug/am-logcat) panel of Android Studio and filtering by the `OkHttpClient` tag. + +To be able to check the calls done by OkHttp, you need to enable verbose logging in the app, that is not enabled by default and is only included in the Preview versions of Aniyomi. To enable it, go to More -> Settings -> Advanced -> Verbose logging. After enabling it, don't forget to restart the app. + +Inspecting the Logcat allows you to get a good look at the call flow and it's more than enough in most cases where issues occurs. However, alternatively, you can also use an external tool like `mitm-proxy`. For that, refer to the next section. + +### Using external network inspecting tools +If you want to take a deeper look into the network flow, such as taking a look into the request and response bodies, you can use an external tool like `mitm-proxy`. + +#### Setup your proxy server +We are going to use [mitm-proxy](https://mitmproxy.org/) but you can replace it with any other Web Debugger (i.e. Charles, burp, Fiddler etc). To install and execute, follow the commands bellow. + +```console +Install the tool. +$ sudo pip3 install mitmproxy +Execute the web interface and the proxy. +$ mitmweb +``` + +Alternatively, you can also use the Docker image: + +``` +$ docker run --rm -it -p 8080:8080 \ + -p 127.0.0.1:8081:8081 \ + --web-host 0.0.0.0 \ + mitmproxy/mitmproxy mitmweb +``` + +After installing and running, open your browser and navigate to http://127.0.0.1:8081. + +#### OkHttp proxy setup +Since most of the manga sources are going to use HTTPS, we need to disable SSL verification in order to use the web debugger. For that, add this code to inside your source class: + + +```kotlin +class AnimeSource : MadTheme( + "AnimeSource", + "https://example.com", + "en" +) { + private fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder { + val naiveTrustManager = object : X509TrustManager { + override fun getAcceptedIssuers(): Array = arrayOf() + override fun checkClientTrusted(certs: Array, authType: String) = Unit + override fun checkServerTrusted(certs: Array, authType: String) = Unit + } + + val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply { + val trustAllCerts = arrayOf(naiveTrustManager) + init(null, trustAllCerts, SecureRandom()) + }.socketFactory + + sslSocketFactory(insecureSocketFactory, naiveTrustManager) + hostnameVerifier(HostnameVerifier { _, _ -> true }) + return this + } + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .ignoreAllSSLErrors() + .proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("10.0.2.2", 8080))) + .... + .build() +``` + +Note: `10.0.2.2` is usually the address of your loopback interface in the android emulator. If Aniyomi tells you that it's unable to connect to 10.0.2.2:8080 you will likely need to change it (the same if you are using hardware device). + +If all went well, you should see all requests and responses made by the source in the web interface of `mitmweb`. ## Building APKs can be created in Android Studio via `Build > Build Bundle(s) / APK(s) > Build APK(s)` or `Build > Generate Signed Bundle / APK`. + +## Submitting the changes + +When you feel confident about your changes, submit a new Pull Request so your code can be reviewed and merged if it's approved. We encourage following a [GitHub Standard Fork & Pull Request Workflow](https://gist.github.com/Chaser324/ce0505fbed06b947d962) and following the good practices of the workflow, such as not commiting directly to `master`: always create a new branch for your changes. + +If you are more comfortable about using Git GUI-based tools, you can refer to [this guide](https://learntodroid.com/how-to-use-git-and-github-in-android-studio/) about the Git integration inside Android Studio, specifically the "How to Contribute to an to Existing Git Repository in Android Studio" section of the guide. + +Please **do test your changes by compiling it through Android Studio** before submitting it. Also make sure to follow the PR checklist available in the PR body field when creating a new PR. As a reference, you can find it below. + +### Pull Request checklist + +- Update `extVersionCode` value in `build.gradle` for individual extensions +- Update `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions +- Reference all related issues in the PR body (e.g. "Closes #xyz") +- Add the `containsNsfw = true` flag in `build.gradle` when appropriate +- Explicitly kept the `id` if a source's name or language were changed +- Test the modifications by compiling and running the extension through Android Studio diff --git a/README.md b/README.md index a3c9d9fe40..e323a2ae4f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ | ![CI](https://github.com/jmir1/aniyomi-extensions/workflows/CI/badge.svg?event=push) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | # ![app icon](./.github/readme-images/app-icon.png)Aniyomi Extensions -Tachiyomi is a free, cool, awesome and open source manga reader for Android 6.0 and above. +Aniyomi is a free and open source manga reader for Android 6.0 and above. This repository contains the available extension catalogues for the [Aniyomi](https://github.com/jmir1/aniyomi) fork. diff --git a/annotations/src/main/kotlin/Nsfw.kt b/annotations/src/main/kotlin/Nsfw.kt deleted file mode 100644 index ac899f0433..0000000000 --- a/annotations/src/main/kotlin/Nsfw.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.annotations - -/** - * Annotation used to mark a Source (i.e. individual sources) or a SourceFactory (i.e. all sources - * within it) as NSFW. Used within the Tachiyomi app to prevent loading sources when parental - * controls are enabled. - */ -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -annotation class Nsfw diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 4c65a4a4c6..0000000000 --- a/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -buildscript { - ext.kotlin_version = '1.6.10' - ext.coroutines_version = '1.6.0' - repositories { - mavenCentral() - google() - maven { url 'https://plugins.gradle.org/m2/' } - } - dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'org.jmailen.gradle:kotlinter-gradle:3.6.0' - } -} - -allprojects { - repositories { - mavenCentral() - google() - maven { url 'https://jitpack.io' } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..b3b8f26ca1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,25 @@ +buildscript { + repositories { + mavenCentral() + google() + maven(url = "https://plugins.gradle.org/m2/") + } + dependencies { + classpath(libs.gradle.agp) + classpath(libs.gradle.kotlin) + classpath(libs.gradle.serialization) + classpath(libs.gradle.kotlinter) + } +} + +allprojects { + repositories { + mavenCentral() + google() + maven(url = "https://jitpack.io") + } +} + +tasks.register("clean") { + delete(rootProject.buildDir) +} diff --git a/buildSrc/src/main/kotlin/AndroidConfig.kt b/buildSrc/src/main/kotlin/AndroidConfig.kt index 94aaf4e26a..66c775774e 100644 --- a/buildSrc/src/main/kotlin/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/AndroidConfig.kt @@ -1,6 +1,5 @@ object AndroidConfig { - const val compileSdk = 30 + const val compileSdk = 32 const val minSdk = 21 - const val targetSdk = 29 - const val buildTools = "30.0.3" + const val targetSdk = 32 } diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt deleted file mode 100644 index 2ef266b984..0000000000 --- a/buildSrc/src/main/kotlin/Deps.kt +++ /dev/null @@ -1,9 +0,0 @@ -object Deps { - object kotlin { - const val version = "1.4.10" - const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version" - } - - const val jsoup = "org.jsoup:jsoup:1.13.1" - const val okhttp = "com.squareup.okhttp3:okhttp:3.10.0" -} diff --git a/common-dependencies.gradle b/common-dependencies.gradle deleted file mode 100644 index 5aaeecce54..0000000000 --- a/common-dependencies.gradle +++ /dev/null @@ -1,18 +0,0 @@ -// used both in common.gradle and themesources library -dependencies { - // Lib 12, but using specific commit so we don't need to bump up the version - compileOnly "com.github.jmir1:extensions-lib:8400462" - - // These are provided by the app itself - compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - - compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440' - compileOnly 'com.squareup.okhttp3:okhttp:4.9.1' - compileOnly 'io.reactivex:rxjava:1.3.8' - compileOnly 'org.jsoup:jsoup:1.13.1' - compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.2.0' - compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0' - - implementation project(":annotations") - compileOnly project(':duktape-stub') -} diff --git a/common.gradle b/common.gradle index 3ced27daae..e56ee17303 100644 --- a/common.gradle +++ b/common.gradle @@ -2,13 +2,6 @@ apply plugin: 'org.jmailen.kotlinter' android { compileSdkVersion AndroidConfig.compileSdk - buildToolsVersion AndroidConfig.buildTools - - buildTypes { - release { - minifyEnabled false - } - } sourceSets { main { @@ -29,7 +22,7 @@ android { targetSdkVersion AndroidConfig.targetSdk applicationIdSuffix pkgNameSuffix versionCode extVersionCode - versionName "$libVersion.$extVersionCode" + versionName project.ext.properties.getOrDefault("libVersion", "13") + ".$extVersionCode" setProperty("archivesBaseName", "aniyomi-$pkgNameSuffix-v$versionName") manifestPlaceholders = [ appName : "Aniyomi: $extName", @@ -39,6 +32,22 @@ android { ] } + signingConfigs { + release { + storeFile rootProject.file("signingkey.jks") + storePassword System.getenv("KEY_STORE_PASSWORD") + keyAlias System.getenv("ALIAS") + keyPassword System.getenv("KEY_PASSWORD") + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled false + } + } + dependenciesInfo { includeInApk = false } @@ -58,6 +67,7 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() + freeCompilerArgs += "-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi" } } @@ -66,9 +76,9 @@ repositories { } dependencies { - implementation project(":core") + implementation(project(":core")) + compileOnly(libs.bundles.common) } -apply from: "$rootDir/common-dependencies.gradle" preBuild.dependsOn(lintKotlin) lintKotlin.dependsOn(formatKotlin) diff --git a/core/AndroidManifest.xml b/core/AndroidManifest.xml index 03efc77cfd..241bdea792 100644 --- a/core/AndroidManifest.xml +++ b/core/AndroidManifest.xml @@ -9,6 +9,8 @@ + + diff --git a/core/build.gradle.kts b/core/build.gradle.kts index c9065b8d4c..90524827b0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,11 +3,10 @@ plugins { } android { - compileSdkVersion(AndroidConfig.compileSdk) - buildToolsVersion(AndroidConfig.buildTools) + compileSdk = AndroidConfig.compileSdk defaultConfig { - minSdkVersion(AndroidConfig.minSdk) + minSdk = AndroidConfig.minSdk } sourceSets { diff --git a/gradle.properties b/gradle.properties index ae3b5127c9..08688f6bd8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,12 +9,13 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx3072m +org.gradle.jvmargs=-Xmx5120m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true +org.gradle.workers.max=5 org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..c878a525d4 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,32 @@ +[versions] +kotlin_version = "1.6.21" +coroutines_version = "1.6.0" +serialization_version = "1.3.2" + +[libraries] +gradle-agp = { module = "com.android.tools.build:gradle", version = "7.2.1" } +gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } +gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" } +gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.6.0" } + +aniyomi-lib = { module = "com.github.jmir1:extensions-lib", version = "a2f1874" } + +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin_version" } +kotlin-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" } +kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } + +coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" } +coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" } + +injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" } +jsoup = { module = "org.jsoup:jsoup", version = "1.13.1" } +duktape = { module = "com.squareup.duktape:duktape-android", version = "1.4.0" } +quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version = "4.9.1" } +rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" } +rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" } + +[bundles] +common = ["kotlin-stdlib", "injekt-core", "rxjava", "kotlin-protobuf", "kotlin-json", "jsoup", "okhttp", "aniyomi-lib", "duktape", "quickjs"] +coroutines = ["coroutines-core", "coroutines-android"] +reactivex = ["rxandroid"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643eed..aa991fceae 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c81..1b6c787337 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/lib/dataimage/build.gradle.kts b/lib/dataimage/build.gradle.kts index f5cf8cb52b..d02fbec564 100644 --- a/lib/dataimage/build.gradle.kts +++ b/lib/dataimage/build.gradle.kts @@ -4,12 +4,11 @@ plugins { } android { - compileSdkVersion(AndroidConfig.compileSdk) - buildToolsVersion(AndroidConfig.buildTools) + compileSdk = AndroidConfig.compileSdk defaultConfig { - minSdkVersion(AndroidConfig.minSdk) - targetSdkVersion(AndroidConfig.targetSdk) + minSdk = AndroidConfig.minSdk + targetSdk = AndroidConfig.targetSdk } } @@ -18,7 +17,7 @@ repositories { } dependencies { - compileOnly(Dependencies.kotlin.stdlib) - compileOnly(Dependencies.okhttp) - compileOnly(Dependencies.jsoup) + compileOnly(libs.kotlin.stdlib) + compileOnly(libs.okhttp) + compileOnly(libs.jsoup) } diff --git a/lib/duktape-stub/build.gradle.kts b/lib/duktape-stub/build.gradle.kts deleted file mode 100644 index b07c0af69c..0000000000 --- a/lib/duktape-stub/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - java -} - -sourceSets { - main { - java { - srcDirs(listOf("src")) - } - } -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} diff --git a/lib/duktape-stub/src/com/squareup/duktape/Duktape.java b/lib/duktape-stub/src/com/squareup/duktape/Duktape.java deleted file mode 100644 index 12bdb02526..0000000000 --- a/lib/duktape-stub/src/com/squareup/duktape/Duktape.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.squareup.duktape; - -import java.io.Closeable; -import java.io.IOException; - -@SuppressWarnings("all") -public class Duktape implements Closeable { - - public static Duktape create() { - throw new RuntimeException("Stub!"); - } - - @Override - public synchronized void close() throws IOException { - throw new RuntimeException("Stub!"); - } - - public synchronized Object evaluate(String script) { - throw new RuntimeException("Stub!"); - } - - public synchronized void set(String name, Class type, T object) { - throw new RuntimeException("Stub!"); - } - -} \ No newline at end of file diff --git a/lib/ratelimit/build.gradle.kts b/lib/ratelimit/build.gradle.kts deleted file mode 100644 index a9c37e057f..0000000000 --- a/lib/ratelimit/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("com.android.library") - kotlin("android") -} - -android { - compileSdkVersion(AndroidConfig.compileSdk) - buildToolsVersion(AndroidConfig.buildTools) - - defaultConfig { - minSdkVersion(AndroidConfig.minSdk) - targetSdkVersion(AndroidConfig.targetSdk) - } -} - -repositories { - mavenCentral() -} - -dependencies { - compileOnly(Dependencies.kotlin.stdlib) - compileOnly(Dependencies.okhttp) -} diff --git a/lib/ratelimit/src/main/AndroidManifest.xml b/lib/ratelimit/src/main/AndroidManifest.xml deleted file mode 100644 index a08d6eaffa..0000000000 --- a/lib/ratelimit/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt b/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt deleted file mode 100644 index 32bcbcb8d9..0000000000 --- a/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt +++ /dev/null @@ -1,58 +0,0 @@ -package eu.kanade.tachiyomi.lib.ratelimit - -import android.os.SystemClock -import okhttp3.Interceptor -import okhttp3.Response -import java.util.concurrent.TimeUnit - -/** - * An OkHttp interceptor that handles rate limiting. - * - * Examples: - * - * permits = 5, period = 1, unit = seconds => 5 requests per second - * permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes - * - * @param permits {Int} Number of requests allowed within a period of units. - * @param period {Long} The limiting duration. Defaults to 1. - * @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. - */ -class RateLimitInterceptor( - private val permits: Int, - private val period: Long = 1, - private val unit: TimeUnit = TimeUnit.SECONDS) : Interceptor { - - private val requestQueue = ArrayList(permits) - private val rateLimitMillis = unit.toMillis(period) - - override fun intercept(chain: Interceptor.Chain): Response { - synchronized(requestQueue) { - val now = SystemClock.elapsedRealtime() - val waitTime = if (requestQueue.size < permits) { - 0 - } else { - val oldestReq = requestQueue[0] - val newestReq = requestQueue[permits - 1] - - if (newestReq - oldestReq > rateLimitMillis) { - 0 - } else { - oldestReq + rateLimitMillis - now // Remaining time - } - } - - if (requestQueue.size == permits) { - requestQueue.removeAt(0) - } - if (waitTime > 0) { - requestQueue.add(now + waitTime) - Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests - } else { - requestQueue.add(now) - } - } - - return chain.proceed(chain.request()) - } - -} diff --git a/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/SpecificHostRateLimitInterceptor.kt b/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/SpecificHostRateLimitInterceptor.kt deleted file mode 100644 index cd2e5cbf00..0000000000 --- a/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/SpecificHostRateLimitInterceptor.kt +++ /dev/null @@ -1,65 +0,0 @@ -package eu.kanade.tachiyomi.lib.ratelimit - -import android.os.SystemClock -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.Response -import java.util.concurrent.TimeUnit - -/** - * An OkHttp interceptor that handles given url host's rate limiting. - * - * Examples: - * - * httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com - * httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com - * - * @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host() - * @param permits {Int} Number of requests allowed within a period of units. - * @param period {Long} The limiting duration. Defaults to 1. - * @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. - */ -class SpecificHostRateLimitInterceptor( - private val httpUrl: HttpUrl, - private val permits: Int, - private val period: Long = 1, - private val unit: TimeUnit = TimeUnit.SECONDS -) : Interceptor { - - private val requestQueue = ArrayList(permits) - private val rateLimitMillis = unit.toMillis(period) - private val host = httpUrl.host - - override fun intercept(chain: Interceptor.Chain): Response { - if (chain.request().url.host != host) { - return chain.proceed(chain.request()) - } - synchronized(requestQueue) { - val now = SystemClock.elapsedRealtime() - val waitTime = if (requestQueue.size < permits) { - 0 - } else { - val oldestReq = requestQueue[0] - val newestReq = requestQueue[permits - 1] - - if (newestReq - oldestReq > rateLimitMillis) { - 0 - } else { - oldestReq + rateLimitMillis - now // Remaining time - } - } - - if (requestQueue.size == permits) { - requestQueue.removeAt(0) - } - if (waitTime > 0) { - requestQueue.add(now + waitTime) - Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests - } else { - requestQueue.add(now) - } - } - - return chain.proceed(chain.request()) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index e4ddca8855..a01f2ac363 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,17 +1,10 @@ -include(":annotations") include(":core") -include(":lib-ratelimit") -project(":lib-ratelimit").projectDir = File("lib/ratelimit") - -include(":duktape-stub") -project(":duktape-stub").projectDir = File("lib/duktape-stub") - include(":lib-dataimage") project(":lib-dataimage").projectDir = File("lib/dataimage") -if (System.getenv("CI") == null || System.getenv("CI_PUSH") == "true") { - // Local development or full build for push +if (System.getenv("CI") == null || System.getenv("CI_MODULE_GEN") == "true") { + // Local development (full project build) //include(":multisrc") //project(":multisrc").projectDir = File("multisrc") @@ -37,44 +30,54 @@ if (System.getenv("CI") == null || System.getenv("CI_PUSH") == "true") { * If you're developing locally and only want to work with a single module, * comment out the parts above and uncomment below. */ - // val lang = "all" - // val name = "mmrcms" - // include(":${lang}-${name}") - // project(":${lang}-${name}").projectDir = File("src/${lang}/${name}") +// val lang = "all" +// val name = "mangadex" +// val projectName = ":extensions:individual:$lang:$name" +// val projectName = ":extensions:multisrc:$lang:$name" +// include(projectName) +// project(projectName).projectDir = File("src/${lang}/${name}") +// project(projectName).projectDir = File("generated-src/${lang}/${name}") } else { // Running in CI (GitHub Actions) val isMultisrc = System.getenv("CI_MULTISRC") == "true" - val lang = System.getenv("CI_MATRIX_LANG") + val chunkSize = System.getenv("CI_CHUNK_SIZE").toInt() + val chunk = System.getenv("CI_CHUNK_NUM").toInt() /*if (isMultisrc) { include(":multisrc") project(":multisrc").projectDir = File("multisrc") // Loads generated extensions from multisrc - File(rootDir, "generated-src").eachDir { dir -> - if (dir.name == lang) { - dir.eachDir { subdir -> - val name = ":extensions:multisrc:${dir.name}:${subdir.name}" - include(name) - project(name).projectDir = File("generated-src/${dir.name}/${subdir.name}") - } - } + File(rootDir, "generated-src").getChunk(chunk, chunkSize)?.forEach { + val name = ":extensions:multisrc:${it.parentFile.name}:${it.name}" + println(name) + include(name) + project(name).projectDir = File("generated-src/${it.parentFile.name}/${it.name}") } } else {*/ - // Loads all extensions - File(rootDir, "src").eachDir { dir -> - if (dir.name == lang) { - dir.eachDir { subdir -> - val name = ":extensions:individual:${dir.name}:${subdir.name}" - include(name) - project(name).projectDir = File("src/${dir.name}/${subdir.name}") - } - } + // Loads individual extensions + File(rootDir, "src").getChunk(chunk, chunkSize)?.forEach { + val name = ":extensions:individual:${it.parentFile.name}:${it.name}" + println(name) + include(name) + project(name).projectDir = File("src/${it.parentFile.name}/${it.name}") } //} } -inline fun File.eachDir(block: (File) -> Unit) { +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) { listFiles()?.filter { it.isDirectory }?.forEach { block(it) } } diff --git a/src/de/fireanime/build.gradle b/src/de/fireanime/build.gradle index 0843b05eaf..f2335a41c6 100644 --- a/src/de/fireanime/build.gradle +++ b/src/de/fireanime/build.gradle @@ -10,8 +10,5 @@ ext { libVersion = '13' containsNsfw = true } -dependencies { - implementation project(':lib-ratelimit') -} apply from: "$rootDir/common.gradle" diff --git a/src/de/fireanime/src/eu/kanade/tachiyomi/animeextension/de/fireanime/FireAnime.kt b/src/de/fireanime/src/eu/kanade/tachiyomi/animeextension/de/fireanime/FireAnime.kt index 72adfb4e57..ef6e0a5a7e 100644 --- a/src/de/fireanime/src/eu/kanade/tachiyomi/animeextension/de/fireanime/FireAnime.kt +++ b/src/de/fireanime/src/eu/kanade/tachiyomi/animeextension/de/fireanime/FireAnime.kt @@ -23,9 +23,9 @@ import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json import okhttp3.FormBody @@ -48,7 +48,7 @@ class FireAnime : ConfigurableAnimeSource, AnimeHttpSource() { override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(120, 1, TimeUnit.MINUTES)) + .rateLimit(120, 1, TimeUnit.MINUTES) .build() private val json = Json { diff --git a/src/pt/animefire/build.gradle b/src/pt/animefire/build.gradle index 992c971a71..bc4bec9b79 100644 --- a/src/pt/animefire/build.gradle +++ b/src/pt/animefire/build.gradle @@ -10,9 +10,5 @@ ext { libVersion = '13' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} - apply from: "$rootDir/common.gradle" diff --git a/src/pt/animesvision/build.gradle b/src/pt/animesvision/build.gradle index 2f57fd0fd8..8d76146467 100644 --- a/src/pt/animesvision/build.gradle +++ b/src/pt/animesvision/build.gradle @@ -12,7 +12,6 @@ ext { dependencies { compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } apply from: "$rootDir/common.gradle" diff --git a/src/pt/animeyabu/build.gradle b/src/pt/animeyabu/build.gradle index 44d3ed662a..9d176bba94 100644 --- a/src/pt/animeyabu/build.gradle +++ b/src/pt/animeyabu/build.gradle @@ -10,8 +10,6 @@ ext { libVersion = '13' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} + apply from: "$rootDir/common.gradle" diff --git a/src/pt/betteranime/build.gradle b/src/pt/betteranime/build.gradle index 6576e97fb2..faf8f1615a 100644 --- a/src/pt/betteranime/build.gradle +++ b/src/pt/betteranime/build.gradle @@ -12,7 +12,6 @@ ext { dependencies { compileOnly 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } android { diff --git a/src/pt/hentaiyabu/build.gradle b/src/pt/hentaiyabu/build.gradle index 94963c818f..c712bf8d12 100644 --- a/src/pt/hentaiyabu/build.gradle +++ b/src/pt/hentaiyabu/build.gradle @@ -11,8 +11,6 @@ ext { containsNsfw = true } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} + apply from: "$rootDir/common.gradle" diff --git a/src/pt/puraymoe/build.gradle b/src/pt/puraymoe/build.gradle index aa512341ce..526ddb14ed 100644 --- a/src/pt/puraymoe/build.gradle +++ b/src/pt/puraymoe/build.gradle @@ -10,9 +10,7 @@ ext { libVersion = '13' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} + apply from: "$rootDir/common.gradle" diff --git a/template/README-REMOVED-TEMPLATE.md b/template/README-REMOVED-TEMPLATE.md new file mode 100644 index 0000000000..58588fa4be --- /dev/null +++ b/template/README-REMOVED-TEMPLATE.md @@ -0,0 +1,11 @@ +# Extension name + +## Why + +Link to the related issue where it is explained why it was removed. +Or a short description to why it was removed. +Or both the related issue link and short description to why + +--- + +Don't find the question you are look for go check out our general FAQs and Guides over at [Extension FAQ](https://aniyomi.org/help/faq/#extensions) or [Getting Started](https://aniyomi.org/help/guides/getting-started/#installation) diff --git a/template/README-TEMPLATE.md b/template/README-TEMPLATE.md new file mode 100644 index 0000000000..72e41f8342 --- /dev/null +++ b/template/README-TEMPLATE.md @@ -0,0 +1,12 @@ +# Extension name + +Table of Content +- [FAQ](#FAQ) + +[Uncomment this if needed; and replace ( and ) with ( and )]: <> (- [Guides](#Guides)) + +Don't find the question you are look for go check out our general FAQs and Guides over at [Extension FAQ](https://aniyomi.org/help/faq/#extensions) or [Getting Started](https://aniyomi.org/help/guides/getting-started/#installation) + +## FAQ + +[Uncomment this if needed]: <> (## Guides) \ No newline at end of file