├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── 01_report_issue.yml
│ ├── 02_request_source.yml
│ ├── 03_report_url_change.yml
│ ├── 04_report_dead_source.yml
│ ├── 05_request_feature.yml
│ ├── 06_request_meta.yml
│ ├── 07_request_removal.yml
│ └── config.yml
├── pull_request_template.md
├── readme-images
│ └── app-icon.png
├── scripts
│ ├── bump-versions.py
│ ├── commit-repo.sh
│ ├── create-repo.py
│ ├── move-apks.py
│ └── sign-apks.sh
└── workflows
│ ├── batch_close_issues.yml
│ ├── build_pull_request.yml
│ ├── build_push.yml
│ ├── issue_moderator.yml
│ └── lock.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── AndroidConfig.kt
│ ├── Extensions.kt
│ ├── lib-android.gradle.kts
│ ├── lib-kotlin.gradle.kts
│ └── lib-multisrc.gradle.kts
├── common.gradle
├── core
├── AndroidManifest.xml
├── build.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
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ktlintCodeStyle.xml
├── lib-multisrc
├── animestream
│ ├── AndroidManifest.xml
│ ├── build.gradle.kts
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── multisrc
│ │ └── animestream
│ │ ├── AnimeStream.kt
│ │ ├── AnimeStreamFilters.kt
│ │ └── AnimeStreamUrlActivity.kt
├── datalifeengine
│ ├── build.gradle.kts
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── multisrc
│ │ └── datalifeengine
│ │ └── DataLifeEngine.kt
├── dooplay
│ ├── AndroidManifest.xml
│ ├── build.gradle.kts
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── multisrc
│ │ └── dooplay
│ │ ├── DooPlay.kt
│ │ └── DooPlayUrlActivity.kt
├── dopeflix
│ ├── build.gradle.kts
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── multisrc
│ │ └── dopeflix
│ │ ├── DopeFlix.kt
│ │ ├── DopeFlixFilters.kt
│ │ ├── dto
│ │ └── DopeFlixDto.kt
│ │ └── extractors
│ │ └── DopeFlixExtractor.kt
└── zorotheme
│ ├── build.gradle.kts
│ └── src
│ └── eu
│ └── kanade
│ └── tachiyomi
│ └── multisrc
│ └── zorotheme
│ ├── ZoroTheme.kt
│ ├── ZoroThemeFilters.kt
│ └── dto
│ └── ZoroThemeDto.kt
├── lib
├── blogger-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── bloggerextractor
│ │ └── BloggerExtractor.kt
├── burstcloud-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── burstcloudextractor
│ │ ├── BurstCloudExtractor.kt
│ │ └── BurstCloudExtractorDto.kt
├── cda-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── cdaextractor
│ │ └── CdaExtractor.kt
├── chillx-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── chillxextractor
│ │ └── ChillxExtractor.kt
├── cloudflare-interceptor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── cloudflareinterceptor
│ │ └── CloudflareInterceptor.kt
├── cryptoaes
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── cryptoaes
│ │ ├── CryptoAES.kt
│ │ └── Deobfuscator.kt
├── dailymotion-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── dailymotionextractor
│ │ ├── DailymotionDto.kt
│ │ └── DailymotionExtractor.kt
├── dataimage
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── dataimage
│ │ └── DataImageInterceptor.kt
├── dood-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── doodextractor
│ │ └── DoodExtractor.kt
├── fastream-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── fastreamextractor
│ │ └── FastreamExtractor.kt
├── filemoon-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── filemoonextractor
│ │ └── FilemoonExtractor.kt
├── fusevideo-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── fusevideoextractor
│ │ └── FusevideoExtractor.kt
├── gdriveplayer-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── gdriveplayerextractor
│ │ └── GdrivePlayerExtractor.kt
├── gogostream-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── gogostreamextractor
│ │ ├── GogoStreamExtractor.kt
│ │ └── GogoStreamExtractorDto.kt
├── googledrive-episodes
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── googledriveepisodes
│ │ └── GoogleDriveEpisodes.kt
├── googledrive-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── googledriveextractor
│ │ └── GoogleDriveExtractor.kt
├── javcoverfetcher
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── javcoverfetcher
│ │ └── JavCoverFetcher.kt
├── megacloud-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── megacloudextractor
│ │ └── MegaCloudExtractor.kt
├── mixdrop-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── mixdropextractor
│ │ └── MixDropExtractor.kt
├── mp4upload-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── mp4uploadextractor
│ │ └── Mp4uploadExtractor.kt
├── okru-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── okruextractor
│ │ └── OkruExtractor.kt
├── playlist-utils
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── playlistutils
│ │ └── PlaylistUtils.kt
├── sendvid-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── sendvidextractor
│ │ └── SendvidExtractor.kt
├── sibnet-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── sibnetextractor
│ │ └── SibnetExtractor.kt
├── streamdav-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamdavextractor
│ │ └── StreamDavExtractor.kt
├── streamhidevid-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamhidevidextractor
│ │ └── StreamHideVidExtractor.kt
├── streamhub-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamhubextractor
│ │ └── StreamHubExtractor.kt
├── streamlare-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamlareextractor
│ │ └── StreamlareExtractor.kt
├── streamtape-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamtapeextractor
│ │ └── StreamTapeExtractor.kt
├── streamvid-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamvidextractor
│ │ └── StreamVidExtractor.kt
├── streamwish-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── streamwishextractor
│ │ └── StreamWishExtractor.kt
├── synchrony
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── assets
│ │ └── synchrony-v2.4.5.1.js
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── synchrony
│ │ └── Deobfuscator.kt
├── unpacker
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── unpacker
│ │ ├── SubstringExtractor.kt
│ │ └── Unpacker.kt
├── upstream-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── upstreamextractor
│ │ └── UpstreamExtractor.kt
├── uqload-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── uqloadextractor
│ │ └── UqloadExtractor.kt
├── vidbom-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vidbomextractor
│ │ └── VidBomExtractor.kt
├── vidhide-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vidhideextractor
│ │ └── VidHideExtractor.kt
├── vido-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vidoextractor
│ │ └── VidoExtractor.kt
├── vidsrc-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vidsrcextractor
│ │ └── VidSrcExtractor.kt
├── vk-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vkextractor
│ │ └── VkExtractor.kt
├── voe-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── voeextractor
│ │ └── VoeExtractor.kt
├── vudeo-extractor
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── lib
│ │ └── vudeoextractor
│ │ └── VudeoExtractor.kt
└── yourupload-extractor
│ ├── build.gradle.kts
│ └── src
│ └── main
│ └── java
│ └── eu
│ └── kanade
│ └── tachiyomi
│ └── lib
│ └── youruploadextractor
│ └── YourUploadExtractor.kt
├── renovate.json
├── repositories.gradle.kts
├── settings.gradle.kts
├── src
├── all
│ ├── debridindex
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ │ └── eu
│ │ │ └── kanade
│ │ │ └── tachiyomi
│ │ │ └── animeextension
│ │ │ └── all
│ │ │ └── debridindex
│ │ │ ├── DebirdIndexUrlActivity.kt
│ │ │ ├── DebridIndex.kt
│ │ │ └── dto
│ │ │ └── DebridIndexDto.kt
│ ├── hentaitorrent
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ │ └── eu
│ │ │ └── kanade
│ │ │ └── tachiyomi
│ │ │ └── animeextension
│ │ │ └── all
│ │ │ └── hentaitorrent
│ │ │ ├── HentaiTorrent.kt
│ │ │ └── HentaiTorrentUrlActivity.kt
│ ├── nyaatorrent
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ │ └── eu
│ │ │ └── kanade
│ │ │ └── tachiyomi
│ │ │ └── animeextension
│ │ │ └── all
│ │ │ └── nyaatorrent
│ │ │ ├── NyaaFactory.kt
│ │ │ ├── NyaaTorrent.kt
│ │ │ └── NyaaTorrentUrlActivity.kt
│ ├── ptorrent
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ │ └── eu
│ │ │ └── kanade
│ │ │ └── tachiyomi
│ │ │ └── animeextension
│ │ │ └── all
│ │ │ └── ptorrent
│ │ │ ├── PTorrent.kt
│ │ │ └── PTorrentUrlActivity.kt
│ ├── torrentio
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ │ └── eu
│ │ │ └── kanade
│ │ │ └── tachiyomi
│ │ │ └── animeextension
│ │ │ └── all
│ │ │ └── torrentio
│ │ │ ├── Torrentio.kt
│ │ │ ├── TorrentioUrlActivity.kt
│ │ │ └── dto
│ │ │ └── TorrentioDto.kt
│ └── torrentioanime
│ │ ├── AndroidManifest.xml
│ │ ├── build.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
│ │ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── all
│ │ └── torrentioanime
│ │ ├── AniListFilters.kt
│ │ ├── AniListQueries.kt
│ │ ├── Torrentio.kt
│ │ ├── TorrentioUrlActivity.kt
│ │ └── dto
│ │ └── TorrentioDto.kt
└── en
│ ├── hanime
│ ├── build.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_500.png
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── en
│ │ └── hanime
│ │ ├── DataModel.kt
│ │ └── Hanime.kt
│ ├── hentaimama
│ ├── build.gradle
│ ├── ic_launcher-playstore.png
│ ├── 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_500.png
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── en
│ │ └── hentaimama
│ │ └── HentaiMama.kt
│ ├── hstream
│ ├── AndroidManifest.xml
│ ├── build.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
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── en
│ │ └── hstream
│ │ ├── Hstream.kt
│ │ ├── HstreamFilters.kt
│ │ └── HstreamUrlActivity.kt
│ ├── oppaistream
│ ├── build.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
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── en
│ │ └── oppaistream
│ │ ├── OppaiStream.kt
│ │ ├── OppaiStreamFilters.kt
│ │ └── dto
│ │ └── OppaiStreamDto.kt
│ ├── rule34video
│ ├── AndroidManifest.xml
│ ├── build.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_500.png
│ └── src
│ │ └── eu
│ │ └── kanade
│ │ └── tachiyomi
│ │ └── animeextension
│ │ └── en
│ │ └── rule34video
│ │ ├── DdosGuardInterceptor.kt
│ │ ├── Rule34Video.kt
│ │ └── Rule34VideoUrlActivity.kt
│ └── subsplease
│ ├── AndroidManifest.xml
│ ├── build.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
│ └── src
│ └── eu
│ └── kanade
│ └── tachiyomi
│ └── animeextension
│ └── en
│ └── subsplease
│ └── Subsplease.kt
└── template
├── README-REMOVED-TEMPLATE.md
└── README-TEMPLATE.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*.kt]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 4
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | ij_kotlin_allow_trailing_comma = true
12 | ij_kotlin_allow_trailing_comma_on_call_site = true
13 | ktlint_experimental = enabled
14 | ktlint_experimental_argument-list-wrapping = disabled # Doesn't play well with Android Studio
15 | ktlint_experimental_comment-wrapping = disabled
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **PLEASE READ THIS**
2 |
3 | I acknowledge that:
4 |
5 | - I have updated to the latest version of the app (stable is v0.15.2.2)
6 | - I have updated all extensions
7 | - If this is an issue with the app itself, that I should be opening an issue in https://github.com/aniyomiorg/aniyomi
8 | - I have searched the existing issues for duplicates
9 | - For source requests, I have checked the list of existing extensions including the multi-source spreadsheet: https://aniyomi.org/extensions/
10 |
11 | **DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
12 |
13 | ---
14 |
15 | ## Device information
16 | * Aniyomi version: ?
17 | * Android version: ?
18 | * Device: ?
19 |
20 | ## Source information
21 | * Source name: ?
22 | * Source extension version: ?
23 |
24 | ## Steps to reproduce
25 | 1. First step
26 | 2. Second step
27 |
28 | ## Issue/Request
29 | ?
30 |
31 | ## Other details
32 | Additional details and attachments.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/01_report_issue.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Issue report
2 | description: Report a source issue in Aniyomi
3 | labels: [Bug]
4 | body:
5 |
6 | - type: input
7 | id: source
8 | attributes:
9 | label: Source information
10 | description: |
11 | You can find the extension name and version in **Browse → Extensions**.
12 | placeholder: |
13 | Example: "AnimePahe 14.19 (English)"
14 | validations:
15 | required: true
16 |
17 | - type: input
18 | id: language
19 | attributes:
20 | label: Source language
21 | placeholder: |
22 | Example: "English"
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: reproduce-steps
28 | attributes:
29 | label: Steps to reproduce
30 | description: Provide an example of the issue. Be as specific as possible.
31 | placeholder: |
32 | Example:
33 | 1. First step (e.g. "Open Mahouka Koukou No Rettousei (first season)")
34 | 2. Second step (e.g. "Try to watch episode 15")
35 | 3. Issue here (e.g. "It shows a HTTP 403 error toast")
36 | validations:
37 | required: true
38 |
39 | - type: textarea
40 | id: expected-behavior
41 | attributes:
42 | label: Expected behavior
43 | placeholder: |
44 | Example:
45 | "This should happen..."
46 | validations:
47 | required: true
48 |
49 | - type: textarea
50 | id: actual-behavior
51 | attributes:
52 | label: Actual behavior
53 | placeholder: |
54 | Example:
55 | "This happened instead..."
56 | validations:
57 | required: true
58 |
59 | - type: input
60 | id: aniyomi-version
61 | attributes:
62 | label: Aniyomi version
63 | description: |
64 | You can find your Aniyomi version in **More → About**.
65 | placeholder: |
66 | Example: "0.15.2.4" or "Preview r7473"
67 | validations:
68 | required: true
69 |
70 | - type: input
71 | id: android-version
72 | attributes:
73 | label: Android version
74 | description: |
75 | You can find this somewhere in your Android settings.
76 | placeholder: |
77 | Example: "Android 11"
78 | validations:
79 | required: true
80 |
81 | - type: textarea
82 | id: other-details
83 | attributes:
84 | label: Other details
85 | placeholder: |
86 | Additional details and attachments.
87 |
88 | - type: checkboxes
89 | id: acknowledgements
90 | attributes:
91 | label: Acknowledgements
92 | description: Your issue will be closed if you haven't done these steps.
93 | options:
94 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
95 | required: true
96 | - label: I have written a short but informative title.
97 | required: true
98 | - label: I have updated the app to version **[0.15.2.4](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
99 | required: true
100 | - label: I have updated all installed extensions.
101 | required: true
102 | - label: I have tried the [troubleshooting guide](https://aniyomi.org/docs/guides/troubleshooting/).
103 | required: true
104 | - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/aniyomiorg/aniyomi/issues/new/choose).
105 | required: true
106 | - label: I will fill out all of the requested information in this form.
107 | required: true
108 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/02_request_source.yml:
--------------------------------------------------------------------------------
1 | name: 🌐 Source request
2 | description: Suggest a new source for Aniyomi
3 | labels: [Source request]
4 | body:
5 |
6 | - type: input
7 | id: name
8 | attributes:
9 | label: Source name
10 | placeholder: |
11 | Example: "Not Real Source"
12 | validations:
13 | required: true
14 |
15 | - type: input
16 | id: link
17 | attributes:
18 | label: Source link
19 | placeholder: |
20 | Example: "https://notrealsource.org"
21 | validations:
22 | required: true
23 |
24 | - type: input
25 | id: language
26 | attributes:
27 | label: Source language
28 | placeholder: |
29 | Example: "English"
30 | validations:
31 | required: true
32 |
33 | - type: textarea
34 | id: other-details
35 | attributes:
36 | label: Other details
37 | placeholder: |
38 | Additional details and attachments.
39 | Example: "+18/NSFW = yes"
40 |
41 | - type: checkboxes
42 | id: acknowledgements
43 | attributes:
44 | label: Acknowledgements
45 | description: Your issue will be closed if you haven't done these steps.
46 | options:
47 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
48 | required: true
49 | - label: I have written a title with source name.
50 | required: true
51 | - label: I have checked that the extension does not already exist on the [website extensions list](https://aniyomi.org/extensions/) or the app.
52 | required: true
53 | - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/aniyomiorg/aniyomi-extensions/) and verified it does not appear in the code base.
54 | required: true
55 | - label: I will fill out all of the requested information in this form.
56 | required: true
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/03_report_url_change.yml:
--------------------------------------------------------------------------------
1 | name: 🔗 URL change report
2 | description: Report URL change of an existing source
3 | labels: [Bug,Domain changed]
4 | body:
5 |
6 | - type: input
7 | id: source
8 | attributes:
9 | label: Source information
10 | description: |
11 | You can find the extension name and version in **Browse → Extensions**.
12 | placeholder: |
13 | Example: "NotRealSource 14.1"
14 | validations:
15 | required: true
16 |
17 | - type: input
18 | id: language
19 | attributes:
20 | label: Source language
21 | placeholder: |
22 | Example: "English"
23 | validations:
24 | required: true
25 |
26 | - type: input
27 | id: link
28 | attributes:
29 | label: Source new URL
30 | placeholder: |
31 | Example: "https://notrealsource.org"
32 | validations:
33 | required: true
34 |
35 | - type: textarea
36 | id: other-details
37 | attributes:
38 | label: Other details
39 | placeholder: |
40 | Additional details and attachments.
41 |
42 | - type: checkboxes
43 | id: acknowledgements
44 | attributes:
45 | label: Acknowledgements
46 | description: Your issue will be closed if you haven't done these steps.
47 | options:
48 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
49 | required: true
50 | - label: I have written a short but informative title.
51 | required: true
52 | - label: I have updated all installed extensions.
53 | required: true
54 | - label: I have opened WebView and checked that the source URL is not updated yet.
55 | required: true
56 | - label: I will fill out all of the requested information in this form.
57 | required: true
58 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/04_report_dead_source.yml:
--------------------------------------------------------------------------------
1 | name: ❌ Dead source report
2 | description: Source is down and website is closed
3 | labels: [Source is down]
4 | body:
5 |
6 | - type: markdown
7 | attributes:
8 | value: |
9 | ### Notice
10 | If you have a lot of dead sources to report, please go back and submit a single meta request.
11 |
12 | - type: input
13 | id: source
14 | attributes:
15 | label: Source name
16 | description: |
17 | You can find the extension name in **Browse → Extensions**.
18 | placeholder: |
19 | Example: "NotRealSource"
20 | validations:
21 | required: true
22 |
23 | - type: input
24 | id: language
25 | attributes:
26 | label: Source language
27 | placeholder: |
28 | Example: "English"
29 | validations:
30 | required: true
31 |
32 | - type: input
33 | id: link
34 | attributes:
35 | label: Source link
36 | placeholder: |
37 | Example: "https://notrealsource.org"
38 | validations:
39 | required: true
40 |
41 | - type: textarea
42 | id: other-details
43 | attributes:
44 | label: Other details
45 | placeholder: |
46 | Additional details and attachments.
47 |
48 | - type: checkboxes
49 | id: acknowledgements
50 | attributes:
51 | label: Acknowledgements
52 | description: Your issue will be closed if you haven't done these steps.
53 | options:
54 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
55 | required: true
56 | - label: I have written a title with source name.
57 | required: true
58 | - label: I have updated all installed extensions.
59 | required: true
60 | - label: I have opened WebView and checked that the source website is down.
61 | required: true
62 | - label: I will fill out all of the requested information in this form.
63 | required: true
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/05_request_feature.yml:
--------------------------------------------------------------------------------
1 | name: ⭐ Feature request
2 | description: Suggest a feature to improve an existing source
3 | labels: [Feature request]
4 | body:
5 |
6 | - type: input
7 | id: source
8 | attributes:
9 | label: Source name
10 | description: |
11 | You can find the extension name in **Browse → Extensions**.
12 | placeholder: |
13 | Example: "DopeBox"
14 | validations:
15 | required: true
16 |
17 | - type: input
18 | id: language
19 | attributes:
20 | label: Source language
21 | placeholder: |
22 | Example: "English"
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: feature-description
28 | attributes:
29 | label: Describe your suggested feature
30 | description: How can an existing extension be improved?
31 | placeholder: |
32 | Example:
33 | "It should work like this..."
34 | validations:
35 | required: true
36 |
37 | - type: textarea
38 | id: other-details
39 | attributes:
40 | label: Other details
41 | placeholder: |
42 | Additional details and attachments.
43 |
44 | - type: checkboxes
45 | id: acknowledgements
46 | attributes:
47 | label: Acknowledgements
48 | description: Your issue will be closed if you haven't done these steps.
49 | options:
50 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
51 | required: true
52 | - label: I have written a short but informative title.
53 | required: true
54 | - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/aniyomiorg/aniyomi/issues/new/choose).
55 | required: true
56 | - label: I have updated the app to version **[0.15.2.4](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
57 | required: true
58 | - label: I will fill out all of the requested information in this form.
59 | required: true
60 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/06_request_meta.yml:
--------------------------------------------------------------------------------
1 | name: 🧠 Meta request
2 | description: Suggest improvements to the project
3 | labels: [Meta request]
4 | body:
5 |
6 | - type: textarea
7 | id: feature-description
8 | attributes:
9 | label: Describe why this should be added
10 | description: How can the project be improved?
11 | placeholder: |
12 | Example:
13 | "It should work like this..."
14 | validations:
15 | required: true
16 |
17 | - type: textarea
18 | id: other-details
19 | attributes:
20 | label: Other details
21 | placeholder: |
22 | Additional details and attachments.
23 |
24 | - type: checkboxes
25 | id: acknowledgements
26 | attributes:
27 | label: Acknowledgements
28 | description: Your issue will be closed if you haven't done these steps.
29 | options:
30 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open (or closed) issue.
31 | required: true
32 | - label: I have written a short but informative title.
33 | required: true
34 | - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/aniyomiorg/aniyomi/issues/new/choose).
35 | required: true
36 | - label: I have updated the app to version **[0.15.2.4](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
37 | required: true
38 | - label: I have updated all installed extensions.
39 | required: true
40 | - label: I will fill out all of the requested information in this form.
41 | required: true
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/07_request_removal.yml:
--------------------------------------------------------------------------------
1 | name: 🗑 Source removal request
2 | description: Scanlators can request their site to be removed
3 | labels: [Meta request]
4 | body:
5 |
6 | - type: input
7 | id: link
8 | attributes:
9 | label: Source link
10 | placeholder: |
11 | Example: "https://notrealscans.org"
12 | validations:
13 | required: true
14 |
15 | - type: textarea
16 | id: other-details
17 | attributes:
18 | label: Other details
19 | placeholder: |
20 | Additional details and attachments.
21 |
22 | - type: checkboxes
23 | id: requirements
24 | attributes:
25 | label: Requirements
26 | description: Your request will be denied if you don't meet these requirements.
27 | options:
28 | - label: Proof of ownership/intent to remove sent to a Aniyomi Discord server mod via DM
29 | required: true
30 | - label: Site only hosts content scanlated by the group and not stolen from other scanlators or official releases (i.e., not an aggregator site)
31 | required: true
32 | - label: Site is not infested with user-hostile features (e.g., invasive or malicious ads)
33 | required: true
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: ⚠️ Application issue
4 | url: https://github.com/aniyomiorg/aniyomi/issues/new/choose
5 | about: Issues and requests about the app itself should be opened in the tachiyomi repository instead
6 | - name: 📦 Aniyomi extensions
7 | url: https://aniyomi.org/extensions
8 | about: List of all available extensions with download links
9 | - name: 🖥️ Aniyomi website
10 | url: https://aniyomi.org/help/
11 | about: Guides, troubleshooting, and answers to common questions
12 | - name: Aniyomi app GitHub repository
13 | url: https://github.com/aniyomiorg/aniyomi
14 | about: Issues about the app itself should be opened here instead.
15 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Checklist:
2 |
3 | - [ ] Updated `extVersionCode` value in `build.gradle` for individual extensions
4 | - [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
5 | - [ ] Referenced all related issues in the PR body (e.g. "Closes #xyz")
6 | - [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
7 | - [ ] Have not changed source names
8 | - [ ] Have explicitly kept the `id` if a source's name or language were changed
9 | - [ ] Have tested the modifications by compiling and running the extension through Android Studio
10 | - [ ] Have removed `web_hi_res_512.png` when adding a new extension
11 |
--------------------------------------------------------------------------------
/.github/readme-images/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/.github/readme-images/app-icon.png
--------------------------------------------------------------------------------
/.github/scripts/bump-versions.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from concurrent.futures import Future, ThreadPoolExecutor, as_completed
4 | import itertools
5 | from pathlib import Path
6 | import re
7 | import subprocess
8 | import sys
9 |
10 | VERSION_STR = "VersionCode ="
11 | VERSION_REGEX = re.compile(f"{VERSION_STR} (\\d+)")
12 | BUMPED_FILES: list[Path] = []
13 |
14 | BOT_EMAIL = "aniyomi-bot@aniyomi.org"
15 | BOT_NAME = "aniyomi-bot[bot]"
16 |
17 | def has_match(query: str, file: Path) -> tuple[Path, bool]:
18 | return (file, query in file.read_text())
19 |
20 | def find_files_with_match(query: str, include_multisrc: bool = True) -> list[Path]:
21 | files = Path("src").glob("*/*/build.gradle")
22 | if include_multisrc:
23 | files = itertools.chain(files, Path("lib-multisrc").glob("*/build.gradle.kts"))
24 |
25 | # Prevent bumping files twice.
26 | files = filter(lambda file: file not in BUMPED_FILES, files)
27 |
28 | # Use multiple threads to find matches.
29 | with ThreadPoolExecutor() as executor:
30 | futures = [executor.submit(has_match, query, file) for file in files]
31 | results = map(Future.result, as_completed(futures))
32 | return [path for path, result in results if result]
33 |
34 | def replace_version(match: re.Match) -> str:
35 | version = int(match.group(1))
36 | print(f"{version} -> {version + 1}")
37 | return f"{VERSION_STR} {version + 1}"
38 |
39 | def bump_version(file: Path):
40 | BUMPED_FILES.append(file)
41 | with file.open("r+") as f:
42 | print(f"\n{file}: ", end="")
43 | text = VERSION_REGEX.sub(replace_version, f.read())
44 | # Move the cursor to the start again, to prevent writing at the end
45 | f.seek(0)
46 | f.write(text)
47 |
48 | def bump_lib_multisrc(theme: str):
49 | for file in find_files_with_match(f"themePkg = '{theme}'", include_multisrc=False):
50 | bump_version(file)
51 |
52 | def commit_changes():
53 | paths = [str(path.resolve()) for path in BUMPED_FILES]
54 | subprocess.check_call(["git", "config", "--local", "user.email", BOT_EMAIL])
55 | subprocess.check_call(["git", "config", "--local", "user.name", BOT_NAME])
56 | subprocess.check_call(["git", "add"] + paths)
57 | subprocess.check_call(["git", "commit", "-S", "-m", "[skip ci] chore: Mass-bump on extensions"])
58 | subprocess.check_call(["git", "push"])
59 |
60 | if __name__ == "__main__":
61 | if len(sys.argv) > 1:
62 | # Regex to match the lib name in the path, like "unpacker" or "dood-extractor".
63 | lib_regex = re.compile(r"lib/([a-z0-9-]+)/")
64 | # Find matches and remove None results.
65 | matches = filter(None, map(lib_regex.search, sys.argv[1:]))
66 | for match in matches:
67 | project_path = ":lib:" + match.group(1)
68 | for file in find_files_with_match(project_path):
69 | if file.parent.parent.name == "lib-multisrc":
70 | bump_lib_multisrc(file.parent.name)
71 | else:
72 | bump_version(file)
73 |
74 | if len(BUMPED_FILES) > 0:
75 | commit_changes()
76 |
--------------------------------------------------------------------------------
/.github/scripts/commit-repo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | rsync -a --delete --exclude .git --exclude .gitignore --exclude repo.json ../master/repo/ .
5 | git config --global user.email "aniyomi-bot@aniyomi.org"
6 | git config --global user.name "aniyomi-bot[bot]"
7 | git status
8 | if [ -n "$(git status --porcelain)" ]; then
9 | git add .
10 | git commit -S -m "Update extensions repo"
11 | git push
12 |
13 | # Purge cached index on jsDelivr
14 | curl https://purge.jsdelivr.net/gh/ni3x/aniyomi-extensions@repo/index.min.json
15 | else
16 | echo "No changes to commit"
17 | fi
18 |
--------------------------------------------------------------------------------
/.github/scripts/create-repo.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import re
4 | import subprocess
5 | from pathlib import Path
6 | from zipfile import ZipFile
7 |
8 | PACKAGE_NAME_REGEX = re.compile(r"package: name='([^']+)'")
9 | VERSION_CODE_REGEX = re.compile(r"versionCode='([^']+)'")
10 | VERSION_NAME_REGEX = re.compile(r"versionName='([^']+)'")
11 | IS_NSFW_REGEX = re.compile(r"'tachiyomi.animeextension.nsfw' value='([^']+)'")
12 | APPLICATION_LABEL_REGEX = re.compile(r"^application-label:'([^']+)'", re.MULTILINE)
13 | APPLICATION_ICON_320_REGEX = re.compile(
14 | r"^application-icon-320:'([^']+)'", re.MULTILINE
15 | )
16 | LANGUAGE_REGEX = re.compile(r"aniyomi-([^\.]+)")
17 |
18 | *_, ANDROID_BUILD_TOOLS = (Path(os.environ["ANDROID_HOME"]) / "build-tools").iterdir()
19 | REPO_DIR = Path("repo")
20 | REPO_APK_DIR = REPO_DIR / "apk"
21 | REPO_ICON_DIR = REPO_DIR / "icon"
22 |
23 | REPO_ICON_DIR.mkdir(parents=True, exist_ok=True)
24 |
25 | with open("output.json", encoding="utf-8") as f:
26 | inspector_data = json.load(f)
27 |
28 | index_data = []
29 | index_min_data = []
30 |
31 | for apk in REPO_APK_DIR.iterdir():
32 | badging = subprocess.check_output(
33 | [
34 | ANDROID_BUILD_TOOLS / "aapt",
35 | "dump",
36 | "--include-meta-data",
37 | "badging",
38 | apk,
39 | ]
40 | ).decode()
41 |
42 | package_info = next(x for x in badging.splitlines() if x.startswith("package: "))
43 | package_name = PACKAGE_NAME_REGEX.search(package_info).group(1)
44 | application_icon = APPLICATION_ICON_320_REGEX.search(badging).group(1)
45 |
46 | with ZipFile(apk) as z, z.open(application_icon) as i, (
47 | REPO_ICON_DIR / f"{package_name}.png"
48 | ).open("wb") as f:
49 | f.write(i.read())
50 |
51 | language = LANGUAGE_REGEX.search(apk.name).group(1)
52 | sources = inspector_data[package_name]
53 |
54 | if len(sources) == 1:
55 | source_language = sources[0]["lang"]
56 |
57 | if (
58 | source_language != language
59 | and source_language not in {"all", "other"}
60 | and language not in {"all", "other"}
61 | ):
62 | language = source_language
63 |
64 | common_data = {
65 | "name": APPLICATION_LABEL_REGEX.search(badging).group(1),
66 | "pkg": package_name,
67 | "apk": apk.name,
68 | "lang": language,
69 | "code": int(VERSION_CODE_REGEX.search(package_info).group(1)),
70 | "version": VERSION_NAME_REGEX.search(package_info).group(1),
71 | "nsfw": int(IS_NSFW_REGEX.search(badging).group(1)),
72 | }
73 | min_data = {
74 | **common_data,
75 | "sources": [],
76 | }
77 |
78 | for source in sources:
79 | min_data["sources"].append(
80 | {
81 | "name": source["name"],
82 | "lang": source["lang"],
83 | "id": source["id"],
84 | "baseUrl": source["baseUrl"],
85 | }
86 | )
87 |
88 | index_min_data.append(min_data)
89 | index_data.append(
90 | {
91 | **common_data,
92 | "hasReadme": 0,
93 | "hasChangelog": 0,
94 | "sources": sources,
95 | }
96 | )
97 |
98 | index_data.sort(key=lambda x: x["pkg"])
99 | index_min_data.sort(key=lambda x: x["pkg"])
100 |
101 | with (REPO_DIR / "index.json").open("w", encoding="utf-8") as f:
102 | index_data_str = json.dumps(index_data, ensure_ascii=False, indent=2)
103 |
104 | print(index_data_str)
105 | f.write(index_data_str)
106 |
107 | with (REPO_DIR / "index.min.json").open("w", encoding="utf-8") as f:
108 | json.dump(index_min_data, f, ensure_ascii=False, separators=(",", ":"))
109 |
--------------------------------------------------------------------------------
/.github/scripts/move-apks.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import shutil
3 |
4 | REPO_APK_DIR = Path("repo/apk")
5 |
6 | try:
7 | shutil.rmtree(REPO_APK_DIR)
8 | except FileNotFoundError:
9 | pass
10 |
11 | REPO_APK_DIR.mkdir(parents=True, exist_ok=True)
12 |
13 | for apk in (Path.home() / "apk-artifacts").glob("**/*.apk"):
14 | apk_name = apk.name.replace("-release.apk", ".apk")
15 |
16 | shutil.move(apk, REPO_APK_DIR / apk_name)
17 |
--------------------------------------------------------------------------------
/.github/scripts/sign-apks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
5 |
6 | shopt -s globstar nullglob extglob
7 | APKS=( **/*".apk" )
8 |
9 | # Fail if too little extensions seem to have been built
10 | if [ "${#APKS[@]}" -le "1" ]; then
11 | echo "Insufficient amount of APKs found. Please check the project configuration."
12 | exit 1;
13 | fi;
14 |
15 | # Take base64 encoded key input and put it into a file
16 | STORE_PATH=$PWD/signingkey.jks
17 | rm -f $STORE_PATH && touch $STORE_PATH
18 | echo $1 | base64 -d > $STORE_PATH
19 |
20 | STORE_ALIAS=$2
21 | export KEY_STORE_PASSWORD=$3
22 | export KEY_PASSWORD=$4
23 |
24 | DEST=$PWD/apk
25 | rm -rf $DEST && mkdir -p $DEST
26 |
27 | MAX_PARALLEL=4
28 |
29 | # Sign all of the APKs
30 | for APK in ${APKS[@]}; do
31 | (
32 | BASENAME=$(basename $APK)
33 | APKNAME="${BASENAME%%+(-release*)}.apk"
34 | APKDEST="$DEST/$APKNAME"
35 |
36 | ${TOOLS}/zipalign -c -v -p 4 $APK
37 |
38 | cp $APK $APKDEST
39 | ${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:KEY_STORE_PASSWORD --key-pass env:KEY_PASSWORD $APKDEST
40 | ) &
41 |
42 | # Allow to execute up to $MAX_PARALLEL jobs in parallel
43 | if [[ $(jobs -r -p | wc -l) -ge $MAX_PARALLEL ]]; then
44 | wait -n
45 | fi
46 | done
47 |
48 | wait
49 |
50 | rm $STORE_PATH
51 | unset KEY_STORE_PASSWORD
52 | unset KEY_PASSWORD
53 |
--------------------------------------------------------------------------------
/.github/workflows/batch_close_issues.yml:
--------------------------------------------------------------------------------
1 | name: "Batch close stale issues"
2 |
3 | on:
4 | # Monthly
5 | schedule:
6 | - cron: '0 0 1 * *'
7 | # Manual trigger
8 | workflow_dispatch:
9 | inputs:
10 |
11 | jobs:
12 | stale:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
16 | with:
17 | repo-token: ${{ secrets.GITHUB_TOKEN }}
18 | # Close everything older than ~6 months
19 | days-before-issue-stale: 180
20 | days-before-issue-close: 0
21 | exempt-issue-labels: "do-not-autoclose,Meta request"
22 | close-issue-message: "In an effort to have a more manageable issue backlog, we're closing older requests that weren't addressed since there's a low chance of it being addressed if it hasn't already. If your request is still relevant, please [open a new request](https://github.com/aniyomiorg/aniyomi-extensions/issues/new/choose)."
23 | close-issue-reason: not_planned
24 | ascending: true
25 | operations-per-run: 250
26 |
--------------------------------------------------------------------------------
/.github/workflows/build_pull_request.yml:
--------------------------------------------------------------------------------
1 | name: PR build check
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '**'
7 | - '!**.md'
8 | - '!.github/**'
9 | - '.github/workflows/build_pull_request.yml'
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
13 | cancel-in-progress: true
14 |
15 | env:
16 | CI_CHUNK_SIZE: 65
17 |
18 | jobs:
19 | prepare:
20 | name: Prepare job
21 | runs-on: ubuntu-latest
22 | outputs:
23 | individualMatrix: ${{ steps.generate-matrices.outputs.individualMatrix }}
24 | steps:
25 | - name: Clone repo
26 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
27 |
28 | - name: Validate Gradle Wrapper
29 | uses: gradle/wrapper-validation-action@a494d935f4b56874c4a5a87d19af7afcf3a163d0 # v2
30 |
31 | - name: Get number of modules
32 | run: |
33 | set -x
34 | projects=(src/*/*)
35 |
36 | echo "NUM_INDIVIDUAL_MODULES=${#projects[@]}" >> $GITHUB_ENV
37 |
38 | - id: generate-matrices
39 | name: Create output matrices
40 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
41 | with:
42 | script: |
43 | const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES;
44 | const chunkSize = process.env.CI_CHUNK_SIZE;
45 |
46 | const numIndividualChunks = Math.ceil(numIndividualModules / chunkSize);
47 |
48 | console.log(`Individual modules: ${numIndividualModules} (${numIndividualChunks} chunks of ${chunkSize})`);
49 |
50 | core.setOutput('individualMatrix', { 'chunk': [...Array(numIndividualChunks).keys()] });
51 |
52 | build_individual:
53 | name: Build individual modules
54 | needs: prepare
55 | runs-on: ubuntu-latest
56 | strategy:
57 | matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }}
58 | steps:
59 | - name: Checkout PR
60 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
61 |
62 | - name: Set up JDK
63 | uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
64 | with:
65 | java-version: 17
66 | distribution: temurin
67 |
68 | - name: Set up Gradle
69 | uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3
70 | with:
71 | cache-read-only: true
72 |
73 | - name: Build extensions (chunk ${{ matrix.chunk }})
74 | env:
75 | CI_CHUNK_NUM: ${{ matrix.chunk }}
76 | run: ./gradlew -p src assembleDebug
77 |
--------------------------------------------------------------------------------
/.github/workflows/issue_moderator.yml:
--------------------------------------------------------------------------------
1 | name: Issue moderator
2 |
3 | on:
4 | issues:
5 | types: [opened, edited, reopened]
6 | issue_comment:
7 | types: [created]
8 |
9 | jobs:
10 | autoclose:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Moderate issues
14 | uses: aniyomiorg/issue-moderator-action@v2
15 | with:
16 | repo-token: ${{ secrets.GITHUB_TOKEN }}
17 | duplicate-label: Duplicate
18 |
19 | duplicate-check-enabled: true
20 | duplicate-check-labels: |
21 | ["Source request", "Domain changed"]
22 |
23 | existing-check-enabled: true
24 | existing-check-labels: |
25 | ["Source request", "Domain changed"]
26 |
27 | auto-close-rules: |
28 | [
29 | {
30 | "type": "body",
31 | "regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
32 | "message": "The acknowledgment section was not removed."
33 | },
34 | {
35 | "type": "body",
36 | "regex": ".*\\* (Aniyomi version|Android version|Device): \\?.*",
37 | "message": "Requested information in the template was not filled out."
38 | },
39 | {
40 | "type": "title",
41 | "regex": ".*(Source name|Short description).*",
42 | "message": "You did not fill out the description in the title."
43 | },
44 | {
45 | "type": "both",
46 | "regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?().configureEach {
17 | kotlinOptions {
18 | jvmTarget = JavaVersion.VERSION_1_8.toString()
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | dependencies {
6 | implementation(libs.gradle.agp)
7 | implementation(libs.gradle.kotlin)
8 | implementation(libs.gradle.kotlin.serialization)
9 | implementation(libs.gradle.kotlinter)
10 | }
11 |
--------------------------------------------------------------------------------
/buildSrc/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | apply(from = "../repositories.gradle.kts")
2 |
3 | dependencyResolutionManagement {
4 | versionCatalogs {
5 | create("libs") {
6 | from(files("../gradle/libs.versions.toml"))
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/AndroidConfig.kt:
--------------------------------------------------------------------------------
1 | object AndroidConfig {
2 | const val compileSdk = 32
3 | const val minSdk = 21
4 | const val targetSdk = 32
5 | const val namespace = "eu.kanade.tachiyomi.animeextension"
6 | const val coreNamespace = "eu.kanade.tachiyomi.lib.core"
7 | const val multisrcNamespace = "eu.kanade.tachiyomi.lib.themesources"
8 | }
9 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Extensions.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.plugins.ExtensionAware
2 | import org.gradle.kotlin.dsl.extra
3 |
4 | var ExtensionAware.baseVersionCode: Int
5 | get() = extra.get("baseVersionCode") as Int
6 | set(value) = extra.set("baseVersionCode", value)
7 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/lib-android.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | kotlin("android")
4 | id("kotlinx-serialization")
5 | }
6 |
7 | android {
8 | compileSdk = AndroidConfig.compileSdk
9 |
10 | defaultConfig {
11 | minSdk = AndroidConfig.minSdk
12 | }
13 |
14 | namespace = "eu.kanade.tachiyomi.lib.${name.replace("-", "")}"
15 | }
16 |
17 | versionCatalogs
18 | .named("libs")
19 | .findBundle("common")
20 | .ifPresent { common ->
21 | dependencies {
22 | compileOnly(common)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/lib-kotlin.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | kotlin("jvm")
4 | }
5 |
6 | versionCatalogs
7 | .named("libs")
8 | .findLibrary("kotlin-stdlib")
9 | .ifPresent { stdlib ->
10 | dependencies {
11 | compileOnly(stdlib)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/lib-multisrc.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | kotlin("android")
4 | id("kotlinx-serialization")
5 | id("org.jmailen.kotlinter")
6 | }
7 |
8 | android {
9 | compileSdk = AndroidConfig.compileSdk
10 |
11 | defaultConfig {
12 | minSdk = AndroidConfig.minSdk
13 | }
14 |
15 | namespace = "eu.kanade.tachiyomi.multisrc.${project.name}"
16 |
17 | sourceSets {
18 | named("main") {
19 | manifest.srcFile("AndroidManifest.xml")
20 | java.setSrcDirs(listOf("src"))
21 | res.setSrcDirs(listOf("res"))
22 | assets.setSrcDirs(listOf("assets"))
23 | }
24 | }
25 |
26 | buildFeatures {
27 | resValues = false
28 | shaders = false
29 | }
30 |
31 | kotlinOptions {
32 | freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
33 | }
34 | }
35 |
36 | versionCatalogs
37 | .named("libs")
38 | .findBundle("common")
39 | .ifPresent { common ->
40 | dependencies {
41 | compileOnly(common)
42 | }
43 | }
44 |
45 | tasks {
46 | preBuild {
47 | dependsOn(lintKotlin)
48 | }
49 |
50 | if (System.getenv("CI") != "true") {
51 | lintKotlin {
52 | dependsOn(formatKotlin)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/common.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlinx-serialization'
4 | apply plugin: 'org.jmailen.kotlinter'
5 |
6 | assert !ext.has("pkgNameSuffix")
7 | assert !ext.has("libVersion")
8 |
9 | assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
10 |
11 | Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
12 | if (theme != null) evaluationDependsOn(theme.path)
13 |
14 | android {
15 | compileSdk 32
16 | namespace AndroidConfig.namespace
17 |
18 | sourceSets {
19 | main {
20 | manifest.srcFile "AndroidManifest.xml"
21 | java.srcDirs = ['src']
22 | res.srcDirs = ['res']
23 | }
24 | release {
25 | manifest.srcFile "AndroidManifest.xml"
26 | }
27 | debug {
28 | manifest.srcFile "AndroidManifest.xml"
29 | }
30 | }
31 |
32 | defaultConfig {
33 | minSdkVersion AndroidConfig.minSdk
34 | targetSdkVersion AndroidConfig.targetSdk
35 | applicationIdSuffix project.parent.name + "." + project.name
36 | versionCode theme == null ? extVersionCode : theme.baseVersionCode + overrideVersionCode
37 | versionName "14.$versionCode"
38 | base {
39 | archivesName = "aniyomi-$applicationIdSuffix-v$versionName"
40 | }
41 | assert extClass.startsWith(".")
42 | manifestPlaceholders = [
43 | appName : "Aniyomi: $extName",
44 | extClass: extClass,
45 | nsfw : project.ext.find("isNsfw") ? 1 : 0,
46 | ]
47 | String baseUrl = project.ext.find("baseUrl") ?: ""
48 | if (theme != null && !baseUrl.isEmpty()) {
49 | def split = baseUrl.split("://")
50 | assert split.length == 2
51 | def path = split[1].split("/")
52 | manifestPlaceholders += [
53 | SOURCEHOST : path[0],
54 | SOURCESCHEME: split[0],
55 | ]
56 | }
57 |
58 | }
59 |
60 | signingConfigs {
61 | release {
62 | storeFile rootProject.file("signingkey.jks")
63 | storePassword System.getenv("KEY_STORE_PASSWORD")
64 | keyAlias System.getenv("ALIAS")
65 | keyPassword System.getenv("KEY_PASSWORD")
66 | }
67 | }
68 |
69 | buildTypes {
70 | release {
71 | signingConfig signingConfigs.release
72 | minifyEnabled false
73 | }
74 | }
75 |
76 | dependenciesInfo {
77 | includeInApk = false
78 | }
79 |
80 | buildFeatures {
81 | // Disable unused AGP features
82 | aidl false
83 | renderScript false
84 | resValues false
85 | shaders false
86 | buildConfig true
87 | }
88 |
89 | compileOptions {
90 | sourceCompatibility = JavaVersion.VERSION_1_8
91 | targetCompatibility = JavaVersion.VERSION_1_8
92 | }
93 |
94 | kotlinOptions {
95 | jvmTarget = JavaVersion.VERSION_1_8.toString()
96 | freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
97 | }
98 | }
99 |
100 | dependencies {
101 | if (theme != null) implementation(theme) // Overrides core launcher icons
102 | implementation(project(":core"))
103 | compileOnly(libs.bundles.common)
104 | }
105 |
106 | configurations.all {
107 | resolutionStrategy.eachDependency { DependencyResolveDetails details ->
108 | if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name == 'kotlin-stdlib-jdk8' && details.requested.version == '1.7.0') {
109 | details.useVersion(libs.versions.kotlin.version.get())
110 | details.because 'Fix problems with dev.datlag JsUnpacker'
111 | }
112 | }
113 | }
114 |
115 | tasks.register("writeManifestFile") {
116 | doLast {
117 | def manifest = android.sourceSets.getByName("main").manifest
118 | if (!manifest.srcFile.exists()) {
119 | File tempFile = layout.buildDirectory.get().file("tempAndroidManifest.xml").getAsFile()
120 | if (!tempFile.exists()) {
121 | tempFile.withWriter {
122 | it.write('\n\n')
123 | }
124 | }
125 | manifest.srcFile(tempFile.path)
126 | }
127 | }
128 | }
129 |
130 | preBuild.dependsOn(writeManifestFile, lintKotlin)
131 | if (System.getenv("CI") != "true") {
132 | lintKotlin.dependsOn(formatKotlin)
133 | }
134 |
--------------------------------------------------------------------------------
/core/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | }
4 |
5 | android {
6 | compileSdk = 32
7 | namespace = AndroidConfig.coreNamespace
8 |
9 | defaultConfig {
10 | minSdk = AndroidConfig.minSdk
11 | }
12 |
13 | sourceSets {
14 | named("main") {
15 | manifest.srcFile("AndroidManifest.xml")
16 | res.setSrcDirs(listOf("res"))
17 | }
18 | }
19 |
20 | libraryVariants.all {
21 | generateBuildConfigProvider?.configure {
22 | enabled = false
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/core/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/core/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/core/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/core/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/core/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx5120m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | org.gradle.parallel=true
18 | org.gradle.workers.max=5
19 |
20 | org.gradle.caching=true
21 |
22 | # Enable AndroidX dependencies
23 | android.useAndroidX=true
24 | android.nonTransitiveRClass=false
25 | android.nonFinalResIds=false
26 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp_version = "8.2.1"
3 | coroutines_version = "1.7.1"
4 | kotlin_version = "1.8.22"
5 | serialization_version = "1.5.1"
6 |
7 | [libraries]
8 | gradle-agp = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
9 | gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" }
10 | gradle-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" }
11 | gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.15.0" }
12 |
13 | aniyomi-lib = { module = "com.github.Diegopyl1209:extensions-lib", version = "bc006c6ba6" }
14 |
15 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin_version" }
16 | kotlin-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization_version" }
17 | kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" }
18 |
19 | coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" }
20 | coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
21 |
22 | injekt = "com.github.mihonapp:injekt:91edab2317"
23 | rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
24 | jsoup = { module = "org.jsoup:jsoup", version = "1.16.1" }
25 | okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
26 | quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" }
27 |
28 | [bundles]
29 | common = ["kotlin-stdlib", "injekt", "rxjava", "kotlin-protobuf", "kotlin-json", "jsoup", "okhttp", "aniyomi-lib", "quickjs", "coroutines-core", "coroutines-android"]
30 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ni3x/aniyomi-extensions/485bcbe0dd990b31080cbaaee2e2ccbe10c10642/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/lib-multisrc/animestream/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lib-multisrc/animestream/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-multisrc")
3 | }
4 |
5 | baseVersionCode = 3
6 |
--------------------------------------------------------------------------------
/lib-multisrc/animestream/src/eu/kanade/tachiyomi/multisrc/animestream/AnimeStreamFilters.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.multisrc.animestream
2 |
3 | import eu.kanade.tachiyomi.animesource.model.AnimeFilter
4 | import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
5 | import org.jsoup.select.Elements
6 |
7 | object AnimeStreamFilters {
8 | open class QueryPartFilter(
9 | displayName: String,
10 | val vals: Array>,
11 | ) : AnimeFilter.Select(
12 | displayName,
13 | vals.map { it.first }.toTypedArray(),
14 | ) {
15 | fun toQueryPart() = vals[state].second
16 | }
17 |
18 | open class CheckBoxFilterList(name: String, val pairs: Array>) :
19 | AnimeFilter.Group(name, pairs.map { CheckBoxVal(it.first, false) })
20 |
21 | private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
22 |
23 | inline fun AnimeFilterList.asQueryPart(): String {
24 | return (getFirst() as QueryPartFilter).toQueryPart()
25 | }
26 |
27 | inline fun AnimeFilterList.getFirst(): R {
28 | return first { it is R } as R
29 | }
30 |
31 | inline fun AnimeFilterList.parseCheckbox(
32 | options: Array>,
33 | name: String,
34 | ): String {
35 | return (getFirst() as CheckBoxFilterList).state
36 | .filter { it.state }
37 | .map { checkbox -> options.find { it.first == checkbox.name }!!.second }
38 | .filter(String::isNotBlank)
39 | .joinToString("&") { "$name[]=$it" }
40 | }
41 |
42 | internal class GenresFilter(name: String) : CheckBoxFilterList(name, GENRES_LIST)
43 | internal class SeasonFilter(name: String) : CheckBoxFilterList(name, SEASON_LIST)
44 | internal class StudioFilter(name: String) : CheckBoxFilterList(name, STUDIO_LIST)
45 |
46 | internal class StatusFilter(name: String) : QueryPartFilter(name, STATUS_LIST)
47 | internal class TypeFilter(name: String) : QueryPartFilter(name, TYPE_LIST)
48 | internal class SubFilter(name: String) : QueryPartFilter(name, SUB_LIST)
49 | internal class OrderFilter(name: String) : QueryPartFilter(name, ORDER_LIST)
50 |
51 | internal data class FilterSearchParams(
52 | val genres: String = "",
53 | val seasons: String = "",
54 | val studios: String = "",
55 | val status: String = "",
56 | val type: String = "",
57 | val sub: String = "",
58 | val order: String = "",
59 | )
60 |
61 | internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
62 | if (filters.isEmpty() || !filterInitialized()) return FilterSearchParams()
63 |
64 | return FilterSearchParams(
65 | filters.parseCheckbox(GENRES_LIST, "genre"),
66 | filters.parseCheckbox(SEASON_LIST, "season"),
67 | filters.parseCheckbox(STUDIO_LIST, "studio"),
68 | filters.asQueryPart(),
69 | filters.asQueryPart(),
70 | filters.asQueryPart(),
71 | filters.asQueryPart(),
72 | )
73 | }
74 |
75 | lateinit var filterElements: Elements
76 |
77 | fun filterInitialized() = ::filterElements.isInitialized
78 |
79 | fun getPairListByIndex(index: Int) = filterElements.get(index)
80 | .select("li")
81 | .map { element ->
82 | val key = element.selectFirst("label")!!.text()
83 | val value = element.selectFirst("input")!!.attr("value")
84 | Pair(key, value)
85 | }.toTypedArray()
86 |
87 | private val GENRES_LIST by lazy { getPairListByIndex(0) }
88 | private val SEASON_LIST by lazy { getPairListByIndex(1) }
89 | private val STUDIO_LIST by lazy { getPairListByIndex(2) }
90 | private val STATUS_LIST by lazy { getPairListByIndex(3) }
91 | private val TYPE_LIST by lazy { getPairListByIndex(4) }
92 | private val SUB_LIST by lazy { getPairListByIndex(5) }
93 | private val ORDER_LIST by lazy { getPairListByIndex(6) }
94 | }
95 |
--------------------------------------------------------------------------------
/lib-multisrc/animestream/src/eu/kanade/tachiyomi/multisrc/animestream/AnimeStreamUrlActivity.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.multisrc.animestream
2 |
3 | import android.app.Activity
4 | import android.content.ActivityNotFoundException
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.util.Log
8 | import kotlin.system.exitProcess
9 |
10 | class AnimeStreamUrlActivity : Activity() {
11 |
12 | private val tag by lazy { javaClass.simpleName }
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | val pathSegments = intent?.data?.pathSegments
17 | if (pathSegments != null && pathSegments.isNotEmpty()) {
18 | val path = pathSegments.joinToString("/")
19 | val mainIntent = Intent().apply {
20 | action = "eu.kanade.tachiyomi.ANIMESEARCH"
21 | putExtra("query", "${AnimeStream.PREFIX_SEARCH}$path")
22 | putExtra("filter", packageName)
23 | }
24 |
25 | try {
26 | startActivity(mainIntent)
27 | } catch (e: ActivityNotFoundException) {
28 | Log.e(tag, e.toString())
29 | }
30 | } else {
31 | Log.e(tag, "could not parse uri from intent $intent")
32 | }
33 |
34 | finish()
35 | exitProcess(0)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib-multisrc/datalifeengine/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-multisrc")
3 | }
4 |
5 | baseVersionCode = 1
--------------------------------------------------------------------------------
/lib-multisrc/dooplay/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lib-multisrc/dooplay/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-multisrc")
3 | }
4 |
5 | baseVersionCode = 2
6 |
--------------------------------------------------------------------------------
/lib-multisrc/dooplay/src/eu/kanade/tachiyomi/multisrc/dooplay/DooPlayUrlActivity.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.multisrc.dooplay
2 |
3 | import android.app.Activity
4 | import android.content.ActivityNotFoundException
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.util.Log
8 | import kotlin.system.exitProcess
9 |
10 | class DooPlayUrlActivity : Activity() {
11 |
12 | private val tag = "DooPlayUrlActivity"
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | val pathSegments = intent?.data?.pathSegments
17 | if (pathSegments != null && pathSegments.size > 1) {
18 | val path = pathSegments[0]
19 | val slug = pathSegments[1]
20 | val searchQuery = "$path/$slug"
21 | val mainIntent = Intent().apply {
22 | action = "eu.kanade.tachiyomi.ANIMESEARCH"
23 | putExtra("query", "${DooPlay.PREFIX_SEARCH}$searchQuery")
24 | putExtra("filter", packageName)
25 | }
26 |
27 | try {
28 | startActivity(mainIntent)
29 | } catch (e: ActivityNotFoundException) {
30 | Log.e(tag, e.toString())
31 | }
32 | } else {
33 | Log.e(tag, "could not parse uri from intent $intent")
34 | }
35 |
36 | finish()
37 | exitProcess(0)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib-multisrc/dopeflix/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-multisrc")
3 | }
4 |
5 | baseVersionCode = 20
6 |
7 | dependencies {
8 | api(project(":lib:dood-extractor"))
9 | api(project(":lib:cryptoaes"))
10 | api(project(":lib:playlist-utils"))
11 | }
12 |
--------------------------------------------------------------------------------
/lib-multisrc/dopeflix/src/eu/kanade/tachiyomi/multisrc/dopeflix/dto/DopeFlixDto.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.multisrc.dopeflix.dto
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.JsonElement
5 |
6 | @Serializable
7 | data class VideoDto(
8 | val sources: List,
9 | val tracks: List? = null,
10 | )
11 |
12 | @Serializable
13 | data class SourceResponseDto(
14 | val sources: JsonElement,
15 | val encrypted: Boolean = true,
16 | val tracks: List? = null,
17 | )
18 |
19 | @Serializable
20 | data class VideoLink(val file: String = "")
21 |
22 | @Serializable
23 | data class TrackDto(val file: String, val kind: String, val label: String = "")
24 |
--------------------------------------------------------------------------------
/lib-multisrc/zorotheme/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-multisrc")
3 | }
4 |
5 | baseVersionCode = 3
6 |
7 | dependencies {
8 | api(project(":lib:megacloud-extractor"))
9 | api(project(":lib:streamtape-extractor"))
10 | }
11 |
--------------------------------------------------------------------------------
/lib-multisrc/zorotheme/src/eu/kanade/tachiyomi/multisrc/zorotheme/dto/ZoroThemeDto.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.multisrc.zorotheme.dto
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.JsonElement
5 | import org.jsoup.Jsoup
6 | import org.jsoup.nodes.Document
7 |
8 | @Serializable
9 | data class HtmlResponse(
10 | val html: String,
11 | ) {
12 | fun getHtml(): Document {
13 | return Jsoup.parseBodyFragment(html)
14 | }
15 | }
16 |
17 | @Serializable
18 | data class SourcesResponse(
19 | val link: String? = null,
20 | )
21 |
22 | @Serializable
23 | data class VideoDto(
24 | val sources: List,
25 | val tracks: List? = null,
26 | )
27 |
28 | @Serializable
29 | data class SourceResponseDto(
30 | val sources: JsonElement,
31 | val encrypted: Boolean = true,
32 | val tracks: List? = null,
33 | )
34 |
35 | @Serializable
36 | data class VideoLink(val file: String = "")
37 |
38 | @Serializable
39 | data class TrackDto(val file: String, val kind: String, val label: String = "")
40 |
--------------------------------------------------------------------------------
/lib/blogger-extractor/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("lib-android")
3 | }
4 |
--------------------------------------------------------------------------------
/lib/blogger-extractor/src/main/java/eu/kanade/tachiyomi/lib/bloggerextractor/BloggerExtractor.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.lib.bloggerextractor
2 |
3 | import eu.kanade.tachiyomi.animesource.model.Video
4 | import eu.kanade.tachiyomi.network.GET
5 | import okhttp3.Headers
6 | import okhttp3.OkHttpClient
7 |
8 | class BloggerExtractor(private val client: OkHttpClient) {
9 | fun videosFromUrl(url: String, headers: Headers, suffix: String = ""): List