merge upstream

This commit is contained in:
jmir1 2022-08-06 20:17:53 +02:00
parent bd076c3e5d
commit 0f7df7a71c
59 changed files with 1386 additions and 672 deletions

View file

@ -8,4 +8,3 @@ indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

View file

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

View file

@ -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.
about: Issues about the app itself should be opened here instead.

View file

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

View file

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

98
.github/ISSUE_TEMPLATE/report_issue.yml vendored Normal file
View file

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

View file

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

41
.github/ISSUE_TEMPLATE/request_meta.yml vendored Normal file
View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
---
name: "🌐 Source Request"
title: "[Source Request] <Source Name>"
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

View file

@ -1,12 +1,9 @@
<!--
If you are updating extensions, please remember to:
Checklist:
- Update the `extVersionCode` value in `build.gradle`
- Annotate `Source` or `SourceFactory` classes with `@Nsfw` when appropriate
- Add the `containsNsfw = true` flag in `build.gradle` when appropriate
Please also mention the related issues, e.g.:
Closes #123
Closes #456
-->
- [ ] 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

View file

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

View file

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

View file

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

26
.github/scripts/move-apks.sh vendored Normal file
View file

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

View file

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

View file

@ -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
java-version: 11
distribution: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- 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

View file

@ -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]')"
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: 11
distribution: adopt
# - 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:
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: 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: 11
distribution: adopt
- name: Prepare signing key
run: |
echo ${{ secrets.SIGNING_KEY }} | base64 -d > signingkey.jks
- name: Build extensions (chunk ${{ matrix.chunk }})
uses: gradle/gradle-command-action@v2
env:
CI_MULTISRC: "false"
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: "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: Cancel previous runs
uses: styfle/cancel-workflow-action@0.8.0
- name: Download APK artifacts
uses: actions/download-artifact@v2
with:
access_token: ${{ github.token }}
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@v2
uses: actions/checkout@v3
with:
ref: master
path: master
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Copy CI gradle.properties
run: |
cd master
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build extensions
uses: eskatos/gradle-command-action@v1
env:
CI_PUSH: "true"
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: 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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ name: Lock threads
on:
# Daily
schedule:
- cron: '0 * * * *'
- cron: '0 0 * * *'
# Manual trigger
workflow_dispatch:
inputs:

View file

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

View file

@ -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.
<details><summary>Steps</summary>
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 <fork-repo-url>
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/<lang>/<source>
# allow a multisrc theme
/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/<source>
/multisrc/overrides/<source>
# or type the source name directly
<source>
```
4. Configure remotes.
```bash
# add upstream
git remote add upstream <aniyomi-repo-url>
# 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).
</details>
## 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/<lang>/<mysourcename>`. Use `all` as `<lang>` if your target source supports multiple languages or if it could support multiple sources.
The `<lang>` used in the folder inside `src` should be the major `language` part. For example, if you will be creating a `pt-BR` source, use `<lang>` 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 = '<lang>.<mysourcename>'
extClass = '.<MySourceName>'
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<V>` | `Int` | A select control, similar to HTML's `<select>`. Only one item can be selected, and the state is the index of the selected one. |
| `Filter.Text` | `String` | A text control, similar to HTML's `<input type="text">`. |
| `Filter.CheckBox` | `Boolean` | A checkbox control, similar to HTML's `<input type="checkbox">`. 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<V>` | `List<V>` | 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<Pair<String, String>>) :
Filter.Select<String>(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 "<your-link>" -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 = <the-id>
```
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
└── <themepkg>
├── 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
└── <sourcepkg>
├── 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
└── <SourceName>.kt
   └── <themepkg>
   ├── 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
   └── <sourcepkg>
   ├── 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
   └── <SourceName>.kt
└── src
└── main
├── AndroidManifest.xml
@ -301,7 +447,7 @@ multisrc
- `multisrc/overrides/<themepkg>/<sourcepkg>` contains overrides for a source that is defined inside the `<Theme>Generator.kt` class.
- `multisrc/overrides/<themepkg>/<sourcepkg>/src` contains source overrides.
- `multisrc/overrides/<themepkg>/<sourcepkg>/res` contains override for icons.
- `multisrc/overrides/<themepkg>/<sourcepkg>/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/<themepkg>/<sourcepkg>/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/<themepkg>/<sourcepkg>/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<X509Certificate> = arrayOf()
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
}
val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
val trustAllCerts = arrayOf<TrustManager>(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

View file

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

View file

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

View file

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

25
build.gradle.kts Normal file
View file

@ -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<Delete>("clean") {
delete(rootProject.buildDir)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,8 @@
<meta-data android:name="tachiyomi.animeextension.class" android:value="${extClass}" />
<meta-data android:name="tachiyomi.animeextension.factory" android:value="${extFactory}" />
<meta-data android:name="tachiyomi.animeextension.nsfw" android:value="${nsfw}" />
<meta-data android:name="tachiyomi.animeextension.hasReadme" android:value="${hasReadme}" />
<meta-data android:name="tachiyomi.animeextension.hasChangelog" android:value="${hasChangelog}" />
</application>

View file

@ -3,11 +3,10 @@ plugins {
}
android {
compileSdkVersion(AndroidConfig.compileSdk)
buildToolsVersion(AndroidConfig.buildTools)
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdkVersion(AndroidConfig.minSdk)
minSdk = AndroidConfig.minSdk
}
sourceSets {

View file

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

32
gradle/libs.versions.toml Normal file
View file

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

Binary file not shown.

View file

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

269
gradlew vendored
View file

@ -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
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
# 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
# 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\""
fi
i=`expr $i + 1`
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" ;;
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
# 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, 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.
# 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"
# For Cygwin or MSYS, switch paths to Windows format before running java
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
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
# 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
fi
# 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.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

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

View file

@ -1,16 +0,0 @@
plugins {
java
}
sourceSets {
main {
java {
srcDirs(listOf("src"))
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View file

@ -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 <T> void set(String name, Class<T> type, T object) {
throw new RuntimeException("Stub!");
}
}

View file

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

View file

@ -1,3 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.lib.ratelimit">
</manifest>

View file

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

View file

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

View file

@ -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<File>? {
return listFiles()
// Lang folder
?.filter { it.isDirectory }
// Extension subfolders
?.mapNotNull { dir -> dir.listFiles()?.filter { it.isDirectory } }
?.flatten()
?.sortedBy { it.name }
?.chunked(chunkSize)
?.get(chunk)
}
fun File.eachDir(block: (File) -> Unit) {
listFiles()?.filter { it.isDirectory }?.forEach { block(it) }
}

View file

@ -10,8 +10,5 @@ ext {
libVersion = '13'
containsNsfw = true
}
dependencies {
implementation project(':lib-ratelimit')
}
apply from: "$rootDir/common.gradle"

View file

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

View file

@ -10,9 +10,5 @@ ext {
libVersion = '13'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
apply from: "$rootDir/common.gradle"

View file

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

View file

@ -10,8 +10,6 @@ ext {
libVersion = '13'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
apply from: "$rootDir/common.gradle"

View file

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

View file

@ -11,8 +11,6 @@ ext {
containsNsfw = true
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
apply from: "$rootDir/common.gradle"

View file

@ -10,9 +10,7 @@ ext {
libVersion = '13'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
apply from: "$rootDir/common.gradle"

View file

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

View file

@ -0,0 +1,12 @@
# Extension name
Table of Content
- [FAQ](#FAQ)
[Uncomment this if needed; and replace &#40; and &#41; with ( and )]: <> (- [Guides]&#40;#Guides&#41;)
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)