diff --git a/.editorconfig b/.editorconfig index 79a8309183..ebf0322a0d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,8 @@ ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site = true ij_kotlin_name_count_to_use_star_import = 2147483647 ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 + +[*.properties] +charset = utf-8 +end_of_line = lf +insert_final_newline = true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 7270838d3f..01fd2c07e6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ I acknowledge that: -- I have updated to the latest version of the app (stable is v0.14.6) +- I have updated to the latest version of the app (stable is v0.14.7) - I have updated all extensions - If this is an issue with the app itself, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi - I have searched the existing issues for duplicates diff --git a/.github/ISSUE_TEMPLATE/01_report_issue.yml b/.github/ISSUE_TEMPLATE/01_report_issue.yml index 9387bcb67d..41379302d8 100644 --- a/.github/ISSUE_TEMPLATE/01_report_issue.yml +++ b/.github/ISSUE_TEMPLATE/01_report_issue.yml @@ -63,7 +63,7 @@ body: description: | You can find your Tachiyomi version in **More → About**. placeholder: | - Example: "0.14.6" + Example: "0.14.7" validations: required: true @@ -95,7 +95,7 @@ body: required: true - label: I have written a short but informative title. required: true - - label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. + - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. required: true - label: I have updated all installed extensions. required: true diff --git a/.github/ISSUE_TEMPLATE/02_request_source.yml b/.github/ISSUE_TEMPLATE/02_request_source.yml index 851dfd7a54..c5752c092f 100644 --- a/.github/ISSUE_TEMPLATE/02_request_source.yml +++ b/.github/ISSUE_TEMPLATE/02_request_source.yml @@ -45,13 +45,15 @@ body: 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 or closed issue. + - label: I have checked that the extension does not already exist on the [website extensions list](https://tachiyomi.org/extensions/) or the app. required: true - - label: I have written a title with source name. + - label: I have checked that the extension is not on [the removed sources list](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md). required: true - - label: I have checked that the extension does not already exist on the [website extensions list](https://tachiyomi.org/extensions/) or the app. + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. required: true - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/tachiyomiorg/tachiyomi-extensions/) and verified it does not appear in the code base. + required: + - label: I have written a meaningful title with the source name. required: true - label: I will fill out all of the requested information in this form. required: true diff --git a/.github/ISSUE_TEMPLATE/03_report_url_change.yml b/.github/ISSUE_TEMPLATE/03_report_url_change.yml index e94f9a0a8a..80646a718b 100644 --- a/.github/ISSUE_TEMPLATE/03_report_url_change.yml +++ b/.github/ISSUE_TEMPLATE/03_report_url_change.yml @@ -45,13 +45,13 @@ body: 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 or closed issue. - required: true - - label: I have written a short but informative title. - required: true - label: I have updated all installed extensions. required: true - label: I have opened WebView and checked that the source URL is not updated yet. required: true + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. + required: true + - label: I have written a short but informative title. + required: true - label: I will fill out all of the requested information in this form. required: true diff --git a/.github/ISSUE_TEMPLATE/04_report_dead_source.yml b/.github/ISSUE_TEMPLATE/04_report_dead_source.yml index 4c0c891f9b..69ca7a555e 100644 --- a/.github/ISSUE_TEMPLATE/04_report_dead_source.yml +++ b/.github/ISSUE_TEMPLATE/04_report_dead_source.yml @@ -51,13 +51,13 @@ body: 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 or closed issue. - required: true - - label: I have written a title with source name. - required: true - label: I have updated all installed extensions. required: true - label: I have opened WebView and checked that the source website is down. required: true + - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. + required: true + - label: I have written a meaningful title with the source name. + required: true - label: I will fill out all of the requested information in this form. required: true diff --git a/.github/ISSUE_TEMPLATE/05_request_feature.yml b/.github/ISSUE_TEMPLATE/05_request_feature.yml index 810461a573..247467f7b6 100644 --- a/.github/ISSUE_TEMPLATE/05_request_feature.yml +++ b/.github/ISSUE_TEMPLATE/05_request_feature.yml @@ -53,7 +53,7 @@ body: required: true - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose). required: true - - label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. + - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. required: true - label: I will fill out all of the requested information in this form. required: true diff --git a/.github/ISSUE_TEMPLATE/06_request_meta.yml b/.github/ISSUE_TEMPLATE/06_request_meta.yml index 382e5b967a..26098fa5e3 100644 --- a/.github/ISSUE_TEMPLATE/06_request_meta.yml +++ b/.github/ISSUE_TEMPLATE/06_request_meta.yml @@ -33,7 +33,7 @@ body: required: true - label: If this is an issue with the app itself, I should be opening an issue in the [app repository](https://github.com/tachiyomiorg/tachiyomi/issues/new/choose). required: true - - label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. + - label: I have updated the app to version **[0.14.7](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**. required: true - label: I have updated all installed extensions. required: true diff --git a/.github/ISSUE_TEMPLATE/07_request_removal.yml b/.github/ISSUE_TEMPLATE/07_request_removal.yml index 043c4a1eee..73f251de3d 100644 --- a/.github/ISSUE_TEMPLATE/07_request_removal.yml +++ b/.github/ISSUE_TEMPLATE/07_request_removal.yml @@ -25,7 +25,7 @@ body: 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 Tachiyomi Discord server mod via DM + - label: I've added a `TXT` DNS record for my domain with the value `tachiyomi-verification` required: true - label: Site only hosts content scanlated by the group and not stolen from other scanlators or official releases (i.e., not an aggregator site) required: true diff --git a/.github/scripts/create-repo.sh b/.github/scripts/create-repo.sh index ca905c2ff4..9bb13b3409 100755 --- a/.github/scripts/create-repo.sh +++ b/.github/scripts/create-repo.sh @@ -30,7 +30,10 @@ for APK in ${APKS[@]}; do LANG=$(echo $APK | grep -Po "tachiyomi-\K[^\.]+") ICON=$(echo "$BADGING" | grep -Po "application-icon-320.*'\K[^']+") - unzip -p $APK $ICON > icon/${FILENAME%.*}.png + unzip -p $APK $ICON > icon/${PKGNAME}.png + + # TODO: legacy icons; remove after a while + cp icon/${PKGNAME}.png icon/${FILENAME%.*}.png SOURCE_INFO=$(jq ".[\"$PKGNAME\"]" < ../output.json) diff --git a/.github/workflows/batch_close_issues.yml b/.github/workflows/batch_close_issues.yml index ead5295180..a5278f6324 100644 --- a/.github/workflows/batch_close_issues.yml +++ b/.github/workflows/batch_close_issues.yml @@ -12,7 +12,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Close everything older than a year diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index d72c89c7ab..bc6677254c 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -26,13 +26,13 @@ jobs: CI_MODULE_GEN: true steps: - name: Clone repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt @@ -54,7 +54,7 @@ jobs: elif [[ ${changedFile} == .github/workflows/issue_moderator.yml ]]; then true elif [[ ${changedFile} == *.md ]]; then - true + true else isIndividualChanged=1 isMultisrcChanged=1 @@ -80,7 +80,7 @@ jobs: - id: generate-matrices name: Create output matrices - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES; @@ -105,10 +105,10 @@ jobs: matrix: ${{ fromJSON(needs.prepare.outputs.multisrcMatrix) }} steps: - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt @@ -139,10 +139,10 @@ jobs: matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }} steps: - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 085e57ee59..a80e5d6666 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -28,13 +28,13 @@ jobs: CI_MODULE_GEN: true steps: - name: Clone repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt @@ -52,7 +52,7 @@ jobs: 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 + uses: actions/github-script@v7 with: script: | const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES; @@ -72,10 +72,10 @@ jobs: matrix: ${{ fromJSON(needs.prepare.outputs.multisrcMatrix) }} steps: - name: Checkout master branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt @@ -120,10 +120,10 @@ jobs: matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }} steps: - name: Checkout master branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: adopt @@ -167,13 +167,13 @@ jobs: path: ~/apk-artifacts - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: adopt - name: Checkout master branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: master path: master @@ -187,7 +187,7 @@ jobs: java -jar ./Inspector.jar "apk" "output.json" "tmp" ./.github/scripts/create-repo.sh - name: Checkout repo branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: repo path: repo diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml index 299349cc62..ea96c1af85 100644 --- a/.github/workflows/issue_moderator.yml +++ b/.github/workflows/issue_moderator.yml @@ -43,24 +43,31 @@ jobs: }, { "type": "both", - "regex": ".*(hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|manhwahot|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|colamanhua|mangadig|hitomi\\.la|copymanga|neox|1manga\\.co|mangafox\\.fun|mangahere\\.onl|mangakakalot\\.fun|manganel(?!o)|mangaonline\\.fun|mangatoday|manga\\.town|onemanga\\.info|koushoku|ksk\\.moe|comikey|leercapitulo|c[uứ]u\\s*truy[eệ]n|reaper\\s*scans).*", + "regex": ".*(hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|manhwahot|leitor\\.?net|manga\\s*livre|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|colamanhua|mangadig|hitomi\\.la|copymanga|neox|1manga\\.co|mangafox\\.fun|mangahere\\.onl|mangakakalot\\.fun|manganel(?!o)|mangaonline\\.fun|mangatoday|manga\\.town|onemanga\\.info|koushoku|ksk\\.moe|comikey|leercapitulo|c[uứ]u\\s*truy[eệ]n|day\\s*comics?|reaper\\s*scans|constellar\\s*scans|mode\\s*scanlator|bakai|japscan|izakaya|blackout\\s*comics|anchira).*", "ignoreCase": true, "labels": ["invalid"], - "message": "{match} will not be added back as it is too difficult to maintain. Read #3475 for more information." + "message": "{match} will not be added back as it is too difficult to maintain. Read [this](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md) for more information." }, { "type": "both", - "regex": ".*(komiktap|gourmet\\s*scans|mangawow|mangagegecesi|knightnoscanlations|ahstudios|mangagecesi|nartag|xxx\\s*yaoi|yaoi\\s*fan\\s*clube|luminous|dragontea|manhwaid\\.org|reset(?:\\s*|-)scan|manga-flix\\.com|astra\\s*scans|manganoon|manga(?:-|\\s*)pro|coven\\s*scans?|shinobiscans|plot ?twist ?no ?fansub(?: ?scans?)?|plot-twistnf-scans(?:\\.com)?|mhscans|realm ?scans?).*", + "regex": ".*(komiktap|gourmet\\s*scans|mangawow|hikari\\s*scans|knightnoscanlations|mangasy|nartag|xxx\\s*yaoi|luminous|hunters\\s*scan|reset(?:\\s*|-)scan|astra\\s*scans|manga(?:-|\\s*)pro|shinobiscans|plot ?twist ?no ?fansub(?: ?scans?)?|plot-twistnf-scans(?:\\.com)?|mhscans|aresmanga|realm ?scans?|mono ?manga|dat(?:\\s*|-)?gar\\s*scan|remangas|moon ?daisy(?: scans?)?).*", "ignoreCase": true, "labels": ["invalid"], - "message": "{match} will not be added back as the scanlator team has requested it to be removed. Read #3475 for more information." + "message": "{match} will not be added back as the scanlator team has requested it to be removed. Read [this](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md) for more information." }, { "type": "both", "regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(? + + + + diff --git a/.run/FlixScansGenerator.run.xml b/.run/FlixScansGenerator.run.xml new file mode 100644 index 0000000000..bbb4ce682e --- /dev/null +++ b/.run/FlixScansGenerator.run.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3e1db9c0a..fe055ba6c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -176,6 +176,13 @@ Each extension should reside in `src//`. Use `all` as `` used in the folder inside `src` should be the major `language` part. For example, if you will be creating a `pt-BR` source, use `` here as `pt` only. Inside the source class, use the full locale string instead. +### Loading a subset of Gradle modules + +By default, all individual and generated multisrc extensions are loaded for local development. +This may be inconvenient if you only need to work on one extension at a time. + +To adjust which modules are loaded, make adjustments to the `settings.gradle.kts` file as needed. + #### Extension file structure The simplest extension structure looks like this: @@ -257,6 +264,16 @@ dependencies { } ``` +#### i18n library + +[`lib-i18n`](https://github.com/tachiyomiorg/tachiyomi-extensions/tree/master/lib/i18n) is a library for handling internationalization in the sources. It allows loading `.properties` files with messages located under the `assets/i18n` folder of each extension, that can be used to translate strings under the source. + +```gradle +dependencies { + implementation(project(':lib-i18n')) +} +``` + #### Additional dependencies If you find yourself needing additional functionality, you can add more dependencies to your `build.gradle` file. @@ -297,7 +314,7 @@ a.k.a. the Browse source entry point in the app (invoked by tapping on the sourc - The app calls `fetchPopularManga` which should return a `MangasPage` containing the first batch of found `SManga` entries. - This method supports pagination. When user scrolls the manga list and more results must be fetched, the app calls it again with increasing `page` values (starting with `page=1`). This continues while `MangasPage.hasNextPage` is passed as `true` and `MangasPage.mangas` is not empty. - To show the list properly, the app needs `url`, `title` and `thumbnail_url`. You **must** set them here. The rest of the fields could be filled later (refer to Manga Details below). - - You should set `thumbnail_url` if is available, if not, `fetchMangaDetails` will be **immediately** called (this will increase network calls heavily and should be avoided). + - You should set `thumbnail_url` if is available, if not, `getMangaDetails` will be **immediately** called (this will increase network calls heavily and should be avoided). #### Latest Manga @@ -314,7 +331,7 @@ a.k.a. the Latest source entry point in the app (invoked by tapping on the "Late ##### 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/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt) and in the table below. +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/tachiyomiorg/tachiyomi/blob/master/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt) and in the table below. | Filter | State type | Description | | ------ | ---------- | ----------- | @@ -340,15 +357,15 @@ open class UriPartFilter(displayName: String, private val vals: Array("clean") { - delete(rootProject.buildDir) + delete(rootProject.layout.buildDirectory.asFile.get()) } diff --git a/buildSrc/src/main/kotlin/AndroidConfig.kt b/buildSrc/src/main/kotlin/AndroidConfig.kt index 6e9a86ec52..ac6d1d01eb 100644 --- a/buildSrc/src/main/kotlin/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/AndroidConfig.kt @@ -1,5 +1,6 @@ object AndroidConfig { - const val compileSdk = 33 + const val compileSdk = 34 const val minSdk = 21 - const val targetSdk = 33 + @Suppress("UNUSED") + const val targetSdk = 34 } diff --git a/common.gradle b/common.gradle index 9d652843c2..769166a924 100644 --- a/common.gradle +++ b/common.gradle @@ -3,11 +3,13 @@ apply plugin: 'org.jmailen.kotlinter' android { compileSdkVersion AndroidConfig.compileSdk + namespace "eu.kanade.tachiyomi.extension" sourceSets { main { manifest.srcFile "AndroidManifest.xml" java.srcDirs = ['src'] res.srcDirs = ['res'] + assets.srcDirs = ['assets'] } release { manifest.srcFile "AndroidManifest.xml" diff --git a/core/AndroidManifest.xml b/core/AndroidManifest.xml index 77ee6b239e..ed0e26f0dd 100644 --- a/core/AndroidManifest.xml +++ b/core/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 90524827b0..e3948945d2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -9,6 +9,9 @@ android { minSdk = AndroidConfig.minSdk } + namespace = "eu.kanade.tachiyomi.extension" + + @Suppress("UnstableApiUsage") sourceSets { named("main") { manifest.srcFile("AndroidManifest.xml") diff --git a/gradle.properties b/gradle.properties index 08688f6bd8..a30adac641 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx5120m +org.gradle.jvmargs=-Xmx6144m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bb80878998..88100fa5ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines_version = "1.6.4" serialization_version = "1.4.0" [libraries] -gradle-agp = { module = "com.android.tools.build:gradle", version = "7.4.1" } +gradle-agp = { module = "com.android.tools.build:gradle", version = "7.4.2" } 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.13.0" } @@ -19,7 +19,6 @@ coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", ve coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" } injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" } -rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" } rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" } jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" } okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" } @@ -27,4 +26,3 @@ quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } [bundles] common = ["kotlin-stdlib", "coroutines-core", "coroutines-android", "injekt-core", "rxjava", "kotlin-protobuf", "kotlin-json", "jsoup", "okhttp", "tachiyomi-lib", "quickjs"] -reactivex = ["rxandroid"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2a..943f0cbfa7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f0..ac72c34e8a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..65dcd68d65 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # 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 +# https://github.com/gradle/gradle/blob/HEAD/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/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # 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"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f938..6689b85bee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/lib/cryptoaes/build.gradle.kts b/lib/cryptoaes/build.gradle.kts index 5eaa29645e..a9c8a57c65 100644 --- a/lib/cryptoaes/build.gradle.kts +++ b/lib/cryptoaes/build.gradle.kts @@ -8,8 +8,9 @@ android { defaultConfig { minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk } + + namespace = "eu.kanade.tachiyomi.lib.cryptoaes" } repositories { diff --git a/lib/cryptoaes/src/main/AndroidManifest.xml b/lib/cryptoaes/src/main/AndroidManifest.xml deleted file mode 100644 index 1ac16ea735..0000000000 --- a/lib/cryptoaes/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt index 8087584399..43594c2475 100644 --- a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt +++ b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt @@ -11,7 +11,6 @@ import javax.crypto.spec.SecretKeySpec /** * Conforming with CryptoJS AES method */ -@Suppress("unused", "FunctionName") object CryptoAES { private const val KEY_SIZE = 256 @@ -29,17 +28,17 @@ object CryptoAES { * @param password passphrase */ fun decrypt(cipherText: String, password: String): String { - try { + return try { val ctBytes = Base64.decode(cipherText, Base64.DEFAULT) val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) val md5: MessageDigest = MessageDigest.getInstance("MD5") val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5) - return decryptAES(cipherTextBytes, + decryptAES(cipherTextBytes, keyAndIV?.get(0) ?: ByteArray(32), keyAndIV?.get(1) ?: ByteArray(16)) } catch (e: Exception) { - return "" + "" } } @@ -93,6 +92,7 @@ object CryptoAES { * @param md the message digest algorithm to use * @return an two-element array with the generated key and IV */ + @Suppress("SameParameterValue") private fun generateKeyAndIV(keyLength: Int, ivLength: Int, iterations: Int, salt: ByteArray, password: ByteArray, md: MessageDigest): Array? { val digestLength = md.digestLength val requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength @@ -107,7 +107,7 @@ object CryptoAES { // Digest data (last digest if available, password data, salt if available) if (generatedLength > 0) md.update(generatedData, generatedLength - digestLength, digestLength) md.update(password) - if (salt != null) md.update(salt, 0, 8) + md.update(salt, 0, 8) md.digest(generatedData, generatedLength, digestLength) // additional rounds diff --git a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt index e1edf4843e..70f1c42271 100644 --- a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt +++ b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/Deobfuscator.kt @@ -9,8 +9,8 @@ package eu.kanade.tachiyomi.lib.cryptoaes object Deobfuscator { fun deobfuscateJsPassword(inputString: String): String { var idx = 0 - val brackets = listOf('[', '(') - var evaluatedString = StringBuilder() + val brackets = listOf('[', '(') + val evaluatedString = StringBuilder() while (idx < inputString.length) { val chr = inputString[idx] if (chr !in brackets) { @@ -60,9 +60,9 @@ object Deobfuscator { therefore '!+[]' count equals the digit if count equals 0, check for '+[]' just to be sure */ - val digit = "\\!\\+\\[\\]".toRegex().findAll(inputSubString).count() // matches '!+[]' + val digit = "!\\+\\[]".toRegex().findAll(inputSubString).count() // matches '!+[]' if (digit == 0) { - if ("\\+\\[\\]".toRegex().findAll(inputSubString).count() == 1) { // matches '+[]' + if ("\\+\\[]".toRegex().findAll(inputSubString).count() == 1) { // matches '+[]' return '0' } } else if (digit in 1..9) { diff --git a/lib/dataimage/build.gradle.kts b/lib/dataimage/build.gradle.kts index d02fbec564..eebbf7808b 100644 --- a/lib/dataimage/build.gradle.kts +++ b/lib/dataimage/build.gradle.kts @@ -8,8 +8,9 @@ android { defaultConfig { minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk } + + namespace = "eu.kanade.tachiyomi.lib.dataimage" } repositories { diff --git a/lib/dataimage/src/main/AndroidManifest.xml b/lib/dataimage/src/main/AndroidManifest.xml deleted file mode 100644 index 11d9e3ff5b..0000000000 --- a/lib/dataimage/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/lib/i18n/build.gradle.kts b/lib/i18n/build.gradle.kts new file mode 100644 index 0000000000..f21bb73148 --- /dev/null +++ b/lib/i18n/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + compileSdk = AndroidConfig.compileSdk + + defaultConfig { + minSdk = AndroidConfig.minSdk + } + + namespace = "eu.kanade.tachiyomi.lib.i18n" +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly(libs.kotlin.stdlib) +} diff --git a/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt b/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt new file mode 100644 index 0000000000..cdb02f5ff8 --- /dev/null +++ b/lib/i18n/src/main/java/eu/kanade/tachiyomi/lib/i18n/Intl.kt @@ -0,0 +1,93 @@ +package eu.kanade.tachiyomi.lib.i18n + +import org.jetbrains.annotations.PropertyKey +import java.io.InputStreamReader +import java.text.Collator +import java.util.Locale +import java.util.PropertyResourceBundle + +/** + * A simple wrapper to make internationalization easier to use in sources. + * + * Message files should be put in the `assets/i18n` folder, with the name + * `messages_{iso_639_1}.properties`, where `iso_639_1` should be using + * snake case and be in lowercase. + * + * To edit the strings, use the official JetBrain's + * [Resource Bundle Editor plugin](https://plugins.jetbrains.com/plugin/17035-resource-bundle-editor). + * + * Make sure to configure Android Studio to save Properties files as UTF-8 as well. + * You can refer to this [documentation](https://www.jetbrains.com/help/idea/properties-files.html#1cbc434e) + * on how to do so. + */ +class Intl( + language: String, + availableLanguages: Set, + private val baseLanguage: String, + private val classLoader: ClassLoader, + private val createMessageFileName: (String) -> String = { createDefaultMessageFileName(it) } +) { + + val chosenLanguage: String = when (language) { + in availableLanguages -> language + else -> baseLanguage + } + + private val locale: Locale = Locale.forLanguageTag(chosenLanguage) + + val collator: Collator = Collator.getInstance(locale) + + private val baseBundle: PropertyResourceBundle by lazy { createBundle(baseLanguage) } + + private val bundle: PropertyResourceBundle by lazy { + if (chosenLanguage == baseLanguage) baseBundle else createBundle(chosenLanguage) + } + + /** + * Returns the string from the message file. If the [key] is not present + * in the current language, the English value will be returned. If the [key] + * is also not present in English, the [key] surrounded by brackets will be returned. + */ + @Suppress("InvalidBundleOrProperty") + operator fun get(@PropertyKey(resourceBundle = "i18n.messages") key: String): String = when { + bundle.containsKey(key) -> bundle.getString(key) + baseBundle.containsKey(key) -> baseBundle.getString(key) + else -> "[$key]" + } + + /** + * Uses the string as a format string and returns a string obtained by + * substituting the specified arguments, using the instance locale. + */ + @Suppress("InvalidBundleOrProperty") + fun format(@PropertyKey(resourceBundle = "i18n.messages") key: String, vararg args: Any?) = + get(key).format(locale, *args) + + fun languageDisplayName(localeCode: String): String = + Locale.forLanguageTag(localeCode) + .getDisplayName(locale) + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } + + /** + * Creates a [PropertyResourceBundle] instance from the language specified. + * The expected message file will be loaded from the `res/raw`. + * + * The [PropertyResourceBundle] is used directly instead of [java.util.ResourceBundle] + * because the later has issues with UTF-8 files in Java 8, which would need + * the message files to be saved in ISO-8859-1, making the file readability bad. + */ + private fun createBundle(lang: String): PropertyResourceBundle { + val fileName = createMessageFileName(lang) + val fileContent = classLoader.getResourceAsStream(fileName) + + return PropertyResourceBundle(InputStreamReader(fileContent, "UTF-8")) + } + + companion object { + fun createDefaultMessageFileName(lang: String): String { + val langSnakeCase = lang.replace("-", "_").lowercase() + + return "assets/i18n/messages_$langSnakeCase.properties" + } + } +} diff --git a/lib/randomua/build.gradle.kts b/lib/randomua/build.gradle.kts new file mode 100644 index 0000000000..36ef0d6786 --- /dev/null +++ b/lib/randomua/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("com.android.library") + kotlin("android") + id("kotlinx-serialization") +} + +android { + compileSdk = AndroidConfig.compileSdk + + defaultConfig { + minSdk = AndroidConfig.minSdk + } + + namespace = "eu.kanade.tachiyomi.lib.randomua" +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly(libs.bundles.common) +} diff --git a/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentInterceptor.kt b/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentInterceptor.kt new file mode 100644 index 0000000000..0d36add8af --- /dev/null +++ b/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentInterceptor.kt @@ -0,0 +1,121 @@ +package eu.kanade.tachiyomi.lib.randomua + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +import uy.kohesive.injekt.injectLazy +import java.io.IOException + +private class RandomUserAgentInterceptor( + private val userAgentType: UserAgentType, + private val customUA: String?, + private val filterInclude: List, + private val filterExclude: List, +) : Interceptor { + + private var userAgent: String? = null + + private val json: Json by injectLazy() + + private val network: NetworkHelper by injectLazy() + + private val client = network.client + + override fun intercept(chain: Interceptor.Chain): Response { + try { + val originalRequest = chain.request() + + val newUserAgent = getUserAgent() + ?: return chain.proceed(originalRequest) + + val originalHeaders = originalRequest.headers + + val modifiedHeaders = originalHeaders.newBuilder() + .set("User-Agent", newUserAgent) + .build() + + return chain.proceed( + originalRequest.newBuilder() + .headers(modifiedHeaders) + .build() + ) + } catch (e: Exception) { + throw IOException(e.message) + } + } + + private fun getUserAgent(): String? { + if (userAgentType == UserAgentType.OFF) { + return customUA?.ifBlank { null } + } + + if (!userAgent.isNullOrEmpty()) return userAgent + + val uaResponse = client.newCall(GET(UA_DB_URL)).execute() + + if (!uaResponse.isSuccessful) { + uaResponse.close() + return null + } + + val userAgentList = uaResponse.use { json.decodeFromString(it.body.string()) } + + return when (userAgentType) { + UserAgentType.DESKTOP -> userAgentList.desktop + UserAgentType.MOBILE -> userAgentList.mobile + else -> error("Expected UserAgentType.DESKTOP or UserAgentType.MOBILE but got UserAgentType.${userAgentType.name} instead") + } + .filter { + filterInclude.isEmpty() || filterInclude.any { filter -> + it.contains(filter, ignoreCase = true) + } + } + .filterNot { + filterExclude.any { filter -> + it.contains(filter, ignoreCase = true) + } + } + .randomOrNull() + .also { userAgent = it } + } + + companion object { + private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" + } +} + +/** + * Helper function to add a latest random user agent interceptor. + * The interceptor will added at the first position in the chain, + * so the CloudflareInterceptor in the app will be able to make usage of it. + * + * @param userAgentType User Agent type one of (DESKTOP, MOBILE, OFF) + * @param customUA Optional custom user agent used when userAgentType is OFF + * @param filterInclude Filter to only include User Agents containing these strings + * @param filterExclude Filter to exclude User Agents containing these strings + */ +fun OkHttpClient.Builder.setRandomUserAgent( + userAgentType: UserAgentType, + customUA: String? = null, + filterInclude: List = emptyList(), + filterExclude: List = emptyList(), +) = apply { + interceptors().add(0, RandomUserAgentInterceptor(userAgentType, customUA, filterInclude, filterExclude)) +} + +enum class UserAgentType { + MOBILE, + DESKTOP, + OFF +} + +@Serializable +private data class UserAgentList( + val desktop: List, + val mobile: List +) diff --git a/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentPreference.kt b/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentPreference.kt new file mode 100644 index 0000000000..a92e67fa95 --- /dev/null +++ b/lib/randomua/src/main/java/eu/kanade/tachiyomi/lib/randomua/RandomUserAgentPreference.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.lib.randomua + +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import okhttp3.Headers + + + /** + * Helper function to return UserAgentType based on SharedPreference value + */ +fun SharedPreferences.getPrefUAType(): UserAgentType { + return when (getString(PREF_KEY_RANDOM_UA, "off")) { + "mobile" -> UserAgentType.MOBILE + "desktop" -> UserAgentType.DESKTOP + else -> UserAgentType.OFF + } +} + +/** + * Helper function to return custom UserAgent from SharedPreference + */ +fun SharedPreferences.getPrefCustomUA(): String? { + return getString(PREF_KEY_CUSTOM_UA, null) +} + +/** + * Helper function to add Random User-Agent settings to SharedPreference + * + * @param screen, PreferenceScreen from `setupPreferenceScreen` + */ +fun addRandomUAPreferenceToScreen( + screen: PreferenceScreen, +) { + ListPreference(screen.context).apply { + key = PREF_KEY_RANDOM_UA + title = TITLE_RANDOM_UA + entries = RANDOM_UA_ENTRIES + entryValues = RANDOM_UA_VALUES + summary = "%s" + setDefaultValue("off") + }.also(screen::addPreference) + + EditTextPreference(screen.context).apply { + key = PREF_KEY_CUSTOM_UA + title = TITLE_CUSTOM_UA + summary = CUSTOM_UA_SUMMARY + setOnPreferenceChangeListener { _, newValue -> + try { + Headers.Builder().add("User-Agent", newValue as String).build() + true + } catch (e: IllegalArgumentException) { + Toast.makeText(screen.context, "User Agent invalid:${e.message}", Toast.LENGTH_LONG).show() + false + } + } + }.also(screen::addPreference) +} + +const val TITLE_RANDOM_UA = "Random User-Agent (Requires Restart)" +const val PREF_KEY_RANDOM_UA = "pref_key_random_ua_" +val RANDOM_UA_ENTRIES = arrayOf("OFF", "Desktop", "Mobile") +val RANDOM_UA_VALUES = arrayOf("off", "desktop", "mobile") + +const val TITLE_CUSTOM_UA = "Custom User-Agent (Requires Restart)" +const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua_" +const val CUSTOM_UA_SUMMARY = "Leave blank to use application default user-agent (IGNORED if Random User-Agent is enabled)" + diff --git a/lib/synchrony/build.gradle.kts b/lib/synchrony/build.gradle.kts index 38ccda93fe..c178641a23 100644 --- a/lib/synchrony/build.gradle.kts +++ b/lib/synchrony/build.gradle.kts @@ -5,12 +5,12 @@ plugins { android { compileSdk = AndroidConfig.compileSdk - namespace = "eu.kanade.tachiyomi.lib.synchrony" defaultConfig { minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk } + + namespace = "eu.kanade.tachiyomi.lib.synchrony" } repositories { diff --git a/lib/synchrony/src/main/java/eu/kanade/tachiyomi/lib/synchrony/Deobfuscator.kt b/lib/synchrony/src/main/java/eu/kanade/tachiyomi/lib/synchrony/Deobfuscator.kt index a32b6f89d1..f85a43cdfd 100644 --- a/lib/synchrony/src/main/java/eu/kanade/tachiyomi/lib/synchrony/Deobfuscator.kt +++ b/lib/synchrony/src/main/java/eu/kanade/tachiyomi/lib/synchrony/Deobfuscator.kt @@ -33,7 +33,6 @@ object Deobfuscator { } } - @Suppress("unused") private interface TestInterface { fun getValue(): String } diff --git a/lib/textinterceptor/build.gradle.kts b/lib/textinterceptor/build.gradle.kts index e8086827ca..f7102a8a40 100644 --- a/lib/textinterceptor/build.gradle.kts +++ b/lib/textinterceptor/build.gradle.kts @@ -8,8 +8,9 @@ android { defaultConfig { minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk } + + namespace = "eu.kanade.tachiyomi.lib.textinterceptor" } repositories { diff --git a/lib/textinterceptor/src/main/AndroidManifest.xml b/lib/textinterceptor/src/main/AndroidManifest.xml deleted file mode 100644 index 6d52001ade..0000000000 --- a/lib/textinterceptor/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt b/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt index b614c03b9e..d1b6fd98f7 100644 --- a/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt +++ b/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.lib.textinterceptor +import android.annotation.SuppressLint import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -53,7 +54,7 @@ class TextInterceptor : Interceptor { } @Suppress("DEPRECATION") - val heading: StaticLayout = StaticLayout( + val heading = StaticLayout( creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(), Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true ) @@ -67,7 +68,7 @@ class TextInterceptor : Interceptor { } @Suppress("DEPRECATION") - val body: StaticLayout = StaticLayout( + val body = StaticLayout( story, paintBody, (WIDTH - 2 * X_PADDING).toInt(), Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true ) @@ -75,12 +76,12 @@ class TextInterceptor : Interceptor { // Image building val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt() val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888) - val canvas: Canvas = Canvas(bitmap) - // Image drawing - canvas.drawColor(Color.WHITE) - heading.draw(canvas, X_PADDING, Y_PADDING) - body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat()) + Canvas(bitmap).apply { + drawColor(Color.WHITE) + heading.draw(this, X_PADDING, Y_PADDING) + body.draw(this, X_PADDING, Y_PADDING + heading.height.toFloat()) + } // Image converting & returning val stream = ByteArrayOutputStream() @@ -95,14 +96,17 @@ class TextInterceptor : Interceptor { .build() } + @SuppressLint("ObsoleteSdkInt") private fun textFixer(htmlString: String): String { return if (Build.VERSION.SDK_INT >= 24) { Html.fromHtml(htmlString , Html.FROM_HTML_MODE_LEGACY).toString() } else { + @Suppress("DEPRECATION") Html.fromHtml(htmlString).toString() } } + @Suppress("SameParameterValue") private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) { canvas.save() canvas.translate(x, y) diff --git a/multisrc/build.gradle.kts b/multisrc/build.gradle.kts index dbc098f91f..7b2948f275 100644 --- a/multisrc/build.gradle.kts +++ b/multisrc/build.gradle.kts @@ -9,9 +9,10 @@ android { defaultConfig { minSdk = 29 - targetSdk = AndroidConfig.targetSdk } + namespace = "eu.kanade.tachiyomi.lib.themesources" + kotlinOptions { freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" } @@ -40,6 +41,7 @@ dependencies { tasks { register("generateExtensions") { + val buildDir = layout.buildDirectory.asFile.get() classpath = configurations.compileOnly.get() + configurations.androidApis.get() + // android.jar path files("$buildDir/intermediates/aar_main_jar/debug/classes.jar") // jar made from this module diff --git a/multisrc/overrides/a3manga/default/AndroidManifest.xml b/multisrc/overrides/a3manga/default/AndroidManifest.xml index 9a92f3a79e..0489d5a185 100644 --- a/multisrc/overrides/a3manga/default/AndroidManifest.xml +++ b/multisrc/overrides/a3manga/default/AndroidManifest.xml @@ -1,7 +1,5 @@ - - + - + - \ No newline at end of file + diff --git a/multisrc/overrides/bilibili/bilibilicomics/src/BilibiliComicsFactory.kt b/multisrc/overrides/bilibili/bilibilicomics/src/BilibiliComicsFactory.kt index 8a6a6a3eb6..c7b2234e85 100644 --- a/multisrc/overrides/bilibili/bilibilicomics/src/BilibiliComicsFactory.kt +++ b/multisrc/overrides/bilibili/bilibilicomics/src/BilibiliComicsFactory.kt @@ -123,12 +123,7 @@ abstract class BilibiliComics(lang: String) : Bilibili( val userEpisodesResponse = client.newCall(userEpisodesRequest).execute() val unlockedEpisodes = userEpisodesParse(userEpisodesResponse) - return comic.episodeList - .filter { episode -> - (episode.payMode == 0 && episode.payGold == 0) || - episode.id in unlockedEpisodes - } - .map { ep -> chapterFromObject(ep, comic.id) } + return comic.episodeList.map { ep -> chapterFromObject(ep, comic.id, isUnlocked = ep.id in unlockedEpisodes) } } private fun userEpisodesRequest(comicId: Int): Request { diff --git a/multisrc/overrides/bilibili/bilibilimanga/AndroidManifest.xml b/multisrc/overrides/bilibili/bilibilimanga/AndroidManifest.xml index 5603a61fe4..6b86dbfce3 100644 --- a/multisrc/overrides/bilibili/bilibilimanga/AndroidManifest.xml +++ b/multisrc/overrides/bilibili/bilibilimanga/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + - \ No newline at end of file + diff --git a/multisrc/overrides/comicake/default/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/comicake/default/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 43f993cb3a..0000000000 Binary files a/multisrc/overrides/comicake/default/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/default/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/comicake/default/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a94861405a..0000000000 Binary files a/multisrc/overrides/comicake/default/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/default/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/comicake/default/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2823398dc7..0000000000 Binary files a/multisrc/overrides/comicake/default/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/default/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/comicake/default/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8cbf2b8791..0000000000 Binary files a/multisrc/overrides/comicake/default/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/default/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/comicake/default/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 28c6f651d1..0000000000 Binary files a/multisrc/overrides/comicake/default/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/default/res/web_hi_res_512.png b/multisrc/overrides/comicake/default/res/web_hi_res_512.png deleted file mode 100644 index 2a9b96f20a..0000000000 Binary files a/multisrc/overrides/comicake/default/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/comicake/whimsubs/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 70c9f482da..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/comicake/whimsubs/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 387803253d..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/comicake/whimsubs/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 321243bf19..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/comicake/whimsubs/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6c5a595842..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/comicake/whimsubs/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7a610d11a8..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/comicake/whimsubs/res/web_hi_res_512.png b/multisrc/overrides/comicake/whimsubs/res/web_hi_res_512.png deleted file mode 100644 index 9f3c7549f2..0000000000 Binary files a/multisrc/overrides/comicake/whimsubs/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..62a89997b4 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3ffb60b76f Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..5154471150 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c550e843d8 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bb7d942e0a Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png b/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png new file mode 100644 index 0000000000..9e00dbe6ee Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt b/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt new file mode 100644 index 0000000000..abecc8907b --- /dev/null +++ b/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.ca.fansubscat + +import eu.kanade.tachiyomi.multisrc.fansubscat.FansubsCat + +class FansubsCatMain : FansubsCat( + "Fansubs.cat", + "https://manga.fansubs.cat", + "ca", + isHentaiSite = false, +) diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..85c7cda144 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3f52697c4d Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..015a708e27 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9d3a29b18f Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e3fb8c3368 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png b/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png new file mode 100644 index 0000000000..c2546bc59c Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt b/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt new file mode 100644 index 0000000000..704e689425 --- /dev/null +++ b/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.ca.fansubscathentai + +import eu.kanade.tachiyomi.multisrc.fansubscat.FansubsCat + +class FansubsCatHentai : FansubsCat( + "Fansubs.cat - Hentai", + "https://hentai.fansubs.cat/manga", + "ca", + isHentaiSite = true, +) diff --git a/multisrc/overrides/flixscans/flixscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/flixscans/flixscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..96992a1858 Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/flixscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/flixscans/flixscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..bf4daa4174 Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/flixscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/flixscans/flixscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8beccb9bc6 Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/flixscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/flixscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d09cf7a560 Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/flixscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/flixscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9067ecd9cd Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/flixscans/res/web_hi_res_512.png b/multisrc/overrides/flixscans/flixscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..8ac2f0947f Binary files /dev/null and b/multisrc/overrides/flixscans/flixscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/flixscans/flixscans/src/FlixScansNet.kt b/multisrc/overrides/flixscans/flixscans/src/FlixScansNet.kt new file mode 100644 index 0000000000..b148a2283e --- /dev/null +++ b/multisrc/overrides/flixscans/flixscans/src/FlixScansNet.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.extension.en.flixscans + +import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans + +class FlixScansNet : FlixScans("Flix Scans", "https://flixscans.net", "en", cdnUrl = "https://media.flixscans.net/") diff --git a/multisrc/overrides/flixscans/galaxymanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..508b1a1d1f Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8a52388887 Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..0ced75903a Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e5e17f45dd Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2e68f0aab3 Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/res/web_hi_res_512.png b/multisrc/overrides/flixscans/galaxymanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..e37dad0c0a Binary files /dev/null and b/multisrc/overrides/flixscans/galaxymanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/flixscans/galaxymanga/src/GalaxyManga.kt b/multisrc/overrides/flixscans/galaxymanga/src/GalaxyManga.kt new file mode 100644 index 0000000000..ce7fb9352b --- /dev/null +++ b/multisrc/overrides/flixscans/galaxymanga/src/GalaxyManga.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.extension.ar.galaxymanga + +import eu.kanade.tachiyomi.multisrc.flixscans.Chapter +import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Request +import okhttp3.Response + +class GalaxyManga : FlixScans("جالاكسي مانجا", "https://flixscans.com", "ar") { + override val versionId = 2 + + override fun chapterListRequest(manga: SManga): Request { + val id = manga.url.split("-")[1] + + return GET("$apiUrl/webtoon/chapters/$id-desc", headers) + } + + override fun chapterListParse(response: Response): List { + val chapters = response.parseAs>() + + return chapters.map(Chapter::toSChapter) + } +} diff --git a/multisrc/overrides/flixscans/manganoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/flixscans/manganoon/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..57f68e7839 Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/manganoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/flixscans/manganoon/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..54fb858234 Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/manganoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/flixscans/manganoon/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b780a4629e Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/manganoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/manganoon/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6b3f3691ec Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/manganoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/flixscans/manganoon/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..574a70fad6 Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/flixscans/manganoon/res/web_hi_res_512.png b/multisrc/overrides/flixscans/manganoon/res/web_hi_res_512.png new file mode 100644 index 0000000000..16b34084ad Binary files /dev/null and b/multisrc/overrides/flixscans/manganoon/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/fmreader/kisslove/src/KissLove.kt b/multisrc/overrides/fmreader/kisslove/src/KissLove.kt index 130b4fe482..2e31fa7df3 100644 --- a/multisrc/overrides/fmreader/kisslove/src/KissLove.kt +++ b/multisrc/overrides/fmreader/kisslove/src/KissLove.kt @@ -1,9 +1,80 @@ package eu.kanade.tachiyomi.extension.ja.kisslove import eu.kanade.tachiyomi.multisrc.fmreader.FMReader -import eu.kanade.tachiyomi.source.model.Page -import org.jsoup.nodes.Document +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Element +import java.util.Calendar -class KissLove : FMReader("KissLove", "https://klmanga.com", "ja") { - override fun pageListParse(document: Document): List = base64PageListParse(document) +class KissLove : FMReader("KissLove", "https://klz9.com", "ja") { + override fun latestUpdatesRequest(page: Int) = + GET("$baseUrl/manga-list.html?page=$page&sort=last_update") + + override fun chapterListRequest(manga: SManga): Request { + val mangaId = MID_URL_REGEX.find(manga.url) + ?.groupValues?.get(1) + ?: throw Exception("Could not find manga id") + + val xhrUrl = "$baseUrl/app/manga/controllers/cont.listChapter.php".toHttpUrl().newBuilder() + .addQueryParameter("slug", mangaId) + .build() + + return GET(xhrUrl, headers) + } + + override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { + return SChapter.create().apply { + element.select(chapterUrlSelector).first()!!.let { + setUrlWithoutDomain("$baseUrl/${it.attr("href")}") + name = it.attr("title") + } + + date_upload = element.select(chapterTimeSelector) + .let { if (it.hasText()) parseChapterDate(it.text()) else 0 } + } + } + + private fun parseChapterDate(date: String): Long { + val value = date.split(' ')[dateValueIndex].toInt() + val chapterDate = Calendar.getInstance().apply { + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + when (date.split(' ')[dateWordIndex]) { + "mins", "minutes" -> chapterDate.add(Calendar.MINUTE, value * -1) + "hours" -> chapterDate.add(Calendar.HOUR_OF_DAY, value * -1) + "days" -> chapterDate.add(Calendar.DATE, value * -1) + "weeks" -> chapterDate.add(Calendar.DATE, value * 7 * -1) + "months" -> chapterDate.add(Calendar.MONTH, value * -1) + "years" -> chapterDate.add(Calendar.YEAR, value * -1) + else -> return 0 + } + + return chapterDate.timeInMillis + } + + override fun pageListRequest(chapter: SChapter): Request { + val request = super.pageListRequest(chapter) + val response = client.newCall(request).execute() + val document = response.asJsoup() + + val chapterId = document.selectFirst("#chapter") + ?.`val`() + ?: throw Exception("Could not find chapter id") + + val xhrUrl = "$baseUrl/app/manga/controllers/cont.listImg.php".toHttpUrl().newBuilder() + .addQueryParameter("cid", chapterId) + .build() + + return GET(xhrUrl, headers) + } + + companion object { + private val MID_URL_REGEX = "-([^.]+).html".toRegex() + } } diff --git a/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt b/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt index e3e26a830e..179b63e532 100644 --- a/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt +++ b/multisrc/overrides/fmreader/manhwa18net/src/Manhwa18NetFactory.kt @@ -5,7 +5,9 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.SChapter import okhttp3.Request +import org.jsoup.nodes.Element class Manhwa18NetFactory : SourceFactory { override fun createSources(): List = listOf( @@ -15,26 +17,61 @@ class Manhwa18NetFactory : SourceFactory { } class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") { - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC&ungenre=raw", headers) + override val requestPath = "genre/manhwa" + override val popularSort = "sort=top" + override val pageListImageSelector = "div#chapter-content > img" override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC&ungenre=raw", headers) + GET( + "$baseUrl/$requestPath?listType=pagination&page=$page&sort=update&sort_type=DESC", + headers, + ) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val noRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().addQueryParameter("ungenre", "raw").toString() + val noRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().toString() return GET(noRawsUrl, headers) } override fun getGenreList() = getAdultGenreList() + + override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { + return SChapter.create().apply { + setUrlWithoutDomain(element.attr("abs:href")) + name = element.attr("title") + date_upload = parseAbsoluteDate( + element.select(chapterTimeSelector).text().substringAfter(" - "), + ) + } + } } class Manhwa18NetRaw : FMReader("Manhwa18.net", "https://manhwa18.net", "ko") { - override val requestPath = "manga-list-genre-raw.html" + override val requestPath = "genre/raw" + override val popularSort = "sort=top" + override val pageListImageSelector = "div#chapter-content > img" + + override fun latestUpdatesRequest(page: Int): Request = + GET( + "$baseUrl/$requestPath?listType=pagination&page=$page&sort=update&sort_type=DESC", + headers, + ) + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().addQueryParameter("genre", "raw").toString() + val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().toString() return GET(onlyRawsUrl, headers) } - override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) }) + override fun getFilterList() = FilterList( + super.getFilterList().filterNot { it == GenreList(getGenreList()) }, + ) + + override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { + return SChapter.create().apply { + setUrlWithoutDomain(element.attr("abs:href")) + name = element.attr("title") + date_upload = parseAbsoluteDate( + element.select(chapterTimeSelector).text().substringAfter(" - "), + ) + } + } } diff --git a/multisrc/overrides/fmreader/rawlh/src/WeLoveManga.kt b/multisrc/overrides/fmreader/rawlh/src/WeLoveManga.kt index a6c8e05cb7..b9431823c4 100644 --- a/multisrc/overrides/fmreader/rawlh/src/WeLoveManga.kt +++ b/multisrc/overrides/fmreader/rawlh/src/WeLoveManga.kt @@ -1,14 +1,12 @@ package eu.kanade.tachiyomi.extension.ja.rawlh -import android.util.Base64 import eu.kanade.tachiyomi.multisrc.fmreader.FMReader import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SManga import okhttp3.Request -import org.jsoup.nodes.Attribute import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import java.nio.charset.Charset class WeLoveManga : FMReader("WeLoveManga", "https://weloma.art", "ja") { // Formerly "RawLH" @@ -17,13 +15,9 @@ class WeLoveManga : FMReader("WeLoveManga", "https://weloma.art", "ja") { override val chapterUrlSelector = "" override fun pageListParse(document: Document): List { fun Element.decoded(): String { - val attr = this.attributes().map(Attribute::key).maxByOrNull(kotlin.String::length) ?: "src" - return if (!this.attr(attr).contains(".")) { - Base64.decode(this.attr(attr), Base64.DEFAULT).toString(Charset.defaultCharset()) - } else { - this.attr("abs:$attr") - } + return this.attr("data-src").trimEnd() } + return document.select(pageListImageSelector).mapIndexed { i, img -> Page(i, document.location(), img.decoded()) } @@ -31,4 +25,20 @@ class WeLoveManga : FMReader("WeLoveManga", "https://weloma.art", "ja") { // Referer needs to be chapter URL override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + element.select(headerSelector).let { + setUrlWithoutDomain(it.attr("abs:href")) + title = it.text() + } + thumbnail_url = element + .select("div.content.img-in-ratio") + .first()!! + .attr("style") + .let { BACKGROUND_IMAGE_REGEX.find(it)?.groups?.get(1)?.value } + } + + companion object { + val BACKGROUND_IMAGE_REGEX = Regex("""url\(['"]?(.*?)['"]?\)""") + } } diff --git a/multisrc/overrides/fmreader/welovemangaone/src/WeLoveMangaOne.kt b/multisrc/overrides/fmreader/welovemangaone/src/WeLoveMangaOne.kt index f1c09a21c5..8c07321f42 100644 --- a/multisrc/overrides/fmreader/welovemangaone/src/WeLoveMangaOne.kt +++ b/multisrc/overrides/fmreader/welovemangaone/src/WeLoveMangaOne.kt @@ -3,6 +3,10 @@ package eu.kanade.tachiyomi.extension.ja.welovemangaone import eu.kanade.tachiyomi.multisrc.fmreader.FMReader import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request import org.jsoup.nodes.Element import java.util.Calendar @@ -10,6 +14,18 @@ class WeLoveMangaOne : FMReader("WeLoveMangaOne", "https://welovemanga.one", "ja override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manga-list.html?page=$page&sort=last_update") + override fun chapterListRequest(manga: SManga): Request { + val mangaId = MID_URL_REGEX.find(manga.url) + ?.groupValues?.get(1) + ?: throw Exception("Could not find manga id") + + val xhrUrl = "$baseUrl/app/manga/controllers/cont.Listchapter.php".toHttpUrl().newBuilder() + .addQueryParameter("mid", mangaId) + .build() + + return GET(xhrUrl, headers) + } + override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { return SChapter.create().apply { element.let { @@ -41,4 +57,24 @@ class WeLoveMangaOne : FMReader("WeLoveMangaOne", "https://welovemanga.one", "ja return chapterDate.timeInMillis } + + override fun pageListRequest(chapter: SChapter): Request { + val request = super.pageListRequest(chapter) + val response = client.newCall(request).execute() + val document = response.asJsoup() + + val chapterId = document.selectFirst("#chapter") + ?.`val`() + ?: throw Exception("Could not find chapter id") + + val xhrUrl = "$baseUrl/app/manga/controllers/cont.listImg.php".toHttpUrl().newBuilder() + .addQueryParameter("cid", chapterId) + .build() + + return GET(xhrUrl, headers) + } + + companion object { + private val MID_URL_REGEX = "(\\d+)/".toRegex() + } } diff --git a/multisrc/overrides/grouple/allhentai/src/AllHentai.kt b/multisrc/overrides/grouple/allhentai/src/AllHentai.kt index 170287b291..66b2850009 100644 --- a/multisrc/overrides/grouple/allhentai/src/AllHentai.kt +++ b/multisrc/overrides/grouple/allhentai/src/AllHentai.kt @@ -13,7 +13,7 @@ import okhttp3.Request import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class AllHentai : GroupLe("AllHentai", "https://2023.allhen.online", "ru") { +class AllHentai : GroupLe("AllHentai", "https://z.allhen.online", "ru") { override val id: Long = 1809051393403180443 @@ -292,6 +292,6 @@ class AllHentai : GroupLe("AllHentai", "https://2023.allhen.online", "ru") { companion object { private const val DOMAIN_TITLE = "Домен" - private const val DOMAIN_DEFAULT = "https://2023.allhen.online" + private const val DOMAIN_DEFAULT = "https://z.allhen.online" } } diff --git a/multisrc/overrides/grouple/default/AndroidManifest.xml b/multisrc/overrides/grouple/default/AndroidManifest.xml index 87f96afd91..294993e552 100644 --- a/multisrc/overrides/grouple/default/AndroidManifest.xml +++ b/multisrc/overrides/grouple/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + ().getSharedPreferences("source_$id", 0x0000) + } + + private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!! + override val baseUrl: String = domain override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = super.searchMangaRequest(page, query, filters).url.newBuilder() (if (filters.isEmpty()) getFilterList().reversed() else filters.reversed()).forEach { filter -> @@ -160,4 +172,30 @@ class MintManga : GroupLe("MintManga", "https://mintmanga.live", "ru") { Genre("юри", "el_1315"), Genre("яой", "el_1336"), ) + + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + super.setupPreferenceScreen(screen) + EditTextPreference(screen.context).apply { + key = DOMAIN_TITLE + this.title = DOMAIN_TITLE + summary = domain + this.setDefaultValue(DOMAIN_DEFAULT) + dialogTitle = DOMAIN_TITLE + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() + Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.let(screen::addPreference) + } + + companion object { + private const val DOMAIN_TITLE = "Домен" + private const val DOMAIN_DEFAULT = "https://mintmanga.com" + } } diff --git a/multisrc/overrides/grouple/readmanga/src/ReadManga.kt b/multisrc/overrides/grouple/readmanga/src/ReadManga.kt index c9dcf08e58..0375fb8588 100644 --- a/multisrc/overrides/grouple/readmanga/src/ReadManga.kt +++ b/multisrc/overrides/grouple/readmanga/src/ReadManga.kt @@ -1,16 +1,28 @@ package eu.kanade.tachiyomi.extension.ru.readmanga +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference import eu.kanade.tachiyomi.multisrc.grouple.GroupLe import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Request +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class ReadManga : GroupLe("ReadManga", "https://readmanga.live", "ru") { override val id: Long = 5 + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!! + override val baseUrl: String = domain override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = super.searchMangaRequest(page, query, filters).url.newBuilder() (if (filters.isEmpty()) getFilterList().reversed() else filters.reversed()).forEach { filter -> @@ -157,4 +169,29 @@ class ReadManga : GroupLe("ReadManga", "https://readmanga.live", "ru") { Genre("школа", "el_2127"), Genre("этти", "el_2149"), ) + + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + super.setupPreferenceScreen(screen) + EditTextPreference(screen.context).apply { + key = DOMAIN_TITLE + this.title = DOMAIN_TITLE + summary = domain + this.setDefaultValue(DOMAIN_DEFAULT) + dialogTitle = DOMAIN_TITLE + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() + Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.let(screen::addPreference) + } + companion object { + private const val DOMAIN_TITLE = "Домен" + private const val DOMAIN_DEFAULT = "https://readmanga.live" + } } diff --git a/multisrc/overrides/grouple/rumix/src/RuMIX.kt b/multisrc/overrides/grouple/rumix/src/RuMIX.kt index cbfeac84d2..6d2efe76d8 100644 --- a/multisrc/overrides/grouple/rumix/src/RuMIX.kt +++ b/multisrc/overrides/grouple/rumix/src/RuMIX.kt @@ -1,14 +1,27 @@ package eu.kanade.tachiyomi.extension.ru.rumix +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference import eu.kanade.tachiyomi.multisrc.grouple.GroupLe import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Request +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class RuMIX : GroupLe("RuMIX", "https://rumix.me", "ru") { + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!! + override val baseUrl: String = domain + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = super.searchMangaRequest(page, query, filters).url.newBuilder() (if (filters.isEmpty()) getFilterList().reversed() else filters.reversed()).forEach { filter -> @@ -39,4 +52,30 @@ class RuMIX : GroupLe("RuMIX", "https://rumix.me", "ru") { override fun getFilterList() = FilterList( OrderBy(), ) + + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + super.setupPreferenceScreen(screen) + EditTextPreference(screen.context).apply { + key = DOMAIN_TITLE + this.title = DOMAIN_TITLE + summary = domain + this.setDefaultValue(DOMAIN_DEFAULT) + dialogTitle = DOMAIN_TITLE + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() + Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.let(screen::addPreference) + } + + companion object { + private const val DOMAIN_TITLE = "Домен" + private const val DOMAIN_DEFAULT = "https://rumix.me" + } } diff --git a/multisrc/overrides/grouple/selfmanga/src/SelfManga.kt b/multisrc/overrides/grouple/selfmanga/src/SelfManga.kt index 24c8590280..a370374aa5 100644 --- a/multisrc/overrides/grouple/selfmanga/src/SelfManga.kt +++ b/multisrc/overrides/grouple/selfmanga/src/SelfManga.kt @@ -1,16 +1,29 @@ package eu.kanade.tachiyomi.extension.ru.selfmanga +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference import eu.kanade.tachiyomi.multisrc.grouple.GroupLe import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Request +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class SelfManga : GroupLe("SelfManga", "https://selfmanga.live", "ru") { override val id: Long = 5227602742162454547 + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!! + override val baseUrl: String = domain + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = super.searchMangaRequest(page, query, filters).url.newBuilder() (if (filters.isEmpty()) getFilterList().reversed() else filters.reversed()).forEach { filter -> @@ -103,4 +116,29 @@ class SelfManga : GroupLe("SelfManga", "https://selfmanga.live", "ru") { Genre("школа", "el_2127"), Genre("этти", "el_4982"), ) + + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + super.setupPreferenceScreen(screen) + EditTextPreference(screen.context).apply { + key = DOMAIN_TITLE + this.title = DOMAIN_TITLE + summary = domain + this.setDefaultValue(DOMAIN_DEFAULT) + dialogTitle = DOMAIN_TITLE + setOnPreferenceChangeListener { _, newValue -> + try { + val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() + Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() + res + } catch (e: Exception) { + e.printStackTrace() + false + } + } + }.let(screen::addPreference) + } + companion object { + private const val DOMAIN_TITLE = "Домен" + private const val DOMAIN_DEFAULT = "https://selfmanga.live" + } } diff --git a/multisrc/overrides/guya/default/AndroidManifest.xml b/multisrc/overrides/guya/default/AndroidManifest.xml index e4d6357a6c..5891c5cca2 100644 --- a/multisrc/overrides/guya/default/AndroidManifest.xml +++ b/multisrc/overrides/guya/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + - \ No newline at end of file + diff --git a/multisrc/overrides/heancms/gloriousscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/heancms/gloriousscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 62aca8dbd8..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/heancms/gloriousscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a6d629c3f9..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/heancms/gloriousscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1a19c57d9e..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1ef342d221..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e3e654ece4..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/res/web_hi_res_512.png b/multisrc/overrides/heancms/gloriousscan/res/web_hi_res_512.png deleted file mode 100644 index 14d45a444f..0000000000 Binary files a/multisrc/overrides/heancms/gloriousscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/heancms/gloriousscan/src/GloriousScan.kt b/multisrc/overrides/heancms/gloriousscan/src/GloriousScan.kt deleted file mode 100644 index 116bca5d62..0000000000 --- a/multisrc/overrides/heancms/gloriousscan/src/GloriousScan.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.gloriousscan - -import eu.kanade.tachiyomi.multisrc.heancms.HeanCms -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.TimeZone - -class GloriousScan : HeanCms( - "Glorious Scan", - "https://gloriousscan.com", - "pt-BR", -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimitHost(apiUrl.toHttpUrl(), 1, 2) - .build() - - // Site changed from Madara to HeanCms. - override val versionId = 2 - - override val coverPath = "" - - override val dateFormat: SimpleDateFormat = super.dateFormat.apply { - timeZone = TimeZone.getTimeZone("GMT+02:00") - } -} diff --git a/multisrc/overrides/heancms/omegascans/src/OmegaScans.kt b/multisrc/overrides/heancms/omegascans/src/OmegaScans.kt index 99f555cd67..6daaec3264 100644 --- a/multisrc/overrides/heancms/omegascans/src/OmegaScans.kt +++ b/multisrc/overrides/heancms/omegascans/src/OmegaScans.kt @@ -1,18 +1,48 @@ package eu.kanade.tachiyomi.extension.en.omegascans +import eu.kanade.tachiyomi.multisrc.heancms.Genre import eu.kanade.tachiyomi.multisrc.heancms.HeanCms import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient class OmegaScans : HeanCms("Omega Scans", "https://omegascans.org", "en") { - override val client: OkHttpClient = super.client.newBuilder() + override val client = super.client.newBuilder() .rateLimitHost(apiUrl.toHttpUrl(), 1) .build() + override val useNewQueryEndpoint = true + // Site changed from MangaThemesia to HeanCms. override val versionId = 2 override val coverPath = "" + + override fun getGenreList() = listOf( + Genre("Romance", 1), + Genre("Drama", 2), + Genre("Fantasy", 3), + Genre("Hardcore", 4), + Genre("SM", 5), + Genre("Harem", 8), + Genre("Hypnosis", 9), + Genre("Novel Adaptation", 10), + Genre("Netori", 11), + Genre("Netorare", 12), + Genre("Isekai", 13), + Genre("Yuri", 14), + Genre("MILF", 16), + Genre("Office", 17), + Genre("Short Story", 18), + Genre("Comedy", 19), + Genre("Campus", 20), + Genre("Crime", 21), + Genre("Revenge", 22), + Genre("Supernatural", 23), + Genre("Action", 24), + Genre("Military", 25), + Genre("Ability", 26), + Genre("Cohabitation", 27), + Genre("Training", 28), + ) } diff --git a/multisrc/overrides/heancms/perfscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/heancms/perfscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..acb868c34f Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/heancms/perfscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/heancms/perfscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..164f6611a0 Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/heancms/perfscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/heancms/perfscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4cf6669f30 Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/heancms/perfscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/heancms/perfscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..74c2a6dc5a Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/heancms/perfscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/heancms/perfscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fee5511710 Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/heancms/perfscan/res/web_hi_res_512.png b/multisrc/overrides/heancms/perfscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..dfdba5083f Binary files /dev/null and b/multisrc/overrides/heancms/perfscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/heancms/perfscan/src/PerfScan.kt b/multisrc/overrides/heancms/perfscan/src/PerfScan.kt new file mode 100644 index 0000000000..00e8b879a4 --- /dev/null +++ b/multisrc/overrides/heancms/perfscan/src/PerfScan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.fr.perfscan + +import eu.kanade.tachiyomi.multisrc.heancms.HeanCms +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient + +class PerfScan : HeanCms("Perf Scan", "https://perf-scan.fr", "fr") { + override val client: OkHttpClient = super.client.newBuilder() + .rateLimitHost(apiUrl.toHttpUrl(), 1, 2) + .build() + + override val coverPath: String = "" + override val useNewQueryEndpoint = true + + override val slugStrategy = SlugStrategy.ID +} diff --git a/multisrc/overrides/heancms/reaperscans/src/ReaperScans.kt b/multisrc/overrides/heancms/reaperscans/src/ReaperScans.kt index 8257e9a7c9..77122a85bd 100644 --- a/multisrc/overrides/heancms/reaperscans/src/ReaperScans.kt +++ b/multisrc/overrides/heancms/reaperscans/src/ReaperScans.kt @@ -21,7 +21,8 @@ class ReaperScans : HeanCms( // Site changed from Madara to HeanCms. override val versionId = 2 - override val fetchAllTitles = true + override val slugStrategy = SlugStrategy.FETCH_ALL + override val useNewQueryEndpoint = true override val coverPath: String = "" diff --git a/multisrc/overrides/madara/templescan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/heancms/templescan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/heancms/templescan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/templescan/res/web_hi_res_512.png b/multisrc/overrides/heancms/templescan/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/templescan/res/web_hi_res_512.png rename to multisrc/overrides/heancms/templescan/res/web_hi_res_512.png diff --git a/multisrc/overrides/heancms/templescan/src/TempleScan.kt b/multisrc/overrides/heancms/templescan/src/TempleScan.kt new file mode 100644 index 0000000000..1470f122f3 --- /dev/null +++ b/multisrc/overrides/heancms/templescan/src/TempleScan.kt @@ -0,0 +1,35 @@ +package eu.kanade.tachiyomi.extension.en.templescan + +import eu.kanade.tachiyomi.multisrc.heancms.Genre +import eu.kanade.tachiyomi.multisrc.heancms.HeanCms +import eu.kanade.tachiyomi.network.interceptor.rateLimit + +class TempleScan : HeanCms("Temple Scan", "https://templescan.net", "en") { + + override val versionId = 3 + + override val client = super.client.newBuilder() + .rateLimit(1) + .build() + + override val useNewQueryEndpoint = true + override val coverPath = "" + override val mangaSubDirectory = "comic" + + override fun getGenreList() = listOf( + Genre("Drama", 1), + Genre("Josei", 2), + Genre("Romance", 3), + Genre("Girls Love", 4), + Genre("Reincarnation", 5), + Genre("Fantasia", 6), + Genre("Ecchi", 7), + Genre("Adventure", 8), + Genre("Boys Love", 9), + Genre("School Life", 10), + Genre("Action", 11), + Genre("Military", 13), + Genre("Comedy", 14), + Genre("Apocalypse", 15), + ) +} diff --git a/multisrc/overrides/heancms/yugenmangas/src/YugenMangas.kt b/multisrc/overrides/heancms/yugenmangas/src/YugenMangas.kt index 8aa9f7310d..ce8e5993fb 100644 --- a/multisrc/overrides/heancms/yugenmangas/src/YugenMangas.kt +++ b/multisrc/overrides/heancms/yugenmangas/src/YugenMangas.kt @@ -1,26 +1,96 @@ package eu.kanade.tachiyomi.extension.es.yugenmangas +import android.app.Application +import android.content.SharedPreferences import eu.kanade.tachiyomi.multisrc.heancms.Genre import eu.kanade.tachiyomi.multisrc.heancms.HeanCms -import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.ProtocolException +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.IOException import java.text.SimpleDateFormat import java.util.TimeZone import java.util.concurrent.TimeUnit -class YugenMangas : HeanCms( - "YugenMangas", - "https://yugenmangas.net", - "es", - "https://api.yugenmangas.net", -) { +class YugenMangas : + HeanCms( + "YugenMangas", + "https://yugenmangas.net", + "es", + "https://api.yugenmangas.net", + ) { // Site changed from Madara to HeanCms. override val versionId = 2 + override val baseUrl by lazy { getPrefBaseUrl() } + + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private var lastDomain = "" + + private fun domainChangeIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (!request.url.host.startsWith("yugenmangas")) { + return chain.proceed(request) + } + + if (lastDomain.isNotEmpty()) { + val newUrl = request.url.newBuilder() + .host(preferences.baseUrlHost) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + + val response = try { + chain.proceed(request) + } catch (e: ProtocolException) { + if (e.message?.contains("Too many follow-up requests") == true) { + throw IOException("No se pudo obtener la nueva URL del sitio") + } else { + throw e + } + } + + if (request.url.host == response.request.url.host) return response + + response.close() + + preferences.baseUrlHost = response.request.url.host + + lastDomain = request.url.host + + val newUrl = request.url.newBuilder() + .host(response.request.url.host) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + + override val slugStrategy = SlugStrategy.ID + override val useNewQueryEndpoint = true + override val client = super.client.newBuilder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(90, TimeUnit.SECONDS) - .rateLimit(1, 1) + .rateLimitHost(apiUrl.toHttpUrl(), 2, 3) + .addInterceptor(::domainChangeIntercept) .build() override val coverPath: String = "" @@ -79,4 +149,17 @@ class YugenMangas : HeanCms( Genre("Yaoi", 43), Genre("Yuri", 44), ) + + companion object { + private const val defaultBaseUrlHost = "yugenmangas.net" + private const val BASE_URL_PREF = "prefOverrideBaseUrl" + } + + private var SharedPreferences.baseUrlHost + get() = getString(BASE_URL_PREF, defaultBaseUrlHost) ?: defaultBaseUrlHost + set(newHost) { + edit().putString(BASE_URL_PREF, newHost).commit() + } + + private fun getPrefBaseUrl(): String = preferences.baseUrlHost.let { "https://$it" } } diff --git a/multisrc/overrides/libgroup/hentailib/AndroidManifest.xml b/multisrc/overrides/libgroup/hentailib/AndroidManifest.xml index 543d49e63c..29cb390940 100644 --- a/multisrc/overrides/libgroup/hentailib/AndroidManifest.xml +++ b/multisrc/overrides/libgroup/hentailib/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - try { - val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() - Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } + val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой." + Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show() + true } }.let(screen::addPreference) } @@ -251,6 +246,6 @@ class HentaiLib : LibGroup("HentaiLib", "https://v1.hentailib.org", "ru") { const val PREFIX_SLUG_SEARCH = "slug:" private const val DOMAIN_TITLE = "Домен" - private const val DOMAIN_DEFAULT = "https://v1.hentailib.org" + private const val DOMAIN_DEFAULT = "https://hentailib.me" } } diff --git a/multisrc/overrides/libgroup/mangalib/AndroidManifest.xml b/multisrc/overrides/libgroup/mangalib/AndroidManifest.xml index ed72c6ca1e..f2f30eed68 100644 --- a/multisrc/overrides/libgroup/mangalib/AndroidManifest.xml +++ b/multisrc/overrides/libgroup/mangalib/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + - try { - val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() - Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } + val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой." + Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show() + true } }.let(screen::addPreference) } diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b7265edbed..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3b2b356e76..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 619a03792d..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ad77c4f45a..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 101ac61d6c..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/adultpainfulnightz/res/web_hi_res_512.png b/multisrc/overrides/madara/adultpainfulnightz/res/web_hi_res_512.png deleted file mode 100644 index 5c7b5998fb..0000000000 Binary files a/multisrc/overrides/madara/adultpainfulnightz/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/aiyumanga/src/AiYuManga.kt b/multisrc/overrides/madara/aiyumanga/src/AiYuManga.kt deleted file mode 100644 index 83ad2347b8..0000000000 --- a/multisrc/overrides/madara/aiyumanga/src/AiYuManga.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.aiyumanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class AiYuManga : Madara( - "AiYuManga", - "https://aiyumangascanlation.com", - "es", - SimpleDateFormat("MM/dd/yyyy", Locale("es")), -) { - override val useNewChapterEndpoint = true - override val chapterUrlSuffix = "" - - override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Status) > div.summary-content" -} diff --git a/multisrc/overrides/madara/akimanga/src/Akimanga.kt b/multisrc/overrides/madara/akimanga/src/Akimanga.kt new file mode 100644 index 0000000000..e831ade1d7 --- /dev/null +++ b/multisrc/overrides/madara/akimanga/src/Akimanga.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.akimanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class Akimanga : Madara( + "Akimangá", + "https://akimanga.com", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/akumanga/AkuManga.kt b/multisrc/overrides/madara/akumanga/AkuManga.kt new file mode 100644 index 0000000000..b7fee1faf1 --- /dev/null +++ b/multisrc/overrides/madara/akumanga/AkuManga.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.akumanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class AkuManga : Madara("AkuManga", "https://akumanga.com", "en") { + + override val id: Long = 107810123708352143 + + override val chapterUrlSuffix = "" +} diff --git a/multisrc/overrides/madara/aleatoriascan/src/AleatoriaScan.kt b/multisrc/overrides/madara/aleatoriascan/src/AleatoriaScan.kt deleted file mode 100644 index d941001750..0000000000 --- a/multisrc/overrides/madara/aleatoriascan/src/AleatoriaScan.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.aleatoriascan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class AleatoriaScan : Madara( - "Aleatória Scan", - "https://aleatoriascan.xyz", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/alnscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/alnscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4a51a9c160..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/alnscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/alnscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d7a3354a58..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/alnscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/alnscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 96fd0e51cb..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/alnscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/alnscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 28302bf8d6..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/alnscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/alnscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 57e4d24078..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/alnscans/res/web_hi_res_512.png b/multisrc/overrides/madara/alnscans/res/web_hi_res_512.png deleted file mode 100644 index 19d9942fb0..0000000000 Binary files a/multisrc/overrides/madara/alnscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/anisamanga/src/AnisaManga.kt b/multisrc/overrides/madara/anisamanga/src/AnisaManga.kt deleted file mode 100644 index eaa2547ca0..0000000000 --- a/multisrc/overrides/madara/anisamanga/src/AnisaManga.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.anisamanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.Headers - -class AnisaManga : Madara("Anisa Manga", "https://anisamanga.com", "tr") { - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", "https://anisamanga.com") -} diff --git a/multisrc/overrides/madara/anshscans/src/AnshScans.kt b/multisrc/overrides/madara/anshscans/src/AnshScans.kt index 1f786bec92..e17bead31a 100644 --- a/multisrc/overrides/madara/anshscans/src/AnshScans.kt +++ b/multisrc/overrides/madara/anshscans/src/AnshScans.kt @@ -6,4 +6,6 @@ class AnshScans : Madara("Ansh Scans", "https://anshscans.org", "en") { override val useNewChapterEndpoint = true override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override val mangaSubString = "comic" } diff --git a/multisrc/overrides/madara/aquamanga/src/AquaManga.kt b/multisrc/overrides/madara/aquamanga/src/AquaManga.kt index 736666b417..aad4ee449c 100644 --- a/multisrc/overrides/madara/aquamanga/src/AquaManga.kt +++ b/multisrc/overrides/madara/aquamanga/src/AquaManga.kt @@ -1,20 +1,48 @@ package eu.kanade.tachiyomi.extension.en.aquamanga +import android.util.Base64 import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.Headers +import kotlin.random.Random class AquaManga : Madara("Aqua Manga", "https://aquamanga.com", "en") { - override val client = super.client.newBuilder() - .rateLimit(1, 3) // 1 request per 3 seconds - .build() - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .set("Origin", baseUrl) - .set("Sec-Fetch-Dest", "empty") - .set("Sec-Fetch-Mode", "cors") - .set("Sec-Fetch-Site", "same-origin") + .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") + .add("Accept-Language", "en-US,en;q=0.5") + .add("Referer", "$baseUrl/") + .add("Sec-Fetch-Dest", "document") + .add("Sec-Fetch-Mode", "navigate") + .add("Sec-Fetch-Site", "same-origin") + .add("Upgrade-Insecure-Requests", "1") + .add("X-Requested-With", randomValue) + + private val littleBitCursedEncodedValue = "ICAgSUFBZ0FFa0FRd0JCQUdjQVNRQkRBRUVBWndCUkFGVUFUZ0JDQUZFQVZRQnNBRUlBVVFCWEFHUUFRZ0JSQURBQVJnQkNBRk1BVlFCR0FFSUFXZ0F3QUVZQVJBQlJBRlVBUmdCVUFGVUFWUUJLQUVVQVVRQlZBRllBUmdCUkFGWUFjQUF6QUZFQWF3QndBRUlBVWdCVkFERUFRZ0JWQUZZQVJnQkRBR0lBYXdCR0FFWUFZUUF3QUVZQVVnQmtBREFBU2dCREFGRUFWUUJrQUdvQVVRQldBRTRBVWdCUkFHc0FVZ0JDQUZJQVZRQnNBRUlBVlFBeUFHUUFRd0JWQUdzQVJnQkhBRllBVlFCR0FGTUFXZ0F3QUVvQU1RQlJBRlVBV2dCR0FGRUFWZ0JhQUZJQVVRQnJBRGtBUWdCU0FGVUFiQUJ" + + "DQUZZQVZnQkdBRU1BVmdBd0FFWUFSZ0JPQUVVQVJnQldBRm9BTUFCS0FGTUFVUUJWQUdRQWVnQlJBRllBVmdCdUFGRUFhd0JPQUVJQVVnQnJBR3dBUWdCV0FHd0FSZ0JEQUZZQU1BQkdBRVlBVXdCVkFFWUFWd0JrQURBQVNnQXhBRkVBVlFCa0FGSUFVUUJXQUVZQU13QlJBR3dBVWdCQ0FGSUFNd0JPQUVJQVZRQnRBR1FBUXdCU0FEQUFSZ0JIQUZVQVZRQkdBRmNBVlFCVkFFb0FTQUJSQUZVQVdnQktBRkVBVmdCYUFGSUFVUUJzQUZvQVFnQlNBRmNBT1FCQ0FGb0FSZ0JHQUVNQVZRQnJBRVlBUndCV0FGVUFSZ0JYQUZvQU1BQktBRFVBVVFCVkFGb0FSZ0JSQUZZQVdnQnVBRkVBYXdCa0FFSUFVZ0JGQURFQVFnQldBRllB" + + "UmdCREFHTUFhd0JHQUVZQVlnQXdBRVlBVWdCYUFEQUFTZ0JVQUZFQVZRQlNBRW9BVVFCV0FGSUFiZ0JSQUdzQVRnQkNBRklBYkFCc0FFSUFWQUJXQUVZQVF3QlNBREFBUmdCR0FGTUFWUUJHQUdFQVZRQlZBRW9BVndCUkFGVUFWZ0JhQUZFQVZnQktBRklBVVFCdEFHZ0FRZ0JTQUVVQVJnQkNBRlVBYlFCa0FFTUFZd0JyQUVZQVJ3QlNBRlVBUmdCWEFGVUFWUUJLQUV3QVVRQlZBRlVBTUFCUkFGWUFWZ0JTQUZFQWJBQmFBRUlBVWdBd0FERUFRZ0JhQUVnQVpBQkRBRlVBYXdCR0FFY0FWd0JWQUVZQVZBQmFBREFBU2dBeEFGRUFWUUJhQUVZQVVRQlhBRVlBYmdCUkFHc0FaQUJDQUZJQVZRQnNBRUlBVmdCWEFHUUFRd0Jr" + + "QUVVQVJnQkZBR0VBTUFCR0FGSUFXZ0F3QUVvQVZ3QlJBRlVBVWdCQ0FGRUFWZ0JLQUc0QVVRQnJBRklBUWdCU0FHc0FNUUJDQUZRQVZRQkdBRU1BVWdBd0FFWUFSZ0JoQURBQVJnQlhBR1FBTUFCS0FGY0FVUUJWQUZZQVdnQlJBRllBWkFCdUFGRUFiQUJhQUVJQVVnQnNBRllBUWdCVkFESUFaQUJEQUZjQWF3QkdBRWNBVWdCVkFFWUFWd0JWQUZVQVN" + + "nQm9BRkVBVlFCV0FGb0FVUUJXQUZZQVVnQlJBR3dBYUFCQ0FGSUFhd0JzQUVJQVZnQlhBR1FBUXdCVkFHc0FSZ0JJQUdRQU1BQkdBR29BVVFCVkFFb0FSQUJSQUZVQVdnQktBRkVBVmdCS0FGSUFVUUJ1QUU0QVFnQlNBRlVBYkFCQ0FGWUFNUUJHQUVNQVZnQnJBRVlBUmdCWEFGVUFSZ0JTQUdRQU1BQktBRkFBVVFCVkFGb0FWZ0JSQUZZQVNnQnVBRkVBYXdCc0FFSUFVZ0JyQURFQVFnQldBR3dBUmdCREFGSUFNQUJHQUVZQVRnQkZBRVlBV0FCYUFEQUFSZ0F6QUZFQVZRQldBRm9BVVFCVkFEVUFRZ0JSQUd3QVNnQkNBRklBYkFCV0FFSUFWd0JyQUVZQVFnQmxBR3NBUmdCSEFGSUFWUUJHQUZjQVdnQXdBRW9BVUFCUkF" + + "GVUFXZ0JLQUZFQVZnQldBRklBVVFCdUFFb0FRZ0JTQUdzQWJBQkNBRlVBVndCa0FFTUFWUUF3QUVZQVJ3QldBRlVBUmdCWEFGb0FNQUJLQUVRQVVRQlZBRm9BYWdCUkFGY0FTZ0J1QUZFQWJRQjBBRUlBVWdCVkFERUFRZ0JXQUZnQVpBQkRBR01BYXdCR0FFWUFWd0JWQUVZQVV3QmFBREFBU2dCV0FGRUFWUUJhQUZZQVVRQldBRW9BYmdCUkFHd0FUZ0JDQUZJQWJBQldBRUlBVmdCc0FFWUFRd0JUQURBQVJnQkpBRllBVlFCR0FGWUFWUUJWQUVvQVZ3QlJBRlVBV2dCYUFGRUFWd0JPQUc0QVVRQnNBRW9BUWdCU0FHd0FiQUJDQUZVQWJRQmtBRUlBWlFCckFFWUFSd0JTQUZVQVJnQm9BR1FBTUFCS0FFd0FVUUJWQUZZQVNn" + + "QlJBRllBVmdCdUFGRUFXQUJzQUVJQVVnQlVBRklBUWdCVkFGY0FaQUJEQUZZQVJRQkdBRWNBVmdCVkFFWUFVd0JhQURBQVNnQkVBRkVBVlFCYUFIWUFVUUJWQURFQVFnQlJBR3NBWkFCQ0FGSUFWZ0JHQUVJQVZnQldBRVlBUXdCV0FHc0FSZ0JHQUZjQVZRQkdBRlFBV2dBd0FFb0FVd0JSQUZVQVdnQldBRkVBVmdCS0FHNEFVUUJ1QUZZQVFnQlNBR3NBVmdCQ0FGWUFiQUJHQUVNQVVnQnJBRVlBUlFCaEFEQUFSZ0JXQUZFQVZRQktBRlVBVVFCVkFGWUFSZ0JSQUZZQWNBQXpBRkVBYXdCd0FFSUFVZ0JWQURFQVFnQlZBRllBUmdCREFHSUFhd0JHQUVZQVlRQXdBRVlBVWdCa0FEQUFTZ0JEQUZFQVZRQmtBR29BVVFCV0FF" + + "NEFVZ0JSQUdzQVNnQkNBRklBUkFCQ0FFSUFWUUJHQUVZQVFnQmFBREFBUmdCRUFGRUFWUUJHQUVvQVVRQlZBRVlBYmdCUkFGVUFUZ0JDQUZFQVZRQnNBRUlBVVFCWEFHTUFad0JKQUVNQVFRQm5BRWtBUXdCQkFEMEFJQUFnQUNBQUlBQT0gICA=" + + private fun getRandomSubstring(input: String, length: Int): String { + val startIndex = (0 until input.length - length + 1).random() + return input.substring(startIndex, startIndex + length) + } + + private val randomLength = Random.Default.nextInt(13, 21) + + private val decodedString = Base64.decode(littleBitCursedEncodedValue, Base64.DEFAULT).toString(Charsets.UTF_8).trim() + + private val randomStringValue = getRandomSubstring(decodedString, randomLength) + + private val chromiumBrowserValue = "org.chromium.chrome" + + private val randomValue = when { + Random.nextInt(1, 11) == 1 -> chromiumBrowserValue // 10% chance + else -> randomStringValue // 90% chance + } override val chapterUrlSuffix = "" } diff --git a/multisrc/overrides/madara/archerscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/archerscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..2a2eaed8c6 Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/archerscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/archerscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..0466e8330b Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/archerscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/archerscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8b066144fd Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/archerscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/archerscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7a0a2d993d Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/archerscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/archerscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fb30df9b01 Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/archerscans/res/web_hi_res_512.png b/multisrc/overrides/madara/archerscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..d97452a259 Binary files /dev/null and b/multisrc/overrides/madara/archerscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/archerscans/src/ArcheRScans.kt b/multisrc/overrides/madara/archerscans/src/ArcheRScans.kt new file mode 100644 index 0000000000..aa0cca8edd --- /dev/null +++ b/multisrc/overrides/madara/archerscans/src/ArcheRScans.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.archerscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ArcheRScans : Madara("ArcheR Scans", "https://www.archerscans.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/astrumscans/src/AstrumScans.kt b/multisrc/overrides/madara/astrumscans/src/AstrumScans.kt new file mode 100644 index 0000000000..5c994d76aa --- /dev/null +++ b/multisrc/overrides/madara/astrumscans/src/AstrumScans.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.pt.astrumscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class AstrumScans : Madara( + "Astrum Scans", + "https://astrumscans.xyz", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override val mangaSubString = "series" +} diff --git a/multisrc/overrides/madara/asurascansus/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/asurascansus/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9866f55dbb Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/asurascansus/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/asurascansus/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4ef6d79367 Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/asurascansus/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/asurascansus/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e0a3e88406 Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/asurascansus/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/asurascansus/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2da953a2f6 Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/asurascansus/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/asurascansus/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7516a2dba6 Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/asurascansus/res/web_hi_res_512.png b/multisrc/overrides/madara/asurascansus/res/web_hi_res_512.png new file mode 100644 index 0000000000..688ccf4f03 Binary files /dev/null and b/multisrc/overrides/madara/asurascansus/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/asurascansus/src/AsuraScansUs.kt b/multisrc/overrides/madara/asurascansus/src/AsuraScansUs.kt new file mode 100644 index 0000000000..5e1b63b87f --- /dev/null +++ b/multisrc/overrides/madara/asurascansus/src/AsuraScansUs.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.asurascansus + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class AsuraScansUs : Madara("Asura Scans.us (unoriginal)", "https://asurascans.us", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/atikrost/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/atikrost/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0aed4d1604..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/atikrost/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bea5b60769..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/atikrost/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 00f26184b5..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/atikrost/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0c00c7f52d..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/atikrost/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a1d193c2b8..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/res/web_hi_res_512.png b/multisrc/overrides/madara/atikrost/res/web_hi_res_512.png deleted file mode 100644 index 61e4b69a33..0000000000 Binary files a/multisrc/overrides/madara/atikrost/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/atikrost/src/Atikrost.kt b/multisrc/overrides/madara/atikrost/src/Atikrost.kt deleted file mode 100644 index f20e4eebd2..0000000000 --- a/multisrc/overrides/madara/atikrost/src/Atikrost.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.atikrost - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class Atikrost : Madara("Atikrost", "https://atikrost.com", "tr", SimpleDateFormat("MMMM dd, yyyy", Locale("tr"))) diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/babelwuxia/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/babelwuxia/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/babelwuxia/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/babelwuxia/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/babelwuxia/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/babelwuxia/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/babelwuxia/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/babelwuxia/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/babelwuxia/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/babelwuxia/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/babelwuxia/res/web_hi_res_512.png b/multisrc/overrides/madara/babelwuxia/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mangathemesia/babelwuxia/res/web_hi_res_512.png rename to multisrc/overrides/madara/babelwuxia/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/babelwuxia/src/BabelWuxia.kt b/multisrc/overrides/madara/babelwuxia/src/BabelWuxia.kt new file mode 100644 index 0000000000..94c3859843 --- /dev/null +++ b/multisrc/overrides/madara/babelwuxia/src/BabelWuxia.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.extension.en.babelwuxia + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import okhttp3.FormBody +import okhttp3.Request + +class BabelWuxia : Madara("Babel Wuxia", "https://read.babelwuxia.com", "en") { + + // moved from MangaThemesia + override val versionId = 2 + + override val useNewChapterEndpoint = true + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + + private fun madaraLoadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "right") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[meta_query][0][paged]", "1") + add("vars[meta_query][0][orderby]", "meta_value_num") + add("vars[meta_query][0][template]", "archive") + add("vars[meta_query][0][sidebar]", "right") + add("vars[meta_query][0][post_type]", "wp-manga") + add("vars[meta_query][0][post_status]", "publish") + add("vars[meta_query][0][meta_key]", metaKey) + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "default") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return madaraLoadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return madaraLoadMoreRequest(page - 1, "_latest_update") + } +} diff --git a/multisrc/overrides/madara/bananamanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/bananamanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f84457b58f Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/bananamanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/bananamanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c242b942b1 Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/bananamanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/bananamanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..7dcd02bb8b Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/bananamanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/bananamanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2e0a5c8daf Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/bananamanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/bananamanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6800241dbb Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/bananamanga/res/web_hi_res_512.png b/multisrc/overrides/madara/bananamanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..e07dd40317 Binary files /dev/null and b/multisrc/overrides/madara/bananamanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/bananamanga/src/BananaManga.kt b/multisrc/overrides/madara/bananamanga/src/BananaManga.kt new file mode 100644 index 0000000000..dcadac1e96 --- /dev/null +++ b/multisrc/overrides/madara/bananamanga/src/BananaManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.bananamanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class BananaManga : Madara("Banana Manga", "https://bananamanga.net", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/barmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/barmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..2d70520758 Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/barmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/barmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b8329e9ca5 Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/barmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/barmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..902e1347de Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/barmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/barmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f68cd3571e Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/barmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/barmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dc81d0d27f Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/barmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/barmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..ccc6ce08b3 Binary files /dev/null and b/multisrc/overrides/madara/barmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/barmanga/src/BarManga.kt b/multisrc/overrides/madara/barmanga/src/BarManga.kt new file mode 100644 index 0000000000..17c6d91e57 --- /dev/null +++ b/multisrc/overrides/madara/barmanga/src/BarManga.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.extension.es.barmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import okhttp3.FormBody +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale + +class BarManga : Madara( + "BarManga", + "https://barmanga.com", + "es", + SimpleDateFormat("MM/dd/yyyy", Locale.ROOT), +) { + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + override val mangaDetailsSelectorDescription = "div.flamesummary > div.manga-excerpt" + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "full") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[order]", "desc") + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "big_thumbnail") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } +} diff --git a/multisrc/overrides/madara/bichentraducoes/src/BichenTraducoes.kt b/multisrc/overrides/madara/bichentraducoes/src/BichenTraducoes.kt deleted file mode 100644 index e5aac81ffa..0000000000 --- a/multisrc/overrides/madara/bichentraducoes/src/BichenTraducoes.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.bichentraducoes - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class BichenTraducoes : Madara( - "Bichen Traduções", - "https://bichentraducoes.com", - "pt-BR", - SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/birdtoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/birdtoon/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..aff88e0252 Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/birdtoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/birdtoon/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9fd3c51556 Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/birdtoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/birdtoon/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1a21fd20b1 Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/birdtoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/birdtoon/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7b06cb268a Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/birdtoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/birdtoon/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e11f6feccf Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/birdtoon/res/web_hi_res_512.png b/multisrc/overrides/madara/birdtoon/res/web_hi_res_512.png new file mode 100644 index 0000000000..7f6a66fb2f Binary files /dev/null and b/multisrc/overrides/madara/birdtoon/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/birdtoon/src/BirdToon.kt b/multisrc/overrides/madara/birdtoon/src/BirdToon.kt new file mode 100644 index 0000000000..59cb32059c --- /dev/null +++ b/multisrc/overrides/madara/birdtoon/src/BirdToon.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.id.birdtoon + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class BirdToon : Madara("BirdToon", "https://birdtoon.net", "id") { + override val mangaSubString = "komik" + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/blmanhwaclub/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3a5842f3e8..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/blmanhwaclub/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 11d0f98266..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6f10b3b000..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 31b191f20f..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b6872a5cd1..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/res/web_hi_res_512.png b/multisrc/overrides/madara/blmanhwaclub/res/web_hi_res_512.png deleted file mode 100644 index 93eea3093d..0000000000 Binary files a/multisrc/overrides/madara/blmanhwaclub/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/blmanhwaclub/src/BlManhwaClub.kt b/multisrc/overrides/madara/blmanhwaclub/src/BlManhwaClub.kt deleted file mode 100644 index f08f396f2b..0000000000 --- a/multisrc/overrides/madara/blmanhwaclub/src/BlManhwaClub.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.blmanhwaclub - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class BlManhwaClub : Madara( - "BL Manhwa Club", - "https://blmanhwa.club", - "pt-BR", - SimpleDateFormat("dd MMM yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/bokugentranslation/src/BokugenTranslation.kt b/multisrc/overrides/madara/bokugentranslation/src/BokugenTranslation.kt index f45bd0d4b4..4e04436099 100644 --- a/multisrc/overrides/madara/bokugentranslation/src/BokugenTranslation.kt +++ b/multisrc/overrides/madara/bokugentranslation/src/BokugenTranslation.kt @@ -14,7 +14,6 @@ import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit class BokugenTranslation : Madara( "BokugenTranslation", @@ -23,8 +22,7 @@ class BokugenTranslation : Madara( dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), ) { private var loadWebView = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(uaIntercept) + override val client: OkHttpClient = super.client.newBuilder() .addInterceptor { chain -> val request = chain.request() val url = request.url.toString() @@ -56,8 +54,6 @@ class BokugenTranslation : Madara( } chain.proceed(request) } - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) .rateLimit(1, 1) .build() diff --git a/multisrc/overrides/madara/cerisescans/src/CeriseScan.kt b/multisrc/overrides/madara/cerisescans/src/CeriseScan.kt new file mode 100644 index 0000000000..5c99fcdce6 --- /dev/null +++ b/multisrc/overrides/madara/cerisescans/src/CeriseScan.kt @@ -0,0 +1,45 @@ +package eu.kanade.tachiyomi.extension.pt.cerisescans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.OkHttpClient +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class CeriseScan : Madara( + "Cerise Scan", + "https://cerisescan.com", + "pt-BR", + SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), +) { + + // Name changed from 'Cerise Scans' to 'Cerise Scan' + override val id: Long = 8629915907358523454 + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET(baseUrl + manga.url.replace("/home1", ""), headers) + } + + override fun chapterListRequest(manga: SManga): Request { + return GET(baseUrl + manga.url.replace("/home1", ""), headers) + } + + override fun pageListRequest(chapter: SChapter): Request { + if (chapter.url.startsWith("http")) { + return GET(chapter.url.replace("/home1", ""), headers) + } + + return GET(baseUrl + chapter.url.replace("/home1", ""), headers) + } +} diff --git a/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt b/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt deleted file mode 100644 index 53d0ebbcec..0000000000 --- a/multisrc/overrides/madara/cerisescans/src/CeriseScans.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.cerisescans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class CeriseScans : Madara( - "Cerise Scans", - "https://cerisescans.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/cocorip/res/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/cocorip/res/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..c5077ea895 Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/cocorip/res/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/cocorip/res/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f3fb3ab36b Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/cocorip/res/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/cocorip/res/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..36e0034926 Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/cocorip/res/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/cocorip/res/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..02bb580dc6 Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/cocorip/res/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/cocorip/res/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3a8a486e67 Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/cocorip/res/web_hi_res_512.png b/multisrc/overrides/madara/cocorip/res/web_hi_res_512.png new file mode 100644 index 0000000000..50f591bdcc Binary files /dev/null and b/multisrc/overrides/madara/cocorip/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/cocorip/src/CocoRip.kt b/multisrc/overrides/madara/cocorip/src/CocoRip.kt new file mode 100644 index 0000000000..d9f79856cb --- /dev/null +++ b/multisrc/overrides/madara/cocorip/src/CocoRip.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.es.cocorip + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class CocoRip : Madara("Coco Rip", "https://cocorip.net", "es", SimpleDateFormat("dd/MM/yyyy", Locale("es"))) { + override val mangaDetailsSelectorDescription = "div.summary__content" +} diff --git a/multisrc/overrides/madara/coffeemanga/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemanga/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/coffeemanga/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/coffeemanga/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemanga/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/coffeemanga/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/coffeemanga/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemanga/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/coffeemanga/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/coffeemanga/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemanga/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/coffeemanga/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/coffeemanga/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemanga/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/coffeemanga/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/coffeemanga/web_hi_res_512.png b/multisrc/overrides/madara/coffeemanga/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/coffeemanga/web_hi_res_512.png rename to multisrc/overrides/madara/coffeemanga/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/coffeemanga/src/CoffeeManga.kt b/multisrc/overrides/madara/coffeemanga/src/CoffeeManga.kt new file mode 100644 index 0000000000..dd652e1985 --- /dev/null +++ b/multisrc/overrides/madara/coffeemanga/src/CoffeeManga.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.en.coffeemanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import org.jsoup.nodes.Element + +class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun imageFromElement(element: Element): String? { + return when { + element.hasAttr("data-src") && element.attr("data-src").isNotEmpty() -> element.attr("abs:data-src") + element.hasAttr("data-lazy-src") && element.attr("data-lazy-src").isNotEmpty() -> element.attr("abs:data-lazy-src") + element.hasAttr("srcset") && element.attr("srcset").isNotEmpty() -> element.attr("abs:srcset").substringBefore(" ") + else -> element.attr("abs:src") + } + } +} diff --git a/multisrc/overrides/madara/coffeemangatop/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemangatop/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..bd0218e9ce Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemangatop/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a9af109203 Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..067e5bfd10 Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4ddb41385c Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7bc1fae4e1 Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/res/web_hi_res_512.png b/multisrc/overrides/madara/coffeemangatop/res/web_hi_res_512.png new file mode 100644 index 0000000000..dae6bccb46 Binary files /dev/null and b/multisrc/overrides/madara/coffeemangatop/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/coffeemangatop/src/CoffeeMangaTop.kt b/multisrc/overrides/madara/coffeemangatop/src/CoffeeMangaTop.kt new file mode 100644 index 0000000000..76b8b2ae0c --- /dev/null +++ b/multisrc/overrides/madara/coffeemangatop/src/CoffeeMangaTop.kt @@ -0,0 +1,64 @@ +package eu.kanade.tachiyomi.extension.en.coffeemangatop + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class CoffeeMangaTop : Madara( + "CoffeeManga.top (unoriginal)", + "https://coffeemanga.top", + "en", + dateFormat = SimpleDateFormat("MMM dd, HH:mm", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = "search?page=$page" + + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/popular-manga?page=$page", headers) + + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/latest-manga?page=$page", headers) + + // Copied from IsekaiScan.top (unoriginal) + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + val chaptersWrapper = document.select("div[id^=manga-chapters-holder]") + + var chapterElements = document.select(chapterListSelector()) + + if (chapterElements.isEmpty() && !chaptersWrapper.isNullOrEmpty()) { + val mangaId = chaptersWrapper.attr("data-id") + val xhrHeaders = headersBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() + val xhrRequest = GET("$baseUrl/ajax-list-chapter?mangaID=$mangaId", xhrHeaders) + val xhrResponse = client.newCall(xhrRequest).execute() + + chapterElements = xhrResponse.asJsoup().select(chapterListSelector()) + xhrResponse.close() + } + + countViews(document) + return chapterElements.map(::chapterFromElement) + } + + // Copied from IsekaiScan.top (unoriginal) + override fun pageListParse(document: Document): List { + val stringArray = document.select("p#arraydata").text().split(",").toTypedArray() + return stringArray.mapIndexed { index, url -> + Page( + index, + document.location(), + url, + ) + } + } +} diff --git a/multisrc/overrides/madara/coloredmanga/src/ColoredManga.kt b/multisrc/overrides/madara/coloredmanga/src/ColoredManga.kt new file mode 100644 index 0000000000..be16c5d063 --- /dev/null +++ b/multisrc/overrides/madara/coloredmanga/src/ColoredManga.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.en.coloredmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class ColoredManga : Madara( + "Colored Manga", + "https://coloredmanga.com", + "en", + dateFormat = SimpleDateFormat("dd-MMM", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/comicarab/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/comicarab/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e9f3b50c33 Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicarab/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/comicarab/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..cf530cf62f Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicarab/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/comicarab/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b8be692b5c Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicarab/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/comicarab/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3bca922d90 Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicarab/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/comicarab/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2382dc7e93 Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicarab/res/web_hi_res_512.png b/multisrc/overrides/madara/comicarab/res/web_hi_res_512.png new file mode 100644 index 0000000000..41b33859ca Binary files /dev/null and b/multisrc/overrides/madara/comicarab/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/comicarab/src/ComicArab.kt b/multisrc/overrides/madara/comicarab/src/ComicArab.kt new file mode 100644 index 0000000000..6dc77be66a --- /dev/null +++ b/multisrc/overrides/madara/comicarab/src/ComicArab.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.ar.comicarab + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class ComicArab : Madara( + "كوميك العرب", + "https://comicarab.com", + "ar", + dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")), +) { + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/comicscans/src/ComicScans.kt b/multisrc/overrides/madara/comicscans/src/ComicScans.kt new file mode 100644 index 0000000000..910b11d707 --- /dev/null +++ b/multisrc/overrides/madara/comicscans/src/ComicScans.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.comicscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ComicScans : Madara("Comic Scans", "https://www.comicscans.org", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/comictoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/comictoon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cfe9de43b3..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/comictoon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 00afe09056..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/comictoon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 42324c4175..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/comictoon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 182779dee4..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/comictoon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5044b93039..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/res/web_hi_res_512.png b/multisrc/overrides/madara/comictoon/res/web_hi_res_512.png deleted file mode 100644 index f4914a0fd9..0000000000 Binary files a/multisrc/overrides/madara/comictoon/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/comictoon/src/Comictoon.kt b/multisrc/overrides/madara/comictoon/src/Comictoon.kt deleted file mode 100644 index 5f8c084345..0000000000 --- a/multisrc/overrides/madara/comictoon/src/Comictoon.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.th.comictoon - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class Comictoon : Madara("Comictoon", "https://comictoonthaith-new.com", "th", SimpleDateFormat("MMMMM dd, yyyy", Locale("th"))) diff --git a/multisrc/overrides/madara/comicznetv2/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/comicznetv2/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..2a512df276 Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicznetv2/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/comicznetv2/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d1b00a2858 Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicznetv2/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/comicznetv2/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2647035d8d Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicznetv2/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/comicznetv2/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9d70ba7551 Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicznetv2/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/comicznetv2/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..687733af37 Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/comicznetv2/res/web_hi_res_512.png b/multisrc/overrides/madara/comicznetv2/res/web_hi_res_512.png new file mode 100644 index 0000000000..081e777620 Binary files /dev/null and b/multisrc/overrides/madara/comicznetv2/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/comicznetv2/src/ComiczNetV2.kt b/multisrc/overrides/madara/comicznetv2/src/ComiczNetV2.kt new file mode 100644 index 0000000000..c04d7bf89a --- /dev/null +++ b/multisrc/overrides/madara/comicznetv2/src/ComiczNetV2.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.all.comicznetv2 + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ComiczNetV2 : Madara("Comicz.net v2", "https://v2.comiz.net", "all") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/cookietoon/src/CookieToon.kt b/multisrc/overrides/madara/cookietoon/src/CookieToon.kt deleted file mode 100644 index 7ce3fac90d..0000000000 --- a/multisrc/overrides/madara/cookietoon/src/CookieToon.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.cookietoon - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class CookieToon : Madara( - "CookieToon", - "https://cookietoon.online", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/cronosscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/cronosscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84879466d2..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/cronosscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f148ae0265..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/cronosscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1070c8d17a..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/cronosscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a66bd4b927..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/cronosscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2f60627045..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/res/web_hi_res_512.png b/multisrc/overrides/madara/cronosscan/res/web_hi_res_512.png deleted file mode 100644 index 9199c25d33..0000000000 Binary files a/multisrc/overrides/madara/cronosscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/cronosscan/src/CronosScan.kt b/multisrc/overrides/madara/cronosscan/src/CronosScan.kt deleted file mode 100644 index 8ed0a6aac0..0000000000 --- a/multisrc/overrides/madara/cronosscan/src/CronosScan.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.cronosscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class CronosScan : Madara( - "Cronos Scan", - "https://cronosscan.net", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/darkscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/darkscans/res/mipmap-hdpi/ic_launcher.png index afe14bd9ee..8dfb57b8f2 100644 Binary files a/multisrc/overrides/madara/darkscans/res/mipmap-hdpi/ic_launcher.png and b/multisrc/overrides/madara/darkscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/darkscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/darkscans/res/mipmap-mdpi/ic_launcher.png index 2ad66d1655..d4f3192f6a 100644 Binary files a/multisrc/overrides/madara/darkscans/res/mipmap-mdpi/ic_launcher.png and b/multisrc/overrides/madara/darkscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/darkscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/darkscans/res/mipmap-xhdpi/ic_launcher.png index a8f4d75581..b340f76218 100644 Binary files a/multisrc/overrides/madara/darkscans/res/mipmap-xhdpi/ic_launcher.png and b/multisrc/overrides/madara/darkscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/darkscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/darkscans/res/mipmap-xxhdpi/ic_launcher.png index a81d4c0a90..e3b07988c5 100644 Binary files a/multisrc/overrides/madara/darkscans/res/mipmap-xxhdpi/ic_launcher.png and b/multisrc/overrides/madara/darkscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/darkscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/darkscans/res/mipmap-xxxhdpi/ic_launcher.png index 6acd47a201..7411446760 100644 Binary files a/multisrc/overrides/madara/darkscans/res/mipmap-xxxhdpi/ic_launcher.png and b/multisrc/overrides/madara/darkscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/darkscans/res/web_hi_res_512.png b/multisrc/overrides/madara/darkscans/res/web_hi_res_512.png index a3c93a8525..012928faa7 100644 Binary files a/multisrc/overrides/madara/darkscans/res/web_hi_res_512.png and b/multisrc/overrides/madara/darkscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/darkscans/src/DarkScans.kt b/multisrc/overrides/madara/darkscans/src/DarkScans.kt new file mode 100644 index 0000000000..68470bd681 --- /dev/null +++ b/multisrc/overrides/madara/darkscans/src/DarkScans.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.en.darkscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class DarkScans : Madara("Dark Scans", "https://darkscans.com", "en") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4) + .build() + + override val mangaSubString = "all-series" + + override val filterNonMangaItems = false +} diff --git a/multisrc/overrides/madara/default/AndroidManifest.xml b/multisrc/overrides/madara/default/AndroidManifest.xml index ad44865c88..5d4316fb8e 100644 --- a/multisrc/overrides/madara/default/AndroidManifest.xml +++ b/multisrc/overrides/madara/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - \ No newline at end of file + diff --git a/multisrc/overrides/madara/default/additional.gradle b/multisrc/overrides/madara/default/additional.gradle index 06abd18c65..214a0df0ed 100644 --- a/multisrc/overrides/madara/default/additional.gradle +++ b/multisrc/overrides/madara/default/additional.gradle @@ -1,3 +1,4 @@ dependencies { - implementation(project(':lib-cryptoaes')) -} \ No newline at end of file + implementation(project(":lib-cryptoaes")) + implementation(project(":lib-randomua")) +} diff --git a/multisrc/overrides/madara/doodmanga/src/Doodmanga.kt b/multisrc/overrides/madara/doodmanga/src/Doodmanga.kt index 232fdfd2ac..682f5c672b 100644 --- a/multisrc/overrides/madara/doodmanga/src/Doodmanga.kt +++ b/multisrc/overrides/madara/doodmanga/src/Doodmanga.kt @@ -7,16 +7,12 @@ import okhttp3.OkHttpClient import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class Doodmanga : Madara("Doodmanga", "https://www.doodmanga.com", "th", SimpleDateFormat("dd MMMMM yyyy", Locale("th"))) { override val filterNonMangaItems = false - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(uaIntercept) + override val client: OkHttpClient = super.client.newBuilder() .addInterceptor(ScrambledImageInterceptor) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) .build() override val pageListParseSelector = "div.text-center > p > img, div.text-center > img, div.text-center > script" diff --git a/multisrc/overrides/madara/dragontea/src/DragonTea.kt b/multisrc/overrides/madara/dragontea/src/DragonTea.kt index d8502af460..fb784bb81f 100644 --- a/multisrc/overrides/madara/dragontea/src/DragonTea.kt +++ b/multisrc/overrides/madara/dragontea/src/DragonTea.kt @@ -1,20 +1,15 @@ package eu.kanade.tachiyomi.extension.en.dragontea -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect +import android.util.Base64 +import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Page -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.nodes.Document -import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.Locale @@ -25,7 +20,6 @@ class DragonTea : Madara( dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US), ) { override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(::begonepeconIntercept) .rateLimit(1) .build() @@ -41,86 +35,54 @@ class DragonTea : Madara( } } - private val begonepeconSelector: String = "div.begonepecon" - - private val peconholderSelector: String = "div.peconholder" + private val pageIndexRegex = Regex("""image-(\d+)[a-z]+""", RegexOption.IGNORE_CASE) override fun pageListParse(document: Document): List { - countViews(document) - - val hasSplitImages = document - .select(begonepeconSelector) - .firstOrNull() != null - - if (!hasSplitImages) { - return super.pageListParse(document) + val dataId = document.selectFirst(".entry-header.header")?.attr("data-id")?.toInt() + ?: return super.pageListParse(document) + val elements = document.select(".reading-content .page-break img") + val pageCount = elements.size + + val idKey = "8" + ((dataId + 1306) * 3 - pageCount).toString() + elements.forEach { + val decryptedId = decryptAesJson(it.attr("id"), idKey).jsonPrimitive.content + it.attr("id", decryptedId) } - return document.select("div.page-break, li.blocks-gallery-item, $begonepeconSelector") - .mapIndexed { index, element -> - val imageUrl = if (element.select(peconholderSelector).firstOrNull() == null) { - element.select("img").first()?.let { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } - } else { - element.select("img").joinToString("|") { it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src") } + BEGONEPECON_SUFFIX - } - Page(index, document.location(), imageUrl) - } - } - - private fun begonepeconIntercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(BEGONEPECON_SUFFIX)) { - return chain.proceed(chain.request()) + val orderedElements = elements.sortedBy { + pageIndexRegex.find(it.attr("id"))?.groupValues?.get(1)?.toInt() ?: 0 } + val dtaKey = "13" + orderedElements.joinToString("") { it.attr("id").takeLast(1) } + (((dataId + 88) * 2) - pageCount - 4).toString() - val imageUrls = chain.request().url.toString() - .removeSuffix(BEGONEPECON_SUFFIX) - .split("%7C") - - var width = 0 - var height = 0 - - val imageBitmaps = imageUrls.map { imageUrl -> - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body.byteStream()) + val srcKey = (dataId + 20).toString() + orderedElements.joinToString("") { + decryptAesJson(it.attr("dta"), dtaKey).jsonPrimitive.content.takeLast(2) + } + (pageCount * 2).toString() - width += bitmap.width - height = bitmap.height - - bitmap + return orderedElements.mapIndexed { i, element -> + val src = decryptAesJson(element.attr("data-src"), srcKey).jsonPrimitive.content + Page(i, document.location(), src) } + } - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - var left = 0 - - imageBitmaps.forEach { bitmap -> - val srcRect = Rect(0, 0, bitmap.width, bitmap.height) - val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) - - canvas.drawBitmap(bitmap, srcRect, dstRect, null) + private fun decryptAesJson(ciphertext: String, key: String): JsonElement { + val cipherData = json.parseToJsonElement(ciphertext).jsonObject - left += bitmap.width - } + val unsaltedCiphertext = Base64.decode(cipherData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) + val salt = cipherData["s"]!!.jsonPrimitive.content.decodeHex() + val saltedCiphertext = SALTED + salt + unsaltedCiphertext - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) + return json.parseToJsonElement(CryptoAES.decrypt(Base64.encodeToString(saltedCiphertext, Base64.DEFAULT), key)) + } - val responseBody = output.toByteArray().toResponseBody(PNG_MEDIA_TYPE) + private fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() } companion object { - private const val BEGONEPECON_SUFFIX = "?begonepecon" - private val PNG_MEDIA_TYPE = "image/png".toMediaType() + private val SALTED = "Salted__".toByteArray(Charsets.UTF_8) } } diff --git a/multisrc/overrides/madara/drakescans/src/DrakeScans.kt b/multisrc/overrides/madara/drakescans/src/DrakeScans.kt index 1fb045ebad..64976b23ac 100644 --- a/multisrc/overrides/madara/drakescans/src/DrakeScans.kt +++ b/multisrc/overrides/madara/drakescans/src/DrakeScans.kt @@ -8,7 +8,7 @@ class DrakeScans : Madara( "Drake Scans", "https://drakescans.com", "en", - SimpleDateFormat("MM/dd/yyyy", Locale.US), + SimpleDateFormat("dd/MM/yyyy", Locale.US), ) { override val mangaDetailsSelectorTag = "" diff --git a/multisrc/overrides/madara/egymanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/egymanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8b7de882e3..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/egymanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 9b68fdb47b..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/egymanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7668e11c07..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/egymanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9c3f8825d5..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/egymanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ccf94d0190..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/res/web_hi_res_512.png b/multisrc/overrides/madara/egymanga/res/web_hi_res_512.png deleted file mode 100644 index aacf8845ff..0000000000 Binary files a/multisrc/overrides/madara/egymanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/egymanga/src/EGYManga.kt b/multisrc/overrides/madara/egymanga/src/EGYManga.kt deleted file mode 100644 index 1a7f9cd675..0000000000 --- a/multisrc/overrides/madara/egymanga/src/EGYManga.kt +++ /dev/null @@ -1,23 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.egymanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Response -import java.text.SimpleDateFormat -import java.util.Locale - -class EGYManga : Madara( - "EGY Manga", - "https://egymanga.net", - "ar", - SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), -) { - - // The website does not flag the content. - override val filterNonMangaItems = false - - override val pageListParseSelector = "div.separator" - - override fun chapterListParse(response: Response): List = - super.chapterListParse(response).reversed() -} diff --git a/multisrc/overrides/madara/elitemanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/elitemanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4424b05927 Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/elitemanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/elitemanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..428955ee26 Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/elitemanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/elitemanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2499371a1b Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/elitemanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/elitemanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..04113d6f08 Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/elitemanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/elitemanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..af819ae32c Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/elitemanga/res/web_hi_res_512.png b/multisrc/overrides/madara/elitemanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..eee33c1774 Binary files /dev/null and b/multisrc/overrides/madara/elitemanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/elitemanga/src/EliteManga.kt b/multisrc/overrides/madara/elitemanga/src/EliteManga.kt new file mode 100644 index 0000000000..5f396483e3 --- /dev/null +++ b/multisrc/overrides/madara/elitemanga/src/EliteManga.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.elitemanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class EliteManga : Madara("Elite Manga", "https://www.elitemanga.org", "en") { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/empirewebtoon/src/EmpireWebtoon.kt b/multisrc/overrides/madara/empirewebtoon/src/EmpireWebtoon.kt index e2ce3921a4..01c672433e 100644 --- a/multisrc/overrides/madara/empirewebtoon/src/EmpireWebtoon.kt +++ b/multisrc/overrides/madara/empirewebtoon/src/EmpireWebtoon.kt @@ -11,9 +11,14 @@ import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale -class EmpireWebtoon : Madara("Empire Webtoon", "https://webtoonempire.com", "ar", SimpleDateFormat("yyyy MMMMM dd", Locale("ar"))) { +class EmpireWebtoon : Madara( + "Empire Webtoon", + "https://webtoonsempireron.com", + "ar", + SimpleDateFormat("d MMMM، yyyy", Locale("ar")), +) { - private val defaultBaseUrl = "https://webtoonempire.com" + private val defaultBaseUrl = "https://webtoonsempireron.com" override val baseUrl by lazy { getPrefBaseUrl() } @@ -21,6 +26,12 @@ class EmpireWebtoon : Madara("Empire Webtoon", "https://webtoonempire.com", "ar" Injekt.get().getSharedPreferences("source_$id", 0x0000) } + override val mangaSubString = "webtoon" + + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + companion object { private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting." private const val BASE_URL_PREF_TITLE = "Override BaseUrl" diff --git a/multisrc/overrides/madara/estufadecristal/src/EstufaDeCristal.kt b/multisrc/overrides/madara/estufadecristal/src/EstufaDeCristal.kt deleted file mode 100644 index d1b598411c..0000000000 --- a/multisrc/overrides/madara/estufadecristal/src/EstufaDeCristal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.estufadecristal - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.OkHttpClient -import okhttp3.Response -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class EstufaDeCristal : Madara( - "Estufa de Cristal", - "https://scanestufadecristal.site", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true - - override fun chapterListParse(response: Response): List { - return super.chapterListParse(response).reversed() - } -} diff --git a/multisrc/overrides/madara/evascans/src/EvaScans.kt b/multisrc/overrides/madara/evascans/src/EvaScans.kt index d76c36a855..6e53567391 100644 --- a/multisrc/overrides/madara/evascans/src/EvaScans.kt +++ b/multisrc/overrides/madara/evascans/src/EvaScans.kt @@ -1,14 +1,32 @@ package eu.kanade.tachiyomi.extension.tr.evascans import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.Page +import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale class EvaScans : Madara( - "EvaScans", - "https://evascans.com", + "ManWe", + "https://manwe.pro", "tr", dateFormat = SimpleDateFormat("MMM d, yyy", Locale("tr")), ) { - override val useNewChapterEndpoint = false + override val id = 5223802501310383833 + + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun pageListParse(document: Document): List { + val pageList = super.pageListParse(document) + + if ( + pageList.isEmpty() && + document.select(".content-blocked, .login-required").isNotEmpty() + ) { + throw Exception("Okumak için WebView üzerinden giriş yapın") + } + return pageList + } } diff --git a/multisrc/overrides/madara/factmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/factmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f53594f273 Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/factmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/factmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..00e99ac3e6 Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/factmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/factmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..af56ddefd8 Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/factmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/factmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..83f3d845a2 Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/factmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/factmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..186131840e Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/factmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/factmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..08868e5731 Binary files /dev/null and b/multisrc/overrides/madara/factmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/factmanga/src/FactManga.kt b/multisrc/overrides/madara/factmanga/src/FactManga.kt new file mode 100644 index 0000000000..af542a68ec --- /dev/null +++ b/multisrc/overrides/madara/factmanga/src/FactManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.factmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FactManga : Madara("FactManga", "https://factmanga.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/faestorm/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/faestorm/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4634a904e3..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/faestorm/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c922e1225a..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/faestorm/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 893159adca..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/faestorm/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 577f07a5f1..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/faestorm/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 87cf1b7377..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/res/web_hi_res_512.png b/multisrc/overrides/madara/faestorm/res/web_hi_res_512.png deleted file mode 100644 index bba71bef15..0000000000 Binary files a/multisrc/overrides/madara/faestorm/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/faestorm/src/FaeStorm.kt b/multisrc/overrides/madara/faestorm/src/FaeStorm.kt deleted file mode 100644 index aa5fc045b6..0000000000 --- a/multisrc/overrides/madara/faestorm/src/FaeStorm.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.faestorm - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class FaeStorm : Madara( - "FaeStorm", - "https://faestormmanga.com", - "tr", - SimpleDateFormat("d MMM yyy", Locale("tr")), -) diff --git a/multisrc/overrides/madara/falconmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/falconmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..95bdbee52d Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/falconmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/falconmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..fcbc380af4 Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/falconmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/falconmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4dd12c2f57 Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/falconmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/falconmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..46d634b909 Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/falconmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/falconmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..004a8231c1 Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/falconmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/falconmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..ac86b8566b Binary files /dev/null and b/multisrc/overrides/madara/falconmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/falconmanga/src/FalconManga.kt b/multisrc/overrides/madara/falconmanga/src/FalconManga.kt new file mode 100644 index 0000000000..8f2069dad7 --- /dev/null +++ b/multisrc/overrides/madara/falconmanga/src/FalconManga.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.ar.falconmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class FalconManga : Madara( + "فالكون مانجا", + "https://falconmanga.com", + "ar", + dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")), +) { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/fdmscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/fdmscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f788a6f877..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/fdmscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 914b442118..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/fdmscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1ac00a17b3..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/fdmscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1dffccae51..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/fdmscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ea6ef4e5b0..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/res/web_hi_res_512.png b/multisrc/overrides/madara/fdmscan/res/web_hi_res_512.png deleted file mode 100644 index f9a41eca02..0000000000 Binary files a/multisrc/overrides/madara/fdmscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/fdmscan/src/FDMScan.kt b/multisrc/overrides/madara/fdmscan/src/FDMScan.kt deleted file mode 100644 index b689a8490e..0000000000 --- a/multisrc/overrides/madara/fdmscan/src/FDMScan.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.fdmscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class FDMScan : Madara( - "FDM Scan", - "https://fdmscan.com", - "pt-BR", - SimpleDateFormat("MMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/firescans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firescans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7ead298449 Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firescans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firescans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..74e31a9187 Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firescans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firescans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bf4e8f08ba Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firescans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firescans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e18e8d8b7c Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firescans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firescans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c677567c36 Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firescans/res/web_hi_res_512.png b/multisrc/overrides/madara/firescans/res/web_hi_res_512.png new file mode 100644 index 0000000000..e06e29a8ac Binary files /dev/null and b/multisrc/overrides/madara/firescans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/firescans/src/FireScans.kt b/multisrc/overrides/madara/firescans/src/FireScans.kt new file mode 100644 index 0000000000..a91394af4c --- /dev/null +++ b/multisrc/overrides/madara/firescans/src/FireScans.kt @@ -0,0 +1,67 @@ +package eu.kanade.tachiyomi.extension.en.firescans + +import android.util.Base64 +import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.OkHttpClient +import org.jsoup.nodes.Document + +class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5) + .build() + + override val useNewChapterEndpoint: Boolean = true + + override fun pageListParse(document: Document): List { + countViews(document) + + val chapterProtector = document.selectFirst(chapterProtectorSelector) + ?: return document.select(pageListParseSelector).mapIndexed { index, element -> + val imageUrl = element.selectFirst("img")?.let { imageFromElement(it) } + Page(index, document.location(), imageUrl) + } + + val chapterProtectorHtml = if (chapterProtector.attr("src").startsWith("data:text/javascript;base64,")) { + Base64.decode(chapterProtector.attr("src").substringAfter(","), Base64.DEFAULT).decodeToString() + } else { + chapterProtector.html() + } + + val password = chapterProtectorHtml + .substringAfter("wpmangaprotectornonce='") + .substringBefore("';") + val chapterData = json.parseToJsonElement( + chapterProtectorHtml + .substringAfter("chapter_data='") + .substringBefore("';") + .replace("\\/", "/"), + ).jsonObject + + val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT) + val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex() + val ciphertext = SALTED + salt + unsaltedCiphertext + + val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password) + val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content + val imgArray = json.parseToJsonElement(imgArrayString).jsonArray + + return imgArray.mapIndexed { idx, it -> + Page(idx, document.location(), it.jsonPrimitive.content) + } + } + + private fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } +} diff --git a/multisrc/overrides/madara/firstkissmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissdashmanga/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/firstkissdashmanga/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/firstkissmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissdashmanga/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/firstkissdashmanga/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/firstkissmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/firstkissmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/firstkissmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/firstkissdashmanga/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/firstkissmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/firstkissdashmanga/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/firstkissmanga/res/web_hi_res_512.png rename to multisrc/overrides/madara/firstkissdashmanga/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/firstkissdashmanga/src/FirstKissDashManga.kt b/multisrc/overrides/madara/firstkissdashmanga/src/FirstKissDashManga.kt new file mode 100644 index 0000000000..6438932b53 --- /dev/null +++ b/multisrc/overrides/madara/firstkissdashmanga/src/FirstKissDashManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.firstkissdashmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FirstKissDashManga : Madara("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/firstkissmanga/src/FirstKissManga.kt b/multisrc/overrides/madara/firstkissmanga/src/FirstKissManga.kt deleted file mode 100644 index c8c6887ff0..0000000000 --- a/multisrc/overrides/madara/firstkissmanga/src/FirstKissManga.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.firstkissmanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.Headers -import java.util.concurrent.TimeUnit - -class FirstKissManga : Madara( - "1st Kiss", - "https://1stkissmanga.me", - "en", -) { - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/firstkissmangablog/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d8bbacd490 Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ec35ead1d0 Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bd959ac04d Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..58989ca02b Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ae51ec0a2 Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/res/web_hi_res_512.png b/multisrc/overrides/madara/firstkissmangablog/res/web_hi_res_512.png new file mode 100644 index 0000000000..a624bd17dd Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangablog/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/firstkissmangablog/src/FirstKissMangaBlog.kt b/multisrc/overrides/madara/firstkissmangablog/src/FirstKissMangaBlog.kt new file mode 100644 index 0000000000..2ec33b61a9 --- /dev/null +++ b/multisrc/overrides/madara/firstkissmangablog/src/FirstKissMangaBlog.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.firstkissmangablog + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FirstKissMangaBlog : Madara("1stKissManga.blog", "https://1stkissmanga.blog", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/firstkissmangaclub/src/FirstKissMangaClub.kt b/multisrc/overrides/madara/firstkissmangaclub/src/FirstKissMangaClub.kt index 71b2e1916d..1f9e3d3b8f 100644 --- a/multisrc/overrides/madara/firstkissmangaclub/src/FirstKissMangaClub.kt +++ b/multisrc/overrides/madara/firstkissmangaclub/src/FirstKissMangaClub.kt @@ -10,7 +10,7 @@ class FirstKissMangaClub : Madara( "en", ) { - override val client = network.cloudflareClient.newBuilder() + override val client = super.client.newBuilder() .rateLimit(1, 3, TimeUnit.SECONDS) .build() } diff --git a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangalove/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 53308340f2..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangalove/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 979753d9b0..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7d83a1455c..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f678027d4c..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aaf95b4e1b..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/res/web_hi_res_512.png b/multisrc/overrides/madara/firstkissmangalove/res/web_hi_res_512.png deleted file mode 100644 index c9559e92fb..0000000000 Binary files a/multisrc/overrides/madara/firstkissmangalove/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmangalove/src/FirstKissMangaLove.kt b/multisrc/overrides/madara/firstkissmangalove/src/FirstKissMangaLove.kt deleted file mode 100644 index 3e4ce15889..0000000000 --- a/multisrc/overrides/madara/firstkissmangalove/src/FirstKissMangaLove.kt +++ /dev/null @@ -1,16 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.firstkissmangalove - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import java.util.concurrent.TimeUnit - -class FirstKissMangaLove : Madara( - "1st Kiss Manga.love", - "https://1stkissmanga.love", - "en", -) { - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/firstkissmangatv/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..36d12e82fe Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..bd9d5104ae Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..006852662b Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..519f14eeb7 Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..709f8a0844 Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/res/web_hi_res_512.png b/multisrc/overrides/madara/firstkissmangatv/res/web_hi_res_512.png new file mode 100644 index 0000000000..2f86f7755e Binary files /dev/null and b/multisrc/overrides/madara/firstkissmangatv/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/firstkissmangatv/src/FirstKissMangaTv.kt b/multisrc/overrides/madara/firstkissmangatv/src/FirstKissMangaTv.kt new file mode 100644 index 0000000000..794df0d49a --- /dev/null +++ b/multisrc/overrides/madara/firstkissmangatv/src/FirstKissMangaTv.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.firstkissmangatv + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FirstKissMangaTv : Madara("1stKissManga.tv", "https://1stkissmanga.tv", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmanhua/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d3f01d2b14..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmanhua/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 854a17abc9..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d0279e2dd5..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d421b8e18f..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a6df0ba573..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/res/web_hi_res_512.png b/multisrc/overrides/madara/firstkissmanhua/res/web_hi_res_512.png deleted file mode 100644 index c48ce78955..0000000000 Binary files a/multisrc/overrides/madara/firstkissmanhua/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/firstkissmanhua/src/FirstKissManhua.kt b/multisrc/overrides/madara/firstkissmanhua/src/FirstKissManhua.kt deleted file mode 100644 index 9afdeb94f6..0000000000 --- a/multisrc/overrides/madara/firstkissmanhua/src/FirstKissManhua.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.firstkissmanhua - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.Page -import okhttp3.Request -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class FirstKissManhua : Madara( - "1st Kiss Manhua", - "https://1stkissmanhua.com", - "en", - SimpleDateFormat("d MMM yyyy", Locale.US), -) { - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() - - override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", "https://1stkissmanga.com").build()) -} diff --git a/multisrc/overrides/madara/firstmanhwa/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/firstmanhwa/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4c01a60c66 Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/firstmanhwa/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..153aa31447 Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6e2a6c4afa Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2767aaf02d Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e666bd3c6d Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/res/web_hi_res_512.png b/multisrc/overrides/madara/firstmanhwa/res/web_hi_res_512.png new file mode 100644 index 0000000000..0d45512480 Binary files /dev/null and b/multisrc/overrides/madara/firstmanhwa/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/firstmanhwa/src/FirstManhwa.kt b/multisrc/overrides/madara/firstmanhwa/src/FirstManhwa.kt new file mode 100644 index 0000000000..d7569cbd09 --- /dev/null +++ b/multisrc/overrides/madara/firstmanhwa/src/FirstManhwa.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.en.firstmanhwa + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FirstManhwa : Madara("1st Manhwa", "https://1stmanhwa.com", "en") { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/fizmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/fizmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9676bf4b03..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/fizmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 927a5513c3..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/fizmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 69cf5e5336..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/fizmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f63ec4f05c..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/fizmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2286af53ff..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/fizmanga/res/web_hi_res_512.png deleted file mode 100644 index dc50fd38c6..0000000000 Binary files a/multisrc/overrides/madara/fizmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/fizmanga/src/FizManga.kt b/multisrc/overrides/madara/fizmanga/src/FizManga.kt deleted file mode 100644 index 91dd0cb750..0000000000 --- a/multisrc/overrides/madara/fizmanga/src/FizManga.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.fizmanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.Headers - -class FizManga : Madara("Fiz Manga", "https://fizmanga.com", "en") { - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) -} diff --git a/multisrc/overrides/madara/fleurblanche/src/FleurBlanche.kt b/multisrc/overrides/madara/fleurblanche/src/FleurBlanche.kt index 5f3635289a..336d6c8a5d 100644 --- a/multisrc/overrides/madara/fleurblanche/src/FleurBlanche.kt +++ b/multisrc/overrides/madara/fleurblanche/src/FleurBlanche.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.pt.fleurblanche import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.Headers import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response @@ -25,8 +24,6 @@ class FleurBlanche : Madara( override val useNewChapterEndpoint = true - override fun headersBuilder(): Headers.Builder = Headers.Builder() - private fun authWarningIntercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) diff --git a/multisrc/overrides/madara/flowermanga/src/FlowerManga.kt b/multisrc/overrides/madara/flowermanga/src/FlowerManga.kt new file mode 100644 index 0000000000..10c57b6056 --- /dev/null +++ b/multisrc/overrides/madara/flowermanga/src/FlowerManga.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.pt.flowermanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class FlowerManga : Madara( + "Flower Manga", + "https://flowermanga.com", + "pt-BR", + SimpleDateFormat("dd MMMMM yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/madara/frdashscan/src/FRScan.kt b/multisrc/overrides/madara/frdashscan/src/FRScan.kt index eb270757ea..6b8f17c258 100644 --- a/multisrc/overrides/madara/frdashscan/src/FRScan.kt +++ b/multisrc/overrides/madara/frdashscan/src/FRScan.kt @@ -1,7 +1,17 @@ package eu.kanade.tachiyomi.extension.fr.frdashscan import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit -class FRScan : Madara("FR-Scan", "https://fr-scan.com", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRANCE)) +class FRScan : Madara("FR-Scan", "https://fr-scan.com", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRANCE)) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/freemanhwa/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/freemanhwa/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d61275c78b Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/freemanhwa/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/freemanhwa/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..e10f48a1cc Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/freemanhwa/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/freemanhwa/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..74537cee6b Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/freemanhwa/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/freemanhwa/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ae7b8396dc Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/freemanhwa/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/freemanhwa/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0a31331676 Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/freemanhwa/res/web_hi_res_512.png b/multisrc/overrides/madara/freemanhwa/res/web_hi_res_512.png new file mode 100644 index 0000000000..15f3102c8a Binary files /dev/null and b/multisrc/overrides/madara/freemanhwa/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/freemanhwa/src/FreeManhwa.kt b/multisrc/overrides/madara/freemanhwa/src/FreeManhwa.kt new file mode 100644 index 0000000000..3b86977e47 --- /dev/null +++ b/multisrc/overrides/madara/freemanhwa/src/FreeManhwa.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.freemanhwa + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class FreeManhwa : Madara("Free Manhwa", "https://manhwas.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/fugmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/fugmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0fb37ffe4c..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fugmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/fugmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 455f932e58..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fugmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/fugmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4e8cf345af..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fugmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/fugmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 22c0e43181..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fugmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/fugmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 197ec7d7c0..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fugmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/fugmanga/res/web_hi_res_512.png deleted file mode 100644 index b14a9bcb00..0000000000 Binary files a/multisrc/overrides/madara/fugmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index da37b48da3..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e48e74241e..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 78dfa61578..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2dd6d3ddcf..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b99a1a1de7..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/res/web_hi_res_512.png b/multisrc/overrides/madara/fukushuunoyuusha/res/web_hi_res_512.png deleted file mode 100644 index 72a7f16869..0000000000 Binary files a/multisrc/overrides/madara/fukushuunoyuusha/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/fukushuunoyuusha/src/FukushuunoYuusha.kt b/multisrc/overrides/madara/fukushuunoyuusha/src/FukushuunoYuusha.kt deleted file mode 100644 index 5d86ca129b..0000000000 --- a/multisrc/overrides/madara/fukushuunoyuusha/src/FukushuunoYuusha.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.fukushuunoyuusha - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class FukushuunoYuusha : Madara( - "Fukushuu no Yuusha", - "https://fny-scantrad.com", - "fr", - dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US), -) diff --git a/multisrc/overrides/madara/furioscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/furioscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6e7fcaa133..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/furioscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 53e69abafd..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/furioscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4ce9567f76..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/furioscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6c5d05fd29..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/furioscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f1bc366cfc..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/res/web_hi_res_512.png b/multisrc/overrides/madara/furioscans/res/web_hi_res_512.png deleted file mode 100644 index deaad95709..0000000000 Binary files a/multisrc/overrides/madara/furioscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/furioscans/src/FurioScans.kt b/multisrc/overrides/madara/furioscans/src/FurioScans.kt deleted file mode 100644 index 2436dc4de6..0000000000 --- a/multisrc/overrides/madara/furioscans/src/FurioScans.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.furioscans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class FurioScans : Madara( - "Furio Scans", - "https://furioscans.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/geasstoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/geasstoon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7d22c90c6c..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/geasstoon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff68ed91b8..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/geasstoon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index cd374f8777..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/geasstoon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3c1827bb02..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/geasstoon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a2f1d05a16..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/res/web_hi_res_512.png b/multisrc/overrides/madara/geasstoon/res/web_hi_res_512.png deleted file mode 100644 index c0affbebbd..0000000000 Binary files a/multisrc/overrides/madara/geasstoon/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/geasstoon/src/GeassToon.kt b/multisrc/overrides/madara/geasstoon/src/GeassToon.kt deleted file mode 100644 index 414eb2716f..0000000000 --- a/multisrc/overrides/madara/geasstoon/src/GeassToon.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.geasstoon - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class GeassToon : Madara( - "GeassToon", - "https://geasstoon.com", - "tr", - dateFormat = SimpleDateFormat("MMM d, yyy", Locale("tr")), -) { - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/ghostscan/src/GhostScan.kt b/multisrc/overrides/madara/ghostscan/src/GhostScan.kt new file mode 100644 index 0000000000..577fac1275 --- /dev/null +++ b/multisrc/overrides/madara/ghostscan/src/GhostScan.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.ghostscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class GhostScan : Madara( + "Ghost Scan", + "https://ghostscan.com.br", + "pt-BR", + SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/girlslovemanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/girlslovemanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..221c1073b6 Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/girlslovemanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ed34c81628 Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..5e5bc117fc Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..94cc3d70b4 Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5417070717 Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/res/web_hi_res_512.png b/multisrc/overrides/madara/girlslovemanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..6f26e4d462 Binary files /dev/null and b/multisrc/overrides/madara/girlslovemanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/girlslovemanga/src/GirlsLoveManga.kt b/multisrc/overrides/madara/girlslovemanga/src/GirlsLoveManga.kt new file mode 100644 index 0000000000..76ce55f5f5 --- /dev/null +++ b/multisrc/overrides/madara/girlslovemanga/src/GirlsLoveManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.girlslovemanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class GirlsLoveManga : Madara("Girls Love Manga!", "https://glmanga.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/goodgirlsscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9411becc21 Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..16f7e03e4d Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b62027ea0c Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6ebc4a6842 Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1de17cb30b Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/res/web_hi_res_512.png b/multisrc/overrides/madara/goodgirlsscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..f5ea283b49 Binary files /dev/null and b/multisrc/overrides/madara/goodgirlsscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/goodgirlsscan/src/GoodGirlsScan.kt b/multisrc/overrides/madara/goodgirlsscan/src/GoodGirlsScan.kt new file mode 100644 index 0000000000..0ec7c82bf4 --- /dev/null +++ b/multisrc/overrides/madara/goodgirlsscan/src/GoodGirlsScan.kt @@ -0,0 +1,89 @@ +package eu.kanade.tachiyomi.extension.en.goodgirlsscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Element + +class GoodGirlsScan : Madara("Good Girls Scan", "https://goodgirls.moe", "en") { + + override val fetchGenres = false + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + override fun searchMangaSelector() = "article.wp-manga" + override fun searchMangaNextPageSelector() = "div.paginator .nav-next" + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + override val mangaDetailsSelectorDescription = "div.summary-specialfields" + override fun chapterListSelector() = "li.wp-manga-chapter:not(.vip-permission)" + + private fun madaraLoadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "right") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[meta_query][0][paged]", "1") + add("vars[meta_query][0][orderby]", "meta_value_num") + add("vars[meta_query][0][template]", "archive") + add("vars[meta_query][0][sidebar]", "right") + add("vars[meta_query][0][post_type]", "wp-manga") + add("vars[meta_query][0][post_status]", "publish") + add("vars[meta_query][0][meta_key]", metaKey) + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "default") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return madaraLoadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return madaraLoadMoreRequest(page - 1, "_latest_update") + } + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } + + // heavily modified madara theme, throws 5xx errors on any search filter + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder().apply { + addQueryParameter("s", query.trim()) + }.build() + + return GET(url, headers) + } + + override fun getFilterList() = FilterList() + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + element.select(".entry-title a").let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + thumbnail_url = element.selectFirst(".post-thumbnail img")?.let(::imageFromElement) + } +} diff --git a/multisrc/overrides/madara/grabberzone/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/grabberzone/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..38d18ddbfe Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/grabberzone/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/grabberzone/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f957266b14 Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/grabberzone/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/grabberzone/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..7cebc98427 Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/grabberzone/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/grabberzone/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dede1ebffa Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/grabberzone/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/grabberzone/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9e48c5591f Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/grabberzone/res/web_hi_res_512.png b/multisrc/overrides/madara/grabberzone/res/web_hi_res_512.png new file mode 100644 index 0000000000..f831ed6857 Binary files /dev/null and b/multisrc/overrides/madara/grabberzone/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/grabberzone/src/GrabberZone.kt b/multisrc/overrides/madara/grabberzone/src/GrabberZone.kt new file mode 100644 index 0000000000..98a3b3502e --- /dev/null +++ b/multisrc/overrides/madara/grabberzone/src/GrabberZone.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.extension.all.grabberzone + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.SChapter +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +class GrabberZone : Madara( + "Grabber Zone", + "https://grabber.zone", + "all", + SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH), +) { + override val mangaSubString = "comics" + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } + + override fun chapterFromElement(element: Element): SChapter { + return super.chapterFromElement(element).apply { + name = element.selectFirst("a + a")!!.text() + } + } +} diff --git a/multisrc/overrides/madara/harimanga/src/Harimanga.kt b/multisrc/overrides/madara/harimanga/src/Harimanga.kt index 9af4f9751f..6fbeffa786 100644 --- a/multisrc/overrides/madara/harimanga/src/Harimanga.kt +++ b/multisrc/overrides/madara/harimanga/src/Harimanga.kt @@ -1,7 +1,5 @@ package eu.kanade.tachiyomi.extension.en.harimanga import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale -class Harimanga : Madara("Harimanga", "https://harimanga.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US)) +class Harimanga : Madara("Harimanga", "https://harimanga.com", "en") diff --git a/multisrc/overrides/madara/hatachimanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/hatachimanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cd720cb144..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hatachimanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/hatachimanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4b25d92802..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hatachimanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/hatachimanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7f3841ef91..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hatachimanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/hatachimanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a69788c980..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hatachimanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/hatachimanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 29336f9061..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hatachimanga/res/web_hi_res_512.png b/multisrc/overrides/madara/hatachimanga/res/web_hi_res_512.png deleted file mode 100644 index f43bae8ddd..0000000000 Binary files a/multisrc/overrides/madara/hatachimanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/helascan/src/HelaScan.kt b/multisrc/overrides/madara/helascan/src/HelaScan.kt deleted file mode 100644 index f6914e2792..0000000000 --- a/multisrc/overrides/madara/helascan/src/HelaScan.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.helascan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class HelaScan : Madara( - "Hela Scan", - "https://helascan.com", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true - - override val mangaDetailsSelectorTag = "" -} diff --git a/multisrc/overrides/madara/hentai3z/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/hentai3z/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7825c08b7e Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai3z/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/hentai3z/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ddcbe1ecca Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai3z/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai3z/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2635b6e493 Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai3z/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai3z/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3edefcaf43 Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai3z/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai3z/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cf5633a64d Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai3z/res/web_hi_res_512.png b/multisrc/overrides/madara/hentai3z/res/web_hi_res_512.png new file mode 100644 index 0000000000..a6f227e83a Binary files /dev/null and b/multisrc/overrides/madara/hentai3z/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/hentai3z/src/Hentai3z.kt b/multisrc/overrides/madara/hentai3z/src/Hentai3z.kt new file mode 100644 index 0000000000..4563a8c3d4 --- /dev/null +++ b/multisrc/overrides/madara/hentai3z/src/Hentai3z.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.hentai3z + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Hentai3z : Madara("Hentai3z", "https://hentai3z.xyz", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/hentai4free/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/hentai4free/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9cd3ea9b58 Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai4free/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/hentai4free/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f6fc19d46f Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai4free/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai4free/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..7c10fc9c03 Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai4free/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai4free/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6f398266dc Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai4free/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentai4free/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..13856ef345 Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentai4free/res/web_hi_res_512.png b/multisrc/overrides/madara/hentai4free/res/web_hi_res_512.png new file mode 100644 index 0000000000..13412e22b2 Binary files /dev/null and b/multisrc/overrides/madara/hentai4free/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/hentai4free/src/Hentai4Free.kt b/multisrc/overrides/madara/hentai4free/src/Hentai4Free.kt new file mode 100644 index 0000000000..d8b2e7729e --- /dev/null +++ b/multisrc/overrides/madara/hentai4free/src/Hentai4Free.kt @@ -0,0 +1,50 @@ +package eu.kanade.tachiyomi.extension.en.hentai4free + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.Request + +class Hentai4Free : Madara("Hentai4Free", "https://hentai4free.net", "en") { + override val useNewChapterEndpoint = true + override val mangaSubString = "hentai" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun popularMangaSelector() = searchMangaSelector() + + override fun popularMangaRequest(page: Int): Request = + searchMangaRequest( + page, + "", + FilterList( + listOf( + OrderByFilter( + "", + listOf( + Pair("", ""), + Pair("", "views"), + ), + 1, + ), + ), + ), + ) + + override fun latestUpdatesRequest(page: Int): Request = + searchMangaRequest( + page, + "", + FilterList( + listOf( + OrderByFilter( + "", + listOf( + Pair("", ""), + Pair("", "latest"), + ), + 1, + ), + ), + ), + ) +} diff --git a/multisrc/overrides/madara/hentaicube/src/HentaiCB.kt b/multisrc/overrides/madara/hentaicube/src/HentaiCB.kt index eb1d34a6b1..9ae77feb79 100644 --- a/multisrc/overrides/madara/hentaicube/src/HentaiCB.kt +++ b/multisrc/overrides/madara/hentaicube/src/HentaiCB.kt @@ -6,7 +6,7 @@ import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale -class HentaiCB : Madara("Hentai CB", "https://cubeteam.xyz", "vi", SimpleDateFormat("dd/MM/yyyy", Locale("vi"))) { +class HentaiCB : Madara("Hentai CB", "https://hentaicube.net", "vi", SimpleDateFormat("dd/MM/yyyy", Locale("vi"))) { override val id: Long = 823638192569572166 override fun pageListParse(document: Document): List { return super.pageListParse(document).distinctBy { it.imageUrl } diff --git a/multisrc/overrides/madara/hentaiteca/src/HentaiTeca.kt b/multisrc/overrides/madara/hentaiteca/src/HentaiTeca.kt index 5689d18f50..f63de0c24f 100644 --- a/multisrc/overrides/madara/hentaiteca/src/HentaiTeca.kt +++ b/multisrc/overrides/madara/hentaiteca/src/HentaiTeca.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.pt.hentaiteca import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.Headers import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale @@ -18,7 +17,4 @@ class HentaiTeca : Madara( override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") } diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f8eb032487 Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..619332776a Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..0816dc0114 Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fa25607252 Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0986c1539d Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/res/web_hi_res_512.png b/multisrc/overrides/madara/hentaixdickgirl/res/web_hi_res_512.png new file mode 100644 index 0000000000..5205361787 Binary files /dev/null and b/multisrc/overrides/madara/hentaixdickgirl/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/hentaixdickgirl/src/HentaiXDickgirl.kt b/multisrc/overrides/madara/hentaixdickgirl/src/HentaiXDickgirl.kt new file mode 100644 index 0000000000..1e6704a684 --- /dev/null +++ b/multisrc/overrides/madara/hentaixdickgirl/src/HentaiXDickgirl.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.extension.en.hentaixdickgirl + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import org.jsoup.nodes.Document + +class HentaiXDickgirl : Madara("HentaiXDickgirl", "https://hentaixdickgirl.com", "en") { + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } + + override fun mangaDetailsParse(document: Document): SManga { + return super.mangaDetailsParse(document).apply { + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + } + } +} diff --git a/multisrc/overrides/madara/hentaizone/src/HentaiZone.kt b/multisrc/overrides/madara/hentaizone/src/HentaiZone.kt new file mode 100644 index 0000000000..bc4d2b4a37 --- /dev/null +++ b/multisrc/overrides/madara/hentaizone/src/HentaiZone.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.fr.hentaizone + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class HentaiZone : Madara("HentaiZone", "https://hentaizone.xyz", "fr", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.FRENCH)) { + override val mangaSubString = "tous-les-mangas" +} diff --git a/multisrc/overrides/madara/hiperdex/src/Hiperdex.kt b/multisrc/overrides/madara/hiperdex/src/Hiperdex.kt index 9f7b72d805..ecfbff564b 100644 --- a/multisrc/overrides/madara/hiperdex/src/Hiperdex.kt +++ b/multisrc/overrides/madara/hiperdex/src/Hiperdex.kt @@ -3,41 +3,103 @@ package eu.kanade.tachiyomi.extension.en.hiperdex import android.app.Application import android.content.SharedPreferences import android.widget.Toast +import androidx.preference.EditTextPreference import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.multisrc.madara.Madara +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.Response import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class Hiperdex : Madara("Hiperdex", "https://1sthiperdex.com", "en") { +class Hiperdex : Madara("Hiperdex", "https://hiperdex.com", "en") { override val useNewChapterEndpoint: Boolean = true - private val defaultBaseUrl = "https://1sthiperdex.com" + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } override val baseUrl by lazy { getPrefBaseUrl() } - private val preferences: SharedPreferences by lazy { + private val preferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } + override val client = super.client.newBuilder() + .addInterceptor(::domainChangeIntercept) + .build() + + private var lastDomain = "" + + private fun domainChangeIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (request.url.host !in listOf(preferences.baseUrlHost, lastDomain)) { + return chain.proceed(request) + } + + if (lastDomain.isNotEmpty()) { + val newUrl = request.url.newBuilder() + .host(preferences.baseUrlHost) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + + val response = chain.proceed(request) + + if (request.url.host == response.request.url.host) return response + + response.close() + + preferences.baseUrlHost = response.request.url.host + + lastDomain = request.url.host + + val newUrl = request.url.newBuilder() + .host(response.request.url.host) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + companion object { + private const val defaultBaseUrlHost = "hiperdex.com" private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting." private const val BASE_URL_PREF_TITLE = "Override BaseUrl" - private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_CODE}" - private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Updating the extension will erase this setting." + private const val BASE_URL_PREF = "overrideBaseUrl_v2" + private const val BASE_URL_PREF_SUMMARY = "Enter a complete url starting with http" } override fun setupPreferenceScreen(screen: PreferenceScreen) { - val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { + val baseUrlPref = EditTextPreference(screen.context).apply { key = BASE_URL_PREF title = BASE_URL_PREF_TITLE summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(defaultBaseUrl) + this.setDefaultValue(defaultBaseUrlHost) dialogTitle = BASE_URL_PREF_TITLE - setOnPreferenceChangeListener { _, _ -> - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - true + setOnPreferenceChangeListener { _, newVal -> + val url = newVal as String + runCatching { + val host = url.toHttpUrl().host + + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + preferences.edit().putString(BASE_URL_PREF, host).commit() + } + false } } screen.addPreference(baseUrlPref) @@ -45,5 +107,11 @@ class Hiperdex : Madara("Hiperdex", "https://1sthiperdex.com", "en") { super.setupPreferenceScreen(screen) } - private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! + private var SharedPreferences.baseUrlHost + get() = getString(BASE_URL_PREF, defaultBaseUrlHost) ?: defaultBaseUrlHost + set(newHost) { + edit().putString(BASE_URL_PREF, newHost).commit() + } + + private fun getPrefBaseUrl(): String = preferences.baseUrlHost.let { "https://$it" } } diff --git a/multisrc/overrides/madara/housemangas/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/housemangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7abce771ea Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/housemangas/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/housemangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..69ee23ba77 Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/housemangas/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/housemangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..18db3543ee Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/housemangas/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/housemangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d151cb8d78 Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/housemangas/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/housemangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..015a5039a3 Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/housemangas/res/web_hi_res_512.png b/multisrc/overrides/madara/housemangas/res/web_hi_res_512.png new file mode 100644 index 0000000000..f91e7a2134 Binary files /dev/null and b/multisrc/overrides/madara/housemangas/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/housemangas/src/HouseMangas.kt b/multisrc/overrides/madara/housemangas/src/HouseMangas.kt new file mode 100644 index 0000000000..078fae9450 --- /dev/null +++ b/multisrc/overrides/madara/housemangas/src/HouseMangas.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.extension.es.housemangas + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.FormBody +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class HouseMangas : Madara( + "HouseMangas", + "https://housemangas.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + + override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Estado) > div.summary-content" + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "right") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[order]", "desc") + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "default") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } +} diff --git a/multisrc/overrides/madara/hscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/hscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 80193085aa..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/hscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f5b8817843..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/hscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c26f25dfa2..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/hscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ab67ea70bf..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/hscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3ba3585a53..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/res/web_hi_res_512.png b/multisrc/overrides/madara/hscans/res/web_hi_res_512.png deleted file mode 100644 index 11b2166dd8..0000000000 Binary files a/multisrc/overrides/madara/hscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/hscans/src/Hscans.kt b/multisrc/overrides/madara/hscans/src/Hscans.kt deleted file mode 100644 index 80952ebb3b..0000000000 --- a/multisrc/overrides/madara/hscans/src/Hscans.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hscans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class Hscans : Madara("Hscans", "https://hscans.com", "en", SimpleDateFormat("dd/MM/yyyy", Locale.US)) diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 10ce53b207..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index dbf838111a..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c80807b318..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d3ede62067..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 56cd6d5a46..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/res/web_hi_res_512.png b/multisrc/overrides/madara/ichirinnohanayuri/res/web_hi_res_512.png deleted file mode 100644 index 066c5dfa4a..0000000000 Binary files a/multisrc/overrides/madara/ichirinnohanayuri/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/ichirinnohanayuri/src/IchirinNoHanaYuri.kt b/multisrc/overrides/madara/ichirinnohanayuri/src/IchirinNoHanaYuri.kt deleted file mode 100644 index 9c5987f09b..0000000000 --- a/multisrc/overrides/madara/ichirinnohanayuri/src/IchirinNoHanaYuri.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.ichirinnohanayuri - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.Headers -import okhttp3.OkHttpClient -import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class IchirinNoHanaYuri : Madara( - "Ichirin No Hana Yuri", - "https://ichirinnohanayuriscan.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .addInterceptor { chain -> - val response = chain.proceed(chain.request()) - - if (response.code == 403) { - response.close() - throw IOException(BLOCKING_MESSAGE) - } - - response - } - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - - companion object { - private const val BLOCKING_MESSAGE = "O site está bloqueando o Tachiyomi. " + - "Migre para outra fonte caso o problema persistir." - } -} diff --git a/multisrc/overrides/madara/inarimanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/inarimanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6a6ee9a744..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/inarimanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 75b96d94ec..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/inarimanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f8e465e967..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/inarimanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6cb7cbd3d2..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e73b3c73f7..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/res/web_hi_res_512.png b/multisrc/overrides/madara/inarimanga/res/web_hi_res_512.png deleted file mode 100644 index 69212c912b..0000000000 Binary files a/multisrc/overrides/madara/inarimanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/inarimanga/src/InariManga.kt b/multisrc/overrides/madara/inarimanga/src/InariManga.kt deleted file mode 100644 index 3e88f17886..0000000000 --- a/multisrc/overrides/madara/inarimanga/src/InariManga.kt +++ /dev/null @@ -1,46 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.inarimanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SManga -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class InariManga : Madara( - "InariManga", - "https://inarimanga.com", - "es", - dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), -) { - override fun popularMangaSelector() = "div.page-listing-item div.post" - override val popularMangaUrlSelector = "div.p-2 > h6 > a" - - override fun searchMangaSelector() = "div.page-listing-item div.post" - private val searchMangaUrlSelector = "div.p-2 > h6 > a" - - override val mangaDetailsSelectorDescription = "div.card-body:has(h5:contains(Sinopsis))" - override val mangaDetailsSelectorThumbnail = "div.col-sticky-top > img" - override val mangaDetailsSelectorStatus = "div.card-body tr:has(th:contains(Estatus)) > td" - override val mangaDetailsSelectorGenre = "div.my-auto > div.inline-block > a" - - override val useNewChapterEndpoint = true - - override fun chapterListSelector() = "tr.wp-manga-chapter" - override fun chapterDateSelector() = "time.chapter-release-date" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(searchMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - manga.title = it.ownText() - } - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it) - } - } - - return manga - } -} diff --git a/multisrc/overrides/madara/inazumanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/inazumanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e27238bfaa..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inazumanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/inazumanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 44371cd303..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inazumanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/inazumanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 16fa1f7673..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inazumanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/inazumanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fa673b592f..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 95c0c9c25e..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/inazumanga/res/web_hi_res_512.png b/multisrc/overrides/madara/inazumanga/res/web_hi_res_512.png deleted file mode 100644 index 2e1b6a1839..0000000000 Binary files a/multisrc/overrides/madara/inazumanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/isekaiscaneu/src/IsekaiScanTo.kt b/multisrc/overrides/madara/isekaiscaneu/src/IsekaiScanTo.kt index 7cd4ebadf3..45797cb35e 100644 --- a/multisrc/overrides/madara/isekaiscaneu/src/IsekaiScanTo.kt +++ b/multisrc/overrides/madara/isekaiscaneu/src/IsekaiScanTo.kt @@ -4,6 +4,8 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara import java.text.SimpleDateFormat import java.util.Locale -class IsekaiScanTo : Madara("IsekaiScan.to (unoriginal)", "https://isekaiscan.to", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US)) { +class IsekaiScanTo : Madara("IsekaiScan.to (unoriginal)", "https://m.isekaiscan.to", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US)) { override val id = 8608305834807261892L; // from former IsekaiScan.eu source + + override val mangaSubString = "mangax" } diff --git a/multisrc/overrides/madara/isekaiscantop/src/IsekaiScanTop.kt b/multisrc/overrides/madara/isekaiscantop/src/IsekaiScanTop.kt index e29c02c568..54614826c4 100644 --- a/multisrc/overrides/madara/isekaiscantop/src/IsekaiScanTop.kt +++ b/multisrc/overrides/madara/isekaiscantop/src/IsekaiScanTop.kt @@ -41,7 +41,6 @@ class IsekaiScanTop : Madara( if (chapterElements.isEmpty() && !chaptersWrapper.isNullOrEmpty()) { val mangaId = chaptersWrapper.attr("data-id") val xhrHeaders = headersBuilder() - .add("Referer", "$baseUrl/") .add("X-Requested-With", "XMLHttpRequest") .build() val xhrRequest = GET("$baseUrl/ajax-list-chapter?mangaID=$mangaId", xhrHeaders) diff --git a/multisrc/overrides/madara/izakaya/src/Izakaya.kt b/multisrc/overrides/madara/izakaya/src/Izakaya.kt deleted file mode 100644 index 752e1050b5..0000000000 --- a/multisrc/overrides/madara/izakaya/src/Izakaya.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.izakaya - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.Page -import okhttp3.OkHttpClient -import okhttp3.Request -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Izakaya : Madara( - "Izakaya", - "https://leitorizakaya.net", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8") - .build() - - return GET(page.imageUrl!!, newHeaders) - } -} diff --git a/multisrc/overrides/madara/jiangzaitoon/src/Jiangzaitoon.kt b/multisrc/overrides/madara/jiangzaitoon/src/Jiangzaitoon.kt index ec6ea33079..6a36821969 100644 --- a/multisrc/overrides/madara/jiangzaitoon/src/Jiangzaitoon.kt +++ b/multisrc/overrides/madara/jiangzaitoon/src/Jiangzaitoon.kt @@ -1,7 +1,25 @@ package eu.kanade.tachiyomi.extension.tr.jiangzaitoon import eu.kanade.tachiyomi.multisrc.madara.Madara +import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit -class Jiangzaitoon : Madara("Jiangzaitoon", "https://jiangzaitoon.co", "tr", SimpleDateFormat("d MMM yyy", Locale("tr"))) +class Jiangzaitoon : Madara( + "Jiangzaitoon", + "https://jiangzaitoon.cc", + "tr", + SimpleDateFormat("d MMM yyy", Locale("tr")), +) { + override val useNewChapterEndpoint = false + + override val client: OkHttpClient by lazy { + super.client.newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(3, TimeUnit.MINUTES) // aka shit source + .build() + } + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/jimanga/src/Jimanga.kt b/multisrc/overrides/madara/jimanga/src/Jimanga.kt new file mode 100644 index 0000000000..df1e41e133 --- /dev/null +++ b/multisrc/overrides/madara/jimanga/src/Jimanga.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.jimanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Jimanga : Madara("Jimanga", "https://jimanga.com", "en") { + override val useNewChapterEndpoint = false + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/kakuseiproject/src/KakuseiProject.kt b/multisrc/overrides/madara/kakuseiproject/src/KakuseiProject.kt new file mode 100644 index 0000000000..25409b4a67 --- /dev/null +++ b/multisrc/overrides/madara/kakuseiproject/src/KakuseiProject.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.kakuseiproject + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class KakuseiProject : Madara( + "Kakusei Project", + "https://kakuseiproject.com.br", + "pt-BR", + SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/kalangoscan/src/KalangoScan.kt b/multisrc/overrides/madara/kalangoscan/src/KalangoScan.kt deleted file mode 100644 index e5f19ebeec..0000000000 --- a/multisrc/overrides/madara/kalangoscan/src/KalangoScan.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.kalangoscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class KalangoScan : Madara( - "Kalango Scan", - "https://kalangoscan.online", - "pt-BR", - SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/kataitake/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/kataitake/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..8a7c31d5e0 Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kataitake/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/kataitake/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..0c1bc2802f Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kataitake/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/kataitake/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1d29bf452c Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kataitake/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/kataitake/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..670bf77366 Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kataitake/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/kataitake/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cef93935d8 Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kataitake/res/web_hi_res_512.png b/multisrc/overrides/madara/kataitake/res/web_hi_res_512.png new file mode 100644 index 0000000000..6b3e83781a Binary files /dev/null and b/multisrc/overrides/madara/kataitake/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/kataitake/src/Kataitake.kt b/multisrc/overrides/madara/kataitake/src/Kataitake.kt new file mode 100644 index 0000000000..99080f11bd --- /dev/null +++ b/multisrc/overrides/madara/kataitake/src/Kataitake.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.fr.kataitake + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class Kataitake : Madara("Kataitake", "https://www.kataitake.fr", "fr", dateFormat = SimpleDateFormat("dd/mm/yyyy", Locale.FRANCE)) { + override val altName: String = "Noms alternatifs :" +} diff --git a/multisrc/overrides/madara/kiara/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/kiara/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6f5fdc059e..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/kiara/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1b5135d2bd..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/kiara/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bd4eb6888c..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/kiara/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ab0518c481..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/kiara/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ddc9ce6a32..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/res/web_hi_res_512.png b/multisrc/overrides/madara/kiara/res/web_hi_res_512.png deleted file mode 100644 index 8d8d9f2c12..0000000000 Binary files a/multisrc/overrides/madara/kiara/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/kiara/src/Kiara.kt b/multisrc/overrides/madara/kiara/src/Kiara.kt deleted file mode 100644 index 95e257d416..0000000000 --- a/multisrc/overrides/madara/kiara/src/Kiara.kt +++ /dev/null @@ -1,11 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.kiara - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.Page -import org.jsoup.nodes.Document - -class Kiara : Madara("Kiara", "https://kiara.cool", "en") { - override fun pageListParse(document: Document): List { - return super.pageListParse(document).filterIndexed { index, _ -> index % 2 == 0 } - } -} diff --git a/multisrc/overrides/madara/kingsmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/kingsmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e5de1eaf75 Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kingsmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/kingsmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..70ccfe481e Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kingsmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/kingsmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..cdb900aa7d Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kingsmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/kingsmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3f9485894b Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kingsmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/kingsmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ad0c337369 Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/kingsmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/kingsmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..c15978aa10 Binary files /dev/null and b/multisrc/overrides/madara/kingsmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/kingsmanga/src/KingsManga.kt b/multisrc/overrides/madara/kingsmanga/src/KingsManga.kt new file mode 100644 index 0000000000..adf99355df --- /dev/null +++ b/multisrc/overrides/madara/kingsmanga/src/KingsManga.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.th.kingsmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class KingsManga : Madara( + "Kings-Manga", + "https://www.kings-manga.co", + "th", + dateFormat = SimpleDateFormat("d MMMM yyyy", Locale("th")), +) { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/koinoboriscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/koinoboriscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4e63d01ef8 Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/koinoboriscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..cb3bf91585 Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..67ee088e6a Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1e363666d4 Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cc3885bc78 Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/res/web_hi_res_512.png b/multisrc/overrides/madara/koinoboriscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..54ec5ad667 Binary files /dev/null and b/multisrc/overrides/madara/koinoboriscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/koinoboriscan/src/KoinoboriScan.kt b/multisrc/overrides/madara/koinoboriscan/src/KoinoboriScan.kt new file mode 100644 index 0000000000..332da54fea --- /dev/null +++ b/multisrc/overrides/madara/koinoboriscan/src/KoinoboriScan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.es.koinoboriscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale + +class KoinoboriScan : Madara( + "Koinobori Scan", + "https://koinoboriscan.com", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1) + .build() +} diff --git a/multisrc/overrides/madara/komikgue/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/komikgue/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..6aa99fd502 Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/komikgue/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/komikgue/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..006ad96cc1 Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/komikgue/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/komikgue/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6b2257f69c Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/komikgue/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/komikgue/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c021046ced Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/komikgue/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/komikgue/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1822e6a36a Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/komikgue/res/web_hi_res_512.png b/multisrc/overrides/madara/komikgue/res/web_hi_res_512.png new file mode 100644 index 0000000000..2a283540a1 Binary files /dev/null and b/multisrc/overrides/madara/komikgue/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/komikgue/src/KomikGue.kt b/multisrc/overrides/madara/komikgue/src/KomikGue.kt new file mode 100644 index 0000000000..d067b98c4b --- /dev/null +++ b/multisrc/overrides/madara/komikgue/src/KomikGue.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.id.komikgue + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class KomikGue : Madara( + "Komik Gue", + "https://komikgue.pro", + "id", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")), +) { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/ladyestelarscan/src/LadyEstelarScan.kt b/multisrc/overrides/madara/ladyestelarscan/src/LadyEstelarScan.kt new file mode 100644 index 0000000000..af4b8153d4 --- /dev/null +++ b/multisrc/overrides/madara/ladyestelarscan/src/LadyEstelarScan.kt @@ -0,0 +1,63 @@ +package eu.kanade.tachiyomi.extension.pt.ladyestelarscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Response +import rx.Observable +import java.lang.IllegalStateException +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class LadyEstelarScan : Madara( + "Lady Estelar Scan", + "https://ladyestelarscan.com.br", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override fun fetchPopularManga(page: Int): Observable { + return client.newCall(popularMangaRequest(page)) + .asObservableSuccessIgnoreCode(404) + .map(::popularMangaParse) + } + + override fun fetchLatestUpdates(page: Int): Observable { + return client.newCall(latestUpdatesRequest(page)) + .asObservableSuccessIgnoreCode(404) + .map(::latestUpdatesParse) + } + + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccessIgnoreCode(404) + .map(::searchMangaParse) + } + + /** + * Their site have some issues and is returning 404 in some pages even if they exist. + */ + private fun Call.asObservableSuccessIgnoreCode(code: Int): Observable { + return asObservable().doOnNext { response -> + if (!response.isSuccessful && response.code != code) { + response.close() + throw IllegalStateException("HTTP error ${response.code}") + } + } + } +} diff --git a/multisrc/overrides/madara/legionscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/legionscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8f6903b252..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/legionscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bb25ada879..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/legionscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 40ab1c2e17..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/legionscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9568ebd609..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/legionscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e15f210a3f..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/res/web_hi_res_512.png b/multisrc/overrides/madara/legionscan/res/web_hi_res_512.png deleted file mode 100644 index 9518e886fc..0000000000 Binary files a/multisrc/overrides/madara/legionscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/legionscan/src/LegionScan.kt b/multisrc/overrides/madara/legionscan/src/LegionScan.kt deleted file mode 100644 index 7ed52c5497..0000000000 --- a/multisrc/overrides/madara/legionscan/src/LegionScan.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.legionscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class LegionScan : Madara("Legion Scan", "https://legionscans.com", "es", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es"))) diff --git a/multisrc/overrides/madara/leviatanscans/src/LeviatanScans.kt b/multisrc/overrides/madara/leviatanscans/src/LeviatanScans.kt index 247bd399c0..611cb5e874 100644 --- a/multisrc/overrides/madara/leviatanscans/src/LeviatanScans.kt +++ b/multisrc/overrides/madara/leviatanscans/src/LeviatanScans.kt @@ -1,21 +1,24 @@ -package eu.kanade.tachiyomi.extension.all.leviatanscans +package eu.kanade.tachiyomi.extension.en.leviatanscans import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import org.jsoup.nodes.Element import java.text.SimpleDateFormat +import java.util.Locale -abstract class LeviatanScans( - baseUrl: String, - lang: String, - dateFormat: SimpleDateFormat, -) : Madara( +class LeviatanScans : Madara( "Leviatan Scans", - baseUrl, - lang, - dateFormat, + "https://lscomic.com", + "en", + dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.US), ) { + + override val id = 4055499394183150749 + + override val mangaDetailsSelectorDescription = "div.manga-summary" + override val mangaDetailsSelectorAuthor = "div.manga-authors" + override val useNewChapterEndpoint: Boolean = true override fun chapterListSelector() = "li.wp-manga-chapter:not(.premium-block)" diff --git a/multisrc/overrides/madara/leviatanscans/src/LeviatanScansFactory.kt b/multisrc/overrides/madara/leviatanscans/src/LeviatanScansFactory.kt deleted file mode 100644 index d110edad1a..0000000000 --- a/multisrc/overrides/madara/leviatanscans/src/LeviatanScansFactory.kt +++ /dev/null @@ -1,36 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.leviatanscans - -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale - -class LeviatanScansFactory : SourceFactory { - override fun createSources(): List = listOf( - LeviatanScansEN(), - LeviatanScansES(), - ) -} - -class LeviatanScansEN : LeviatanScans( - "https://en.leviatanscans.com", - "en", - SimpleDateFormat("MMM dd, yyyy", Locale.US), -) { - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2) - .build() - - override val mangaDetailsSelectorDescription = "div.manga-summary" - override val mangaDetailsSelectorAuthor = "div.manga-authors" -} - -class LeviatanScansES : LeviatanScans( - "https://es.leviatanscans.com", - "es", - SimpleDateFormat("MMM dd, yy", Locale("es")), -) { - override val mangaDetailsSelectorStatus = ".post-content_item:contains(Status) .summary-content" -} diff --git a/multisrc/overrides/madara/limascans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/limascans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9689b437a8..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/limascans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c386746eeb..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/limascans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d7038fb484..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/limascans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0feba0bae0..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/limascans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 912927f9c9..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/res/web_hi_res_512.png b/multisrc/overrides/madara/limascans/res/web_hi_res_512.png deleted file mode 100644 index ced8ba5dea..0000000000 Binary files a/multisrc/overrides/madara/limascans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/limascans/src/LimaScans.kt b/multisrc/overrides/madara/limascans/src/LimaScans.kt deleted file mode 100644 index a811c9afac..0000000000 --- a/multisrc/overrides/madara/limascans/src/LimaScans.kt +++ /dev/null @@ -1,31 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.limascans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.OkHttpClient -import okhttp3.Request -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class LimaScans : Madara( - "Lima Scans", - "http://limascans.xyz/v2", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl + manga.url.removePrefix("/v2"), headers) - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url.removePrefix("/v2"), headers) - } -} diff --git a/multisrc/overrides/madara/limboscan/src/LimboScan.kt b/multisrc/overrides/madara/limboscan/src/LimboScan.kt new file mode 100644 index 0000000000..1d9181b562 --- /dev/null +++ b/multisrc/overrides/madara/limboscan/src/LimboScan.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.pt.limboscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class LimboScan : Madara( + "Limbo Scan", + "https://limboscan.com.br", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val mangaSubString = "obras" + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/linkstartscan/src/LinkStartScan.kt b/multisrc/overrides/madara/linkstartscan/src/LinkStartScan.kt new file mode 100644 index 0000000000..c0df72aaeb --- /dev/null +++ b/multisrc/overrides/madara/linkstartscan/src/LinkStartScan.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.pt.linkstartscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale + +class LinkStartScan : Madara( + "Link Start Scan", + "https://www.linkstartscan.xyz", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/lkscanlation/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/lkscanlation/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4084c218be Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/lkscanlation/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/lkscanlation/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..5897927bf1 Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/lkscanlation/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/lkscanlation/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..a34adfd897 Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/lkscanlation/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/lkscanlation/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..799a13cbc2 Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/lkscanlation/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/lkscanlation/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f2809cecdc Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/lkscanlation/res/web_hi_res_512.png b/multisrc/overrides/madara/lkscanlation/res/web_hi_res_512.png new file mode 100644 index 0000000000..6883f143b0 Binary files /dev/null and b/multisrc/overrides/madara/lkscanlation/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/lkscanlation/src/LKScanlation.kt b/multisrc/overrides/madara/lkscanlation/src/LKScanlation.kt new file mode 100644 index 0000000000..ff70698ddb --- /dev/null +++ b/multisrc/overrides/madara/lkscanlation/src/LKScanlation.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.extension.es.lkscanlation + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale + +class LKScanlation : Madara( + "Last Knight Translation", + "https://lkscanlation.com", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1) + .build() + + override val useNewChapterEndpoint = true + + override val mangaSubString = "manhwa" + + override val popularMangaUrlSelector = "div.post-title a:not([target='_self'])" + + override val mangaDetailsSelectorAuthor = "div.manga-authors > a" + override val mangaDetailsSelectorDescription = "div.manga-summary" +} diff --git a/multisrc/overrides/madara/luffymanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/luffymanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..fd2b83cad9 Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/luffymanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/luffymanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b70207b1d1 Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/luffymanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/luffymanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dfd26d5488 Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/luffymanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/luffymanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4cb00558fb Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/luffymanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/luffymanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fe2edcf4fe Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/luffymanga/res/web_hi_res_512.png b/multisrc/overrides/madara/luffymanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..61ff34eee9 Binary files /dev/null and b/multisrc/overrides/madara/luffymanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/luffymanga/src/LuffyManga.kt b/multisrc/overrides/madara/luffymanga/src/LuffyManga.kt new file mode 100644 index 0000000000..36a37b3afa --- /dev/null +++ b/multisrc/overrides/madara/luffymanga/src/LuffyManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.luffymanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class LuffyManga : Madara("Luffy Manga", "https://luffymanga.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/maidscan/src/MaidScan.kt b/multisrc/overrides/madara/maidscan/src/MaidScan.kt new file mode 100644 index 0000000000..2ea8073d09 --- /dev/null +++ b/multisrc/overrides/madara/maidscan/src/MaidScan.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.maidscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class MaidScan : Madara( + "Maid Scan", + "https://maidscan.com.br", + "pt-BR", + SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/manga18fx/src/Manga18fx.kt b/multisrc/overrides/madara/manga18fx/src/Manga18fx.kt index 43592535da..87c54b9ea2 100644 --- a/multisrc/overrides/madara/manga18fx/src/Manga18fx.kt +++ b/multisrc/overrides/madara/manga18fx/src/Manga18fx.kt @@ -26,8 +26,6 @@ class Manga18fx : Madara( ) { override val id = 3157287889751723714 - override val client = network.client - override val fetchGenres = false override val sendViewCount = false diff --git a/multisrc/overrides/madara/manga18h/src/Manga18h.kt b/multisrc/overrides/madara/manga18h/src/Manga18h.kt new file mode 100644 index 0000000000..ef951efe49 --- /dev/null +++ b/multisrc/overrides/madara/manga18h/src/Manga18h.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manga18h + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Manga18h : Madara("Manga 18h", "https://manga18h.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manga18x/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manga18x/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..bb728ca0f0 Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manga18x/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manga18x/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..2e44b77c85 Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manga18x/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manga18x/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4ed5e495ce Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manga18x/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga18x/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..96a508391f Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manga18x/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga18x/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..49621c939d Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manga18x/res/web_hi_res_512.png b/multisrc/overrides/madara/manga18x/res/web_hi_res_512.png new file mode 100644 index 0000000000..99c220a077 Binary files /dev/null and b/multisrc/overrides/madara/manga18x/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manga18x/src/Manga18x.kt b/multisrc/overrides/madara/manga18x/src/Manga18x.kt new file mode 100644 index 0000000000..509fc67251 --- /dev/null +++ b/multisrc/overrides/madara/manga18x/src/Manga18x.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manga18x + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Manga18x : Madara("Manga 18x", "https://manga18x.net", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manga1st/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manga1st/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f36a5c2a0d..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga1st/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manga1st/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 819f5b2cb0..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga1st/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manga1st/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6ce1565f9b..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga1st/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga1st/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fb6d7684fb..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga1st/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga1st/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ce3c26098a..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga1st/res/web_hi_res_512.png b/multisrc/overrides/madara/manga1st/res/web_hi_res_512.png deleted file mode 100644 index 9c82d8117d..0000000000 Binary files a/multisrc/overrides/madara/manga1st/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manga4all/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d7044f3248..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manga4all/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0bf5afcff2..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manga4all/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 472e52f9bf..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga4all/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5711a845bf..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manga4all/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 42fb8dd08f..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/res/web_hi_res_512.png b/multisrc/overrides/madara/manga4all/res/web_hi_res_512.png deleted file mode 100644 index 808045b49d..0000000000 Binary files a/multisrc/overrides/madara/manga4all/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/manga4all/src/Manga4All.kt b/multisrc/overrides/madara/manga4all/src/Manga4All.kt deleted file mode 100644 index 84a04622ad..0000000000 --- a/multisrc/overrides/madara/manga4all/src/Manga4All.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.manga4all - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class Manga4All : Madara( - "Manga4All", - "https://manga4all.net", - "en", - dateFormat = SimpleDateFormat("d MMM yyyy", Locale.US), -) diff --git a/multisrc/overrides/madara/mangaarabteam/src/MangaArabTeam.kt b/multisrc/overrides/madara/mangaarabteam/src/MangaArabTeam.kt deleted file mode 100644 index ac2620f9c3..0000000000 --- a/multisrc/overrides/madara/mangaarabteam/src/MangaArabTeam.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.mangaarabteam - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Page -import okhttp3.Request -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaArabTeam : Madara("مانجا عرب تيم Manga Arab Team", "https://mangaarbteam.com", "ar", SimpleDateFormat("dd MMM، yyyy", Locale.forLanguageTag("ar"))) { - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!.replace("http:", "https:")) - } -} diff --git a/multisrc/overrides/madara/mangabee/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangabee/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..93c827bc0f Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangabee/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangabee/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a14794ca78 Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangabee/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabee/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..31e8ceb2fa Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangabee/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabee/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ad483f5ba Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangabee/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabee/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..775c3c6b6a Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangabee/res/web_hi_res_512.png b/multisrc/overrides/madara/mangabee/res/web_hi_res_512.png new file mode 100644 index 0000000000..cc60e72e2d Binary files /dev/null and b/multisrc/overrides/madara/mangabee/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangabee/src/MangaBee.kt b/multisrc/overrides/madara/mangabee/src/MangaBee.kt new file mode 100644 index 0000000000..9a80c001c6 --- /dev/null +++ b/multisrc/overrides/madara/mangabee/src/MangaBee.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.en.mangabee + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaBee : Madara( + "Manga Bee", + "https://mangabee.net", + "en", + dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.ROOT), +) { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangabilgini/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangabilgini/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f14515d90b..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangabilgini/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 266b5527f6..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabilgini/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index aab5aecb1b..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabilgini/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 56cac9f444..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangabilgini/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0efbad0854..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/res/web_hi_res_512.png b/multisrc/overrides/madara/mangabilgini/res/web_hi_res_512.png deleted file mode 100644 index 1e901e7cec..0000000000 Binary files a/multisrc/overrides/madara/mangabilgini/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangabilgini/src/MangaBilgini.kt b/multisrc/overrides/madara/mangabilgini/src/MangaBilgini.kt deleted file mode 100644 index 47c51c4192..0000000000 --- a/multisrc/overrides/madara/mangabilgini/src/MangaBilgini.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.mangabilgini - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaBilgini : Madara( - "Manga Bilgini", - "https://mangabilgini.com", - "tr", - dateFormat = SimpleDateFormat("MMM d, yyy", Locale("tr")), -) { - override val useNewChapterEndpoint = false -} diff --git a/multisrc/overrides/madara/mangaclashtv/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaclashtv/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9c6e5e1808 Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaclashtv/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..125060730d Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1cab39ea25 Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..eec512f713 Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..30ba26fdfe Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaclashtv/res/web_hi_res_512.png new file mode 100644 index 0000000000..b64f6b9565 Binary files /dev/null and b/multisrc/overrides/madara/mangaclashtv/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaclashtv/src/MangaClashTv.kt b/multisrc/overrides/madara/mangaclashtv/src/MangaClashTv.kt new file mode 100644 index 0000000000..9f85cc2176 --- /dev/null +++ b/multisrc/overrides/madara/mangaclashtv/src/MangaClashTv.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaclashtv + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaClashTv : Madara("MangaClash.tv (unoriginal)", "https://mangaclash.tv", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangacrab/src/MangaCrab.kt b/multisrc/overrides/madara/mangacrab/src/MangaCrab.kt index d5333863c9..16d44f2348 100644 --- a/multisrc/overrides/madara/mangacrab/src/MangaCrab.kt +++ b/multisrc/overrides/madara/mangacrab/src/MangaCrab.kt @@ -7,7 +7,7 @@ import java.util.Locale class MangaCrab : Madara( "Manga Crab", - "https://mangacrab.com", + "https://mangacrab3.com", "es", SimpleDateFormat("dd/MM/yyyy", Locale("es")), ) { @@ -15,6 +15,8 @@ class MangaCrab : Madara( .rateLimit(1, 2) .build() + override val mangaSubString = "series" + override fun chapterListSelector() = "div.listing-chapters_wrap > ul > li" - override val mangaDetailsSelectorDescription = "div.c-page__content div.contenedor" + override val mangaDetailsSelectorDescription = "div.c-page__content div.modal-contenido" } diff --git a/multisrc/overrides/madara/mangacrazy/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangacrazy/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b14dad52b4 Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangacrazy/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangacrazy/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4905f2c0b6 Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangacrazy/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangacrazy/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..3f3b88fe22 Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangacrazy/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangacrazy/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1f6922f2e1 Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangacrazy/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangacrazy/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..975a5ff3e1 Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangacrazy/res/web_hi_res_512.png b/multisrc/overrides/madara/mangacrazy/res/web_hi_res_512.png new file mode 100644 index 0000000000..2880c3b52b Binary files /dev/null and b/multisrc/overrides/madara/mangacrazy/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangacrazy/src/MangaCrazy.kt b/multisrc/overrides/madara/mangacrazy/src/MangaCrazy.kt new file mode 100644 index 0000000000..c9fbab93f8 --- /dev/null +++ b/multisrc/overrides/madara/mangacrazy/src/MangaCrazy.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.all.mangacrazy + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaCrazy : Madara("MangaCrazy", "https://mangacrazy.net", "all") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangadash1001com/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangadash1001com/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..33f91b06d4 Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangadash1001com/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8a97e25b44 Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..65907a275b Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..426772bfac Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..109ecc4dd3 Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/res/web_hi_res_512.png b/multisrc/overrides/madara/mangadash1001com/res/web_hi_res_512.png new file mode 100644 index 0000000000..a2cb2b9862 Binary files /dev/null and b/multisrc/overrides/madara/mangadash1001com/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangadash1001com/src/MangaDash1001Com.kt b/multisrc/overrides/madara/mangadash1001com/src/MangaDash1001Com.kt new file mode 100644 index 0000000000..88b3cd0ead --- /dev/null +++ b/multisrc/overrides/madara/mangadash1001com/src/MangaDash1001Com.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangadash1001com + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaDash1001Com : Madara("Manga-1001.com", "https://manga-1001.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangadinotop/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangadinotop/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d8bbacd490 Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadinotop/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangadinotop/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ec35ead1d0 Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadinotop/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadinotop/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bd959ac04d Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadinotop/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadinotop/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..58989ca02b Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadinotop/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangadinotop/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ae51ec0a2 Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangadinotop/res/web_hi_res_512.png b/multisrc/overrides/madara/mangadinotop/res/web_hi_res_512.png new file mode 100644 index 0000000000..a624bd17dd Binary files /dev/null and b/multisrc/overrides/madara/mangadinotop/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangadinotop/src/MangaDinoTop.kt b/multisrc/overrides/madara/mangadinotop/src/MangaDinoTop.kt new file mode 100644 index 0000000000..ef4d3cce17 --- /dev/null +++ b/multisrc/overrides/madara/mangadinotop/src/MangaDinoTop.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangadinotop + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaDinoTop : Madara("MangaDino.top (unoriginal)", "https://mangadino.top", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangadistrict/src/MangaDistrict.kt b/multisrc/overrides/madara/mangadistrict/src/MangaDistrict.kt index 5f10b78e60..515e522955 100644 --- a/multisrc/overrides/madara/mangadistrict/src/MangaDistrict.kt +++ b/multisrc/overrides/madara/mangadistrict/src/MangaDistrict.kt @@ -1,12 +1,11 @@ package eu.kanade.tachiyomi.extension.en.mangadistrict import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale class MangaDistrict : Madara( "Manga District", "https://mangadistrict.com", "en", - dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US), -) +) { + override fun searchMangaNextPageSelector() = "div[role=navigation] a.last" +} diff --git a/multisrc/overrides/madara/mangadods/src/MangaDods.kt b/multisrc/overrides/madara/mangadods/src/MangaDods.kt index 1ae4bcf2c9..777e228ad1 100644 --- a/multisrc/overrides/madara/mangadods/src/MangaDods.kt +++ b/multisrc/overrides/madara/mangadods/src/MangaDods.kt @@ -4,4 +4,12 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara import java.text.SimpleDateFormat import java.util.Locale -class MangaDods : Madara("MangaDods", "https://www.mangadods.com", "en", SimpleDateFormat("yyyy-MM-dd", Locale.US)) +class MangaDods : Madara("MangaDods", "https://mangadods.com", "en", SimpleDateFormat("dd-MMM", Locale.US)) { + override val useNewChapterEndpoint = true + + override val chapterUrlSelector = "a:not([style])" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun chapterDateSelector() = "span i" +} diff --git a/multisrc/overrides/madara/mangafenix/src/MangaFenix.kt b/multisrc/overrides/madara/mangafenix/src/MangaFenix.kt index 5716d90f3e..ba431a7dbf 100644 --- a/multisrc/overrides/madara/mangafenix/src/MangaFenix.kt +++ b/multisrc/overrides/madara/mangafenix/src/MangaFenix.kt @@ -8,7 +8,7 @@ import java.util.Locale class MangaFenix : Madara( "Manga Fenix", - "https://manga-fenix.com", + "https://manhua-fenix.com", "es", SimpleDateFormat("dd MMMM, yyyy", Locale("es")), ) { diff --git a/multisrc/overrides/madara/mangagoyaoi/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..09983f687e Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..77e4aa64fd Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d95dc46ab9 Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ec53998fe8 Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..29d1ba100c Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/res/web_hi_res_512.png b/multisrc/overrides/madara/mangagoyaoi/res/web_hi_res_512.png new file mode 100644 index 0000000000..a9978cae15 Binary files /dev/null and b/multisrc/overrides/madara/mangagoyaoi/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangagoyaoi/src/MangaGoYaoi.kt b/multisrc/overrides/madara/mangagoyaoi/src/MangaGoYaoi.kt new file mode 100644 index 0000000000..ec62c7bc45 --- /dev/null +++ b/multisrc/overrides/madara/mangagoyaoi/src/MangaGoYaoi.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangagoyaoi + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaGoYaoi : Madara("MangaGo Yaoi", "https://mangagoyaoi.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangagreat/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangagreat/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c153fca913..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangagreat/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5773957bb7..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagreat/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b23c047536..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagreat/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a0bac61ee7..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangagreat/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b1e1803171..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/res/web_hi_res_512.png b/multisrc/overrides/madara/mangagreat/res/web_hi_res_512.png deleted file mode 100644 index bae60d6b49..0000000000 Binary files a/multisrc/overrides/madara/mangagreat/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangagreat/src/MangaGreat.kt b/multisrc/overrides/madara/mangagreat/src/MangaGreat.kt deleted file mode 100644 index 6fddf8cf06..0000000000 --- a/multisrc/overrides/madara/mangagreat/src/MangaGreat.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangagreat - -import eu.kanade.tachiyomi.multisrc.madara.Madara - -class MangaGreat : Madara("MangaGreat", "https://mangagreat.com", "en") { - - // The website does not flag the content. - override val filterNonMangaItems = false -} diff --git a/multisrc/overrides/madara/mangahentai/src/MangaHentai.kt b/multisrc/overrides/madara/mangahentai/src/MangaHentai.kt index 808ed82985..3d5de98bd5 100644 --- a/multisrc/overrides/madara/mangahentai/src/MangaHentai.kt +++ b/multisrc/overrides/madara/mangahentai/src/MangaHentai.kt @@ -3,5 +3,9 @@ package eu.kanade.tachiyomi.extension.en.mangahentai import eu.kanade.tachiyomi.multisrc.madara.Madara class MangaHentai : Madara("Manga Hentai", "https://mangahentai.me", "en") { + override val mangaSubString = "manga-hentai" + override val useNewChapterEndpoint: Boolean = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" } diff --git a/multisrc/overrides/madara/mangahubfr/src/MangaHubFr.kt b/multisrc/overrides/madara/mangahubfr/src/MangaHubFr.kt index 4cb3a70404..2794d135b1 100644 --- a/multisrc/overrides/madara/mangahubfr/src/MangaHubFr.kt +++ b/multisrc/overrides/madara/mangahubfr/src/MangaHubFr.kt @@ -1,9 +1,25 @@ package eu.kanade.tachiyomi.extension.fr.mangahubfr import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.Page +import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale class MangaHubFr : Madara("MangaHub.fr", "https://mangahub.fr", "fr", dateFormat = SimpleDateFormat("d MMMM yyyy", Locale.FRENCH)) { - override fun chapterListSelector() = "li.wp-mangas-chapters" + // Only display chapters which don't have Premium + override fun chapterListSelector() = "li.wp-manga-chapter:not(.vip-permission)" + + override val chapterUrlSuffix = "" + + override fun pageListParse(document: Document): List { + countViews(document) + + return document.select(pageListParseSelector) + .mapIndexed { index, element -> + // Had to add trim because of white space in source. + val imageUrl = element.select("img").attr("abs:src").trim() + Page(index, document.location(), imageUrl) + } + } } diff --git a/multisrc/overrides/madara/mangakakalotio/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangakakalotio/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..397ccc6d19 Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangakakalotio/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..67bbbe769a Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b51b2f7b04 Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4ae5101959 Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4449730233 Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/res/web_hi_res_512.png b/multisrc/overrides/madara/mangakakalotio/res/web_hi_res_512.png new file mode 100644 index 0000000000..202ad7c00b Binary files /dev/null and b/multisrc/overrides/madara/mangakakalotio/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangakakalotio/src/MangakakalotIo.kt b/multisrc/overrides/madara/mangakakalotio/src/MangakakalotIo.kt new file mode 100644 index 0000000000..be17512b48 --- /dev/null +++ b/multisrc/overrides/madara/mangakakalotio/src/MangakakalotIo.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangakakalotio + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangakakalotIo : Madara("Mangakakalot.io (unoriginal)", "https://mangakakalot.io", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangakakalotone/src/MangakakalotOne.kt b/multisrc/overrides/madara/mangakakalotone/src/MangakakalotOne.kt new file mode 100644 index 0000000000..b82935347e --- /dev/null +++ b/multisrc/overrides/madara/mangakakalotone/src/MangakakalotOne.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangakakalotone + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangakakalotOne : Madara("Mangakakalot.one (unoriginal)", "https://mangakakalot.one", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangakio/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangakio/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bc75d71472..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangakio/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ba32c2e054..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakio/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 418f7e546d..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakio/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 87eebefaa7..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangakio/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2eecfc87d6..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/res/web_hi_res_512.png b/multisrc/overrides/madara/mangakio/res/web_hi_res_512.png deleted file mode 100644 index d7be8a5dcd..0000000000 Binary files a/multisrc/overrides/madara/mangakio/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangakio/src/MangaKio.kt b/multisrc/overrides/madara/mangakio/src/MangaKio.kt deleted file mode 100644 index 967e096554..0000000000 --- a/multisrc/overrides/madara/mangakio/src/MangaKio.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangakio - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaKio : Madara( - "Manga Kio", - "https://mangakio.com", - "en", - dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US), -) { - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/mangakitsu/src/MangaKitsu.kt b/multisrc/overrides/madara/mangakitsu/src/MangaKitsu.kt new file mode 100644 index 0000000000..db609628e7 --- /dev/null +++ b/multisrc/overrides/madara/mangakitsu/src/MangaKitsu.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.mangakitsu + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaKitsu : Madara("Manga Kitsu", "https://mangakitsu.com", "en") { + override val useNewChapterEndpoint = false + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaland/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaland/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..5223ee1804 Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaland/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaland/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a97240fc84 Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaland/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaland/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d02e32ebe7 Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaland/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaland/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6ed828bb15 Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaland/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaland/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5a7f9e377d Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaland/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaland/res/web_hi_res_512.png new file mode 100644 index 0000000000..e7d3a3f408 Binary files /dev/null and b/multisrc/overrides/madara/mangaland/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaland/src/Mangaland.kt b/multisrc/overrides/madara/mangaland/src/Mangaland.kt new file mode 100644 index 0000000000..caafbe629c --- /dev/null +++ b/multisrc/overrides/madara/mangaland/src/Mangaland.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.extension.es.mangaland + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.FormBody +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class Mangaland : Madara( + "Mangaland", + "https://mangaland.net", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "full") + add("vars[meta_query][0][0][key]", "_wp_manga_chapter_type") + add("vars[meta_query][0][0][value]", "manga") + add("vars[meta_query][0][relation]", "AND") + add("vars[meta_query][relation]", "AND") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[manga_archives_item_layout]", "big_thumbnail") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } +} diff --git a/multisrc/overrides/madara/mangalek/src/Mangalek.kt b/multisrc/overrides/madara/mangalek/src/Mangalek.kt index 1e2ccdf0ee..bc5611341d 100644 --- a/multisrc/overrides/madara/mangalek/src/Mangalek.kt +++ b/multisrc/overrides/madara/mangalek/src/Mangalek.kt @@ -1,10 +1,116 @@ package eu.kanade.tachiyomi.extension.ar.mangalek +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import okhttp3.CacheControl +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale -class Mangalek : Madara("مانجا ليك", "https://mangalek.com", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) { +class Mangalek : Madara("مانجا ليك", "https://manga-lek.net", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) { override val chapterUrlSuffix = "" + + private val defaultBaseUrl = "https://manga-lek.net" + override val baseUrl by lazy { getPrefBaseUrl() } + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + companion object { + private const val RESTART_TACHIYOMI = ".لتطبيق الإعدادات الجديدة Tachiyomi أعد تشغيل" + private const val BASE_URL_PREF_TITLE = "تعديل الرابط" + private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_CODE}" + private const val BASE_URL_PREF_SUMMARY = ".للاستخدام المؤقت. تحديث التطبيق سيؤدي الى حذف الإعدادات" + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { + key = BASE_URL_PREF + title = BASE_URL_PREF_TITLE + summary = BASE_URL_PREF_SUMMARY + this.setDefaultValue(defaultBaseUrl) + dialogTitle = BASE_URL_PREF_TITLE + dialogMessage = "Default: $defaultBaseUrl" + + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + true + } + } + screen.addPreference(baseUrlPref) + + super.setupPreferenceScreen(screen) + } + + private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! + + override fun popularMangaRequest(page: Int): Request { + return GET( + url = "$baseUrl/$mangaSubString/${searchPage(page)}", + headers = headers, + cache = CacheControl.FORCE_NETWORK, + ) + } + + override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + POST( + "$baseUrl/wp-admin/admin-ajax.php", + headers, + FormBody.Builder() + .add("action", "wp-manga-search-manga") + .add("title", query) + .build(), + ) + + private inline fun Response.parseAs(): T { + return json.decodeFromString(body.string()) + } + + @Serializable + data class SearchResponseDto( + val data: List, + val success: Boolean, + ) + + @Serializable + data class SearchEntryDto( + val url: String = "", + val title: String = "", + ) + + override fun searchMangaParse(response: Response): MangasPage { + val dto = response.parseAs() + + if (!dto.success) { + return MangasPage(emptyList(), false) + } + + val manga = dto.data.map { + SManga.create().apply { + setUrlWithoutDomain(it.url) + title = it.title + } + } + + return MangasPage(manga, false) + } } diff --git a/multisrc/overrides/madara/mangaleks/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaleks/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..348915430d Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaleks/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaleks/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..345012bc3d Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaleks/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaleks/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2e7af18d5f Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaleks/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaleks/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7ae8ef32a7 Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaleks/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaleks/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8843070919 Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaleks/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaleks/res/web_hi_res_512.png new file mode 100644 index 0000000000..242a833dac Binary files /dev/null and b/multisrc/overrides/madara/mangaleks/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaleks/src/MangaLeks.kt b/multisrc/overrides/madara/mangaleks/src/MangaLeks.kt new file mode 100644 index 0000000000..c17a92efb1 --- /dev/null +++ b/multisrc/overrides/madara/mangaleks/src/MangaLeks.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.ar.mangaleks + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaLeks : Madara( + "مانجا ليكس", + "https://mangaleks.com", + "ar", + SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH), +) { + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/mangalionz/src/MangaLionz.kt b/multisrc/overrides/madara/mangalionz/src/MangaLionz.kt index 75180ac696..908bedc820 100644 --- a/multisrc/overrides/madara/mangalionz/src/MangaLionz.kt +++ b/multisrc/overrides/madara/mangalionz/src/MangaLionz.kt @@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.source.model.SManga import org.jsoup.nodes.Element -class MangaLionz : Madara("MangaLionz", "https://mangalionz.com", "ar") { +class MangaLionz : Madara("MangaLionz", "https://mangalionz.org", "ar") { override fun popularMangaFromElement(element: Element): SManga { val manga = SManga.create() diff --git a/multisrc/overrides/madara/mangamammy/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangamammy/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..652f6cc073 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangamammy/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangamammy/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f810ec5566 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangamammy/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamammy/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c074271fb0 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangamammy/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamammy/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d5d3a60485 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangamammy/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamammy/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..219a3c8960 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangamammy/res/web_hi_res_512.png b/multisrc/overrides/madara/mangamammy/res/web_hi_res_512.png new file mode 100644 index 0000000000..afa9890917 Binary files /dev/null and b/multisrc/overrides/madara/mangamammy/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangamammy/src/MangaMammy.kt b/multisrc/overrides/madara/mangamammy/src/MangaMammy.kt new file mode 100644 index 0000000000..a477f0846b --- /dev/null +++ b/multisrc/overrides/madara/mangamammy/src/MangaMammy.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.extension.ru.mangamammy + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaMammy : Madara( + "Manga Mammy", + "https://mangamammy.ru", + "ru", + dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT), +) { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun popularMangaSelector() = searchMangaSelector() + + override fun popularMangaRequest(page: Int): Request = + searchMangaRequest( + page, + "", + FilterList( + listOf( + OrderByFilter( + "", + listOf( + Pair("", ""), + Pair("", "views"), + ), + 1, + ), + ), + ), + ) + + override fun latestUpdatesRequest(page: Int): Request = + searchMangaRequest( + page, + "", + FilterList( + listOf( + OrderByFilter( + "", + listOf( + Pair("", ""), + Pair("", "latest"), + ), + 1, + ), + ), + ), + ) +} diff --git a/multisrc/overrides/madara/mangamanhua/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangamanhua/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 740450db30..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangamanhua/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0a3ca109f..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamanhua/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 628e2cc88f..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamanhua/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a79191fe76..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangamanhua/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 58fc1cfb87..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/res/web_hi_res_512.png b/multisrc/overrides/madara/mangamanhua/res/web_hi_res_512.png deleted file mode 100644 index d4c64c7da3..0000000000 Binary files a/multisrc/overrides/madara/mangamanhua/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangamanhua/src/MangaManhua.kt b/multisrc/overrides/madara/mangamanhua/src/MangaManhua.kt deleted file mode 100644 index 6765b1f033..0000000000 --- a/multisrc/overrides/madara/mangamanhua/src/MangaManhua.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangamanhua - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaManhua : Madara("MangaManhua", "https://mangamanhua.online", "en", SimpleDateFormat("d MMMM'،' yyyy", Locale.US)) diff --git a/multisrc/overrides/madara/mangame/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangame/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6b9ced9363..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangame/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 45e9b09c60..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangame/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1e2c0bb4ae..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangame/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 05deb3fe4d..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangame/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c0c9c5b7db..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/res/web_hi_res_512.png b/multisrc/overrides/madara/mangame/res/web_hi_res_512.png deleted file mode 100644 index ce80be44e4..0000000000 Binary files a/multisrc/overrides/madara/mangame/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangame/src/MangaMe.kt b/multisrc/overrides/madara/mangame/src/MangaMe.kt deleted file mode 100644 index 12eae13578..0000000000 --- a/multisrc/overrides/madara/mangame/src/MangaMe.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangame - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaMe : Madara( - "MangaMe", - "https://mangame.org", - "en", - dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.US), -) diff --git a/multisrc/overrides/madara/mangananquim/src/MangaNanquim.kt b/multisrc/overrides/madara/mangananquim/src/MangaNanquim.kt new file mode 100644 index 0000000000..6262e90c56 --- /dev/null +++ b/multisrc/overrides/madara/mangananquim/src/MangaNanquim.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.pt.mangananquim + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class MangaNanquim : Madara( + "Mangá Nanquim", + "https://mangananquim.com", + "pt-BR", + SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val mangaSubString = "ler-manga" + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/manganelobiz/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manganelobiz/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d8bbacd490 Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelobiz/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manganelobiz/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ec35ead1d0 Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelobiz/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelobiz/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bd959ac04d Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelobiz/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelobiz/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..58989ca02b Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelobiz/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelobiz/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ae51ec0a2 Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelobiz/res/web_hi_res_512.png b/multisrc/overrides/madara/manganelobiz/res/web_hi_res_512.png new file mode 100644 index 0000000000..a624bd17dd Binary files /dev/null and b/multisrc/overrides/madara/manganelobiz/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manganelobiz/src/ManganeloBiz.kt b/multisrc/overrides/madara/manganelobiz/src/ManganeloBiz.kt new file mode 100644 index 0000000000..81ea5245f6 --- /dev/null +++ b/multisrc/overrides/madara/manganelobiz/src/ManganeloBiz.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manganelobiz + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManganeloBiz : Madara("Manganelo.biz", "https://manganelo.biz", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manganelowebsite/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manganelowebsite/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..03b4a84c38 Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manganelowebsite/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..2706869bb9 Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..982cec3a3d Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a20e946d24 Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..839c009e9c Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/res/web_hi_res_512.png b/multisrc/overrides/madara/manganelowebsite/res/web_hi_res_512.png new file mode 100644 index 0000000000..c6bad55edb Binary files /dev/null and b/multisrc/overrides/madara/manganelowebsite/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manganelowebsite/src/ManganeloWebsite.kt b/multisrc/overrides/madara/manganelowebsite/src/ManganeloWebsite.kt new file mode 100644 index 0000000000..b4c39d9103 --- /dev/null +++ b/multisrc/overrides/madara/manganelowebsite/src/ManganeloWebsite.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manganelowebsite + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManganeloWebsite : Madara("Manganelo.website (unoriginal)", "https://manganelo.website", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manganerds/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manganerds/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..fb9f9b87b1 Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganerds/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manganerds/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8b4ab3601d Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganerds/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manganerds/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8a5ae6a74a Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganerds/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganerds/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c98a79948d Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganerds/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manganerds/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0214a98821 Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manganerds/res/web_hi_res_512.png b/multisrc/overrides/madara/manganerds/res/web_hi_res_512.png new file mode 100644 index 0000000000..723ae884e0 Binary files /dev/null and b/multisrc/overrides/madara/manganerds/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manganerds/src/MangaNerds.kt b/multisrc/overrides/madara/manganerds/src/MangaNerds.kt new file mode 100644 index 0000000000..eb162e60bb --- /dev/null +++ b/multisrc/overrides/madara/manganerds/src/MangaNerds.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manganerds + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaNerds : Madara("Manga Nerds", "https://manganerds.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaonlineco/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineco/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c872377d73..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineco/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 98f0b94832..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineco/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index decd54db3b..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9ee13e3c39..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2732399409..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaonlineco/res/web_hi_res_512.png deleted file mode 100644 index af8adf4234..0000000000 Binary files a/multisrc/overrides/madara/mangaonlineco/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangaonlineco/src/MangaOnlineCo.kt b/multisrc/overrides/madara/mangaonlineco/src/MangaOnlineCo.kt deleted file mode 100644 index c66dfb6fe9..0000000000 --- a/multisrc/overrides/madara/mangaonlineco/src/MangaOnlineCo.kt +++ /dev/null @@ -1,11 +0,0 @@ -package eu.kanade.tachiyomi.extension.th.mangaonlineco - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Response -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaOnlineCo : Madara("Manga-Online.co", "https://www.manga-online.co", "th", SimpleDateFormat("MMM dd, yyyy", Locale("th"))) { - override fun chapterListParse(response: Response): List = super.chapterListParse(response).reversed() -} diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e07fbaf126 Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..6a0426ef1a Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..922aef74f2 Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5782860ca6 Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1fab747f49 Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/web_hi_res_512.png new file mode 100644 index 0000000000..24c0b68c3b Binary files /dev/null and b/multisrc/overrides/madara/mangaonlineteamunoriginal/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaonlineteamunoriginal/src/MangaOnlineTeamUnoriginal.kt b/multisrc/overrides/madara/mangaonlineteamunoriginal/src/MangaOnlineTeamUnoriginal.kt new file mode 100644 index 0000000000..44491dd7bf --- /dev/null +++ b/multisrc/overrides/madara/mangaonlineteamunoriginal/src/MangaOnlineTeamUnoriginal.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaonlineteamunoriginal + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaOnlineTeamUnoriginal : Madara("MangaOnline.team (unoriginal)", "https://mangaonline.team", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaowlblog/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlblog/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4862cd83ed Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlblog/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a8635c99d0 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..f74bb6b32a Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4f46369e89 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..598971deef Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaowlblog/res/web_hi_res_512.png new file mode 100644 index 0000000000..29e87c79b7 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlblog/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaowlblog/src/MangaOwlBlog.kt b/multisrc/overrides/madara/mangaowlblog/src/MangaOwlBlog.kt new file mode 100644 index 0000000000..e51f921831 --- /dev/null +++ b/multisrc/overrides/madara/mangaowlblog/src/MangaOwlBlog.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaowlblog + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaOwlBlog : Madara("MangaOwl.blog (unoriginal)", "https://mangaowl.blog", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaowlio/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlio/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..49bf4a917f Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlio/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlio/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..7951283145 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlio/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlio/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4f51b0e2bf Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlio/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlio/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dcf9aa94fc Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlio/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlio/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..25b5c27792 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlio/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaowlio/res/web_hi_res_512.png new file mode 100644 index 0000000000..1b7dc6b6b7 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlio/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaowlio/src/MangaOwlIo.kt b/multisrc/overrides/madara/mangaowlio/src/MangaOwlIo.kt new file mode 100644 index 0000000000..8f41cc9f7a --- /dev/null +++ b/multisrc/overrides/madara/mangaowlio/src/MangaOwlIo.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaowlio + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaOwlIo : Madara("MangaOwl.io (unoriginal)", "https://mangaowl.io", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaowlone/src/MangaOwlOne.kt b/multisrc/overrides/madara/mangaowlone/src/MangaOwlOne.kt new file mode 100644 index 0000000000..36d7ad2f64 --- /dev/null +++ b/multisrc/overrides/madara/mangaowlone/src/MangaOwlOne.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.mangaowlone + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaOwlOne : Madara("MangaOwl.one (unoriginal)", "https://mangaowl.one", "en") { + override val useNewChapterEndpoint = false + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaowlus/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlus/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..639597b4cb Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlus/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlus/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..894494f762 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlus/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlus/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d61ff9e20a Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlus/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlus/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2f68aecf16 Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlus/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaowlus/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d5f1e7bf4b Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaowlus/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaowlus/res/web_hi_res_512.png new file mode 100644 index 0000000000..7f5c02843b Binary files /dev/null and b/multisrc/overrides/madara/mangaowlus/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaowlus/src/MangaOwlUs.kt b/multisrc/overrides/madara/mangaowlus/src/MangaOwlUs.kt new file mode 100644 index 0000000000..4b5dba41b5 --- /dev/null +++ b/multisrc/overrides/madara/mangaowlus/src/MangaOwlUs.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaowlus + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaOwlUs : Madara("MangaOwl.us (unoriginal)", "https://mangaowl.us", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangapure/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangapure/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..6bd16fed43 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangapure/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangapure/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d0c5bd9549 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangapure/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangapure/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c70b55da99 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangapure/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangapure/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1c74979fb3 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangapure/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangapure/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d475ff12e4 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangapure/res/web_hi_res_512.png b/multisrc/overrides/madara/mangapure/res/web_hi_res_512.png new file mode 100644 index 0000000000..f8a8d7c118 Binary files /dev/null and b/multisrc/overrides/madara/mangapure/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangapure/src/MangaPure.kt b/multisrc/overrides/madara/mangapure/src/MangaPure.kt new file mode 100644 index 0000000000..a918c44ea3 --- /dev/null +++ b/multisrc/overrides/madara/mangapure/src/MangaPure.kt @@ -0,0 +1,82 @@ +package eu.kanade.tachiyomi.extension.en.mangapure + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaPure : Madara( + "MangaPure", + "https://mangapure.net", + "en", + dateFormat = SimpleDateFormat("MMM dd, HH:mm", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = false + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchMangaNextPageSelector(): String? = ".pagination a[rel=next]" + + override fun searchPage(page: Int): String = "search?page=$page" + + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/popular-manga?page=$page", headers) + + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/latest-manga?page=$page", headers) + + // Copied from IsekaiScan.top (unoriginal) + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + val chaptersWrapper = document.select("div[id^=manga-chapters-holder]") + + var chapterElements = document.select(chapterListSelector()) + + if (chapterElements.isEmpty() && !chaptersWrapper.isNullOrEmpty()) { + val mangaId = chaptersWrapper.attr("data-id") + val xhrHeaders = headersBuilder() + .add("Referer", "$baseUrl/") + .add("X-Requested-With", "XMLHttpRequest") + .build() + val xhrRequest = GET("$baseUrl/ajax-list-chapter?mangaID=$mangaId", xhrHeaders) + val xhrResponse = client.newCall(xhrRequest).execute() + + chapterElements = xhrResponse.asJsoup().select(chapterListSelector()) + xhrResponse.close() + } + + countViews(document) + return chapterElements.map(::chapterFromElement) + } + + // Copied from IsekaiScan.top (unoriginal) + override fun pageListParse(document: Document): List { + val stringArray = document.select("p#arraydata").text().split(",").toTypedArray() + return stringArray.mapIndexed { index, url -> + Page( + index, + document.location(), + url, + ) + } + } + + // Some thumbnails expect harimanga.com, which has hotlink protection + override fun headersBuilder() = super.headersBuilder() + .removeAll("Referer") + + // OnGoing => Ongoing + override fun mangaDetailsParse(document: Document): SManga { + return super.mangaDetailsParse(document).apply { + document.select(mangaDetailsSelectorStatus).lastOrNull()?.text() + .takeIf { it == "Ongoing" } + ?.let { status = SManga.ONGOING } + } + } +} diff --git a/multisrc/overrides/madara/mangaqueencom/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueencom/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..1193cd3748 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueencom/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..1aa7601976 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..416fb40ded Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..10a517b348 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f35d211201 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaqueencom/res/web_hi_res_512.png new file mode 100644 index 0000000000..b888d7534c Binary files /dev/null and b/multisrc/overrides/madara/mangaqueencom/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaqueencom/src/MangaQueenCom.kt b/multisrc/overrides/madara/mangaqueencom/src/MangaQueenCom.kt new file mode 100644 index 0000000000..ac7398fb7a --- /dev/null +++ b/multisrc/overrides/madara/mangaqueencom/src/MangaQueenCom.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaqueencom + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaQueenCom : Madara("Manga Queen.com", "https://mangaqueen.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaqueenonline/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..25a5b1a38b Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..dbe85bc46f Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..164b647f92 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c8530f2148 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..00c6e317e7 Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaqueenonline/res/web_hi_res_512.png new file mode 100644 index 0000000000..ee69bd153a Binary files /dev/null and b/multisrc/overrides/madara/mangaqueenonline/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaqueenonline/src/MangaQueenOnline.kt b/multisrc/overrides/madara/mangaqueenonline/src/MangaQueenOnline.kt new file mode 100644 index 0000000000..4539fcf35a --- /dev/null +++ b/multisrc/overrides/madara/mangaqueenonline/src/MangaQueenOnline.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaqueenonline + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaQueenOnline : Madara("Manga Queen.online (unoriginal)", "https://mangaqueen.online", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangarawinfo/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangarawinfo/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0d137e4505 Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangarawinfo/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8f581c07bb Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b9850c6610 Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0caeae1f5f Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..351aeba936 Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/res/web_hi_res_512.png b/multisrc/overrides/madara/mangarawinfo/res/web_hi_res_512.png new file mode 100644 index 0000000000..a7b94c3771 Binary files /dev/null and b/multisrc/overrides/madara/mangarawinfo/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangarawinfo/src/MangaRawInfo.kt b/multisrc/overrides/madara/mangarawinfo/src/MangaRawInfo.kt new file mode 100644 index 0000000000..aadc1a1bef --- /dev/null +++ b/multisrc/overrides/madara/mangarawinfo/src/MangaRawInfo.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangarawinfo + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaRawInfo : Madara("Manga-Raw.info (unoriginal)", "https://manga-raw.info", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangareadorg/src/MangaReadOrg.kt b/multisrc/overrides/madara/mangareadorg/src/MangaReadOrg.kt index 090a26f3f1..2f9121aa3d 100644 --- a/multisrc/overrides/madara/mangareadorg/src/MangaReadOrg.kt +++ b/multisrc/overrides/madara/mangareadorg/src/MangaReadOrg.kt @@ -1,7 +1,23 @@ package eu.kanade.tachiyomi.extension.en.mangareadorg import eu.kanade.tachiyomi.multisrc.madara.Madara +import org.jsoup.nodes.Element import java.text.SimpleDateFormat import java.util.Locale -class MangaReadOrg : Madara("MangaRead.org", "https://www.mangaread.org", "en", SimpleDateFormat("dd.MM.yyy", Locale.US)) +class MangaReadOrg : Madara( + "MangaRead.org", + "https://www.mangaread.org", + "en", + SimpleDateFormat("dd.MM.yyy", Locale.US), +) { + override fun imageFromElement(element: Element): String? { + return when { + element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc") + element.hasAttr("data-src") -> element.attr("abs:data-src") + element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src") + element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ") + else -> element.attr("abs:src") + } + } +} diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..c4f9323374 Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9368d956eb Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..fc4dee1169 Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2ace1cc1bb Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4826ab2e2c Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/res/web_hi_res_512.png b/multisrc/overrides/madara/mangarockteamunoriginal/res/web_hi_res_512.png new file mode 100644 index 0000000000..86f271f4da Binary files /dev/null and b/multisrc/overrides/madara/mangarockteamunoriginal/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangarockteamunoriginal/src/MangaRockTeamUnoriginal.kt b/multisrc/overrides/madara/mangarockteamunoriginal/src/MangaRockTeamUnoriginal.kt new file mode 100644 index 0000000000..fe7d0fb266 --- /dev/null +++ b/multisrc/overrides/madara/mangarockteamunoriginal/src/MangaRockTeamUnoriginal.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangarockteamunoriginal + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaRockTeamUnoriginal : Madara("Manga Rock.team (unoriginal)", "https://mangarock.team", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangarolls/src/MangaRolls.kt b/multisrc/overrides/madara/mangarolls/src/MangaRolls.kt index 839740cbf7..59dd66ebb9 100644 --- a/multisrc/overrides/madara/mangarolls/src/MangaRolls.kt +++ b/multisrc/overrides/madara/mangarolls/src/MangaRolls.kt @@ -2,6 +2,6 @@ package eu.kanade.tachiyomi.extension.en.mangarolls import eu.kanade.tachiyomi.multisrc.madara.Madara -class MangaRolls : Madara("MangaRolls", "https://mangarolls.com", "en") { +class MangaRolls : Madara("MangaRolls", "https://mangarolls.net", "en") { override val useNewChapterEndpoint = true } diff --git a/multisrc/overrides/madara/mangarose/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangarose/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..445a0c9299 Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarose/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangarose/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..dae395afbf Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarose/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarose/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..75d7f70dc0 Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarose/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarose/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b81f439a7a Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarose/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarose/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4918ad3213 Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarose/res/web_hi_res_512.png b/multisrc/overrides/madara/mangarose/res/web_hi_res_512.png new file mode 100644 index 0000000000..4aba204501 Binary files /dev/null and b/multisrc/overrides/madara/mangarose/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangarose/src/MangaRose.kt b/multisrc/overrides/madara/mangarose/src/MangaRose.kt new file mode 100644 index 0000000000..f6a9884707 --- /dev/null +++ b/multisrc/overrides/madara/mangarose/src/MangaRose.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.ar.mangarose + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaRose : Madara( + "Manga Rose", + "https://mangarose.net", + "ar", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), +) { + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/mangarosie/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangarosie/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..df35ab65d1 Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarosie/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangarosie/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..14aa21896e Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarosie/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarosie/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b343dd43dd Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarosie/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarosie/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bb0770db70 Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarosie/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangarosie/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1703be00ea Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangarosie/res/web_hi_res_512.png b/multisrc/overrides/madara/mangarosie/res/web_hi_res_512.png new file mode 100644 index 0000000000..bad73d3e3c Binary files /dev/null and b/multisrc/overrides/madara/mangarosie/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangarosie/src/MangaRosie.kt b/multisrc/overrides/madara/mangarosie/src/MangaRosie.kt new file mode 100644 index 0000000000..08b563be91 --- /dev/null +++ b/multisrc/overrides/madara/mangarosie/src/MangaRosie.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.mangarosie + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaRosie : Madara("MangaRosie", "https://mangarosie.in", "en") { + override val useNewChapterEndpoint = false + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangarubycom/src/MangaRubyCom.kt b/multisrc/overrides/madara/mangarubycom/src/MangaRubyCom.kt new file mode 100644 index 0000000000..c75ef93363 --- /dev/null +++ b/multisrc/overrides/madara/mangarubycom/src/MangaRubyCom.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.mangarubycom + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaRubyCom : Madara("MangaRuby.com", "https://mangaruby.com", "en") { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaryu/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaryu/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..66b742d5aa Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaryu/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaryu/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..6c0b1d49cc Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaryu/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaryu/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..57c8aa6a1a Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaryu/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaryu/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e84d14bea2 Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaryu/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaryu/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b154a8a74b Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaryu/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaryu/res/web_hi_res_512.png new file mode 100644 index 0000000000..552c952df3 Binary files /dev/null and b/multisrc/overrides/madara/mangaryu/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaryu/src/Mangaryu.kt b/multisrc/overrides/madara/mangaryu/src/Mangaryu.kt new file mode 100644 index 0000000000..616998644c --- /dev/null +++ b/multisrc/overrides/madara/mangaryu/src/Mangaryu.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaryu + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Mangaryu : Madara("Mangaryu", "https://mangaryu.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangasehri/src/MangaSehri.kt b/multisrc/overrides/madara/mangasehri/src/MangaSehri.kt index 94d9507c48..e5d8ffde7f 100644 --- a/multisrc/overrides/madara/mangasehri/src/MangaSehri.kt +++ b/multisrc/overrides/madara/mangasehri/src/MangaSehri.kt @@ -6,9 +6,11 @@ import java.util.Locale class MangaSehri : Madara( "Manga Şehri", - "https://mangasehri.com", + "https://manga-sehri.com", "tr", SimpleDateFormat("dd/MM/yyy", Locale("tr")), ) { - override val useNewChapterEndpoint = true + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" } diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangasnosekai/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/mangasnosekai/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangasnosekai/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/mangasnosekai/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasnosekai/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/mangasnosekai/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasnosekai/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/mangasnosekai/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasnosekai/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/mangasnosekai/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/res/web_hi_res_512.png b/multisrc/overrides/madara/mangasnosekai/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/zeistmanga/muslosnosekai/res/web_hi_res_512.png rename to multisrc/overrides/madara/mangasnosekai/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/mangasnosekai/src/MangasNoSekai.kt b/multisrc/overrides/madara/mangasnosekai/src/MangasNoSekai.kt new file mode 100644 index 0000000000..0a84b89336 --- /dev/null +++ b/multisrc/overrides/madara/mangasnosekai/src/MangasNoSekai.kt @@ -0,0 +1,115 @@ +package eu.kanade.tachiyomi.extension.es.mangasnosekai + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +class MangasNoSekai : Madara( + "Mangas No Sekai", + "https://mangasnosekai.com", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2, 1) + .build() + + override val useNewChapterEndpoint = true + + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/${searchPage(page)}?s=&post_type=wp-manga&m_orderby=views", headers) + + override fun popularMangaSelector() = searchMangaSelector() + + override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element) + + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/${searchPage(page)}?s=&post_type=wp-manga&m_orderby=latest", headers) + + override fun latestUpdatesSelector() = searchMangaSelector() + + override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) + + override fun searchPage(page: Int): String { + return if (page > 1) "page/$page/" else "" + } + + override fun searchMangaNextPageSelector() = "nav.navigation a.next" + + override val mangaDetailsSelectorTitle = "div.summary-content h1.titleManga" + override val mangaDetailsSelectorThumbnail = "div.tab-summary img.img-responsive" + override val mangaDetailsSelectorDescription = "div.summary-content div.artist-content" + override val mangaDetailsSelectorStatus = "div.summary-content ul.general-List li:has(span:contains(Estado))" + override val mangaDetailsSelectorAuthor = "div.summary-content ul.general-List li:has(span:contains(Autor))" + override val mangaDetailsSelectorArtist = "div.summary-content ul.general-List li:has(span:contains(Dibujante))" + override val seriesTypeSelector = "div.summary-content ul.general-List li:has(span:contains(Tipo))" + + override fun mangaDetailsParse(document: Document): SManga { + val manga = SManga.create() + with(document) { + selectFirst(mangaDetailsSelectorTitle)?.let { + manga.title = it.ownText() + } + selectFirst(mangaDetailsSelectorAuthor)?.ownText()?.let { + manga.author = it + } + selectFirst(mangaDetailsSelectorArtist)?.ownText()?.let { + manga.artist = it + } + select(mangaDetailsSelectorDescription).let { + manga.description = it.text() + } + select(mangaDetailsSelectorThumbnail).first()?.let { + manga.thumbnail_url = imageFromElement(it) + } + selectFirst(mangaDetailsSelectorStatus)?.ownText()?.let { + manga.status = when (it) { + in completedStatusList -> SManga.COMPLETED + in ongoingStatusList -> SManga.ONGOING + in hiatusStatusList -> SManga.ON_HIATUS + in canceledStatusList -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + } + val genres = select(mangaDetailsSelectorGenre) + .map { element -> element.text().lowercase(Locale.ROOT) } + .toMutableSet() + + // add manga/manhwa/manhua thinggy to genre + document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { + if (it.isEmpty().not() && it.notUpdating() && it != "-" && genres.contains(it).not()) { + genres.add(it.lowercase(Locale.ROOT)) + } + } + + manga.genre = genres.toList().joinToString(", ") { genre -> + genre.replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase( + Locale.ROOT, + ) + } else { + it.toString() + } + } + } + + // add alternative name to manga description + document.select(altNameSelector).firstOrNull()?.ownText()?.let { + if (it.isBlank().not() && it.notUpdating()) { + manga.description = when { + manga.description.isNullOrBlank() -> altName + it + else -> manga.description + "\n\n$altName" + it + } + } + } + } + + return manga + } +} diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..19e312104e Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ab64511ba5 Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..701f0bf67c Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b0e8a2baf9 Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..03f4db8613 Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/res/web_hi_res_512.png b/multisrc/overrides/madara/mangasoriginesfr/res/web_hi_res_512.png new file mode 100644 index 0000000000..f465790acb Binary files /dev/null and b/multisrc/overrides/madara/mangasoriginesfr/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangasoriginesfr/src/MangasOriginesFr.kt b/multisrc/overrides/madara/mangasoriginesfr/src/MangasOriginesFr.kt new file mode 100644 index 0000000000..2af4ddc5dd --- /dev/null +++ b/multisrc/overrides/madara/mangasoriginesfr/src/MangasOriginesFr.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.fr.mangasoriginesfr + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MangasOriginesFr : Madara("Mangas-Origines.fr", "https://mangas-origines.fr", "fr", SimpleDateFormat("dd/mm/yyyy", Locale("fr"))) { + override val mangaSubString = "catalogues" + + // Manga Details Selectors + override val mangaDetailsSelectorAuthor = "div.manga-authors > a" + override val mangaDetailsSelectorDescription = "div.summary__content > p" +} diff --git a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesx/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 081e9f6bf3..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesx/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2cabf7f70d..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 58b14a5f53..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a1e16eafae..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ba31465175..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/res/web_hi_res_512.png b/multisrc/overrides/madara/mangasoriginesx/res/web_hi_res_512.png deleted file mode 100644 index 87dbfb91ce..0000000000 Binary files a/multisrc/overrides/madara/mangasoriginesx/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangasoriginesx/src/MangasOriginesX.kt b/multisrc/overrides/madara/mangasoriginesx/src/MangasOriginesX.kt deleted file mode 100644 index 56e3bcce24..0000000000 --- a/multisrc/overrides/madara/mangasoriginesx/src/MangasOriginesX.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.mangasoriginesx - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class MangasOriginesX : Madara("Mangas Origines X", "https://x.mangas-origines.fr", "fr", SimpleDateFormat("dd/MM/yyyy", Locale("fr"))) diff --git a/multisrc/overrides/madara/mangaspark/src/MangaSpark.kt b/multisrc/overrides/madara/mangaspark/src/MangaSpark.kt index d82e09e9d8..e35aaa4b00 100644 --- a/multisrc/overrides/madara/mangaspark/src/MangaSpark.kt +++ b/multisrc/overrides/madara/mangaspark/src/MangaSpark.kt @@ -1,26 +1,18 @@ package eu.kanade.tachiyomi.extension.ar.mangaspark import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SManga -import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale -class MangaSpark : Madara("MangaSpark", "https://mangaspark.com", "ar") { - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(popularMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - manga.title = it.ownText() - } - - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it)?.replace("mangaspark", "mangalek") - } - } +class MangaSpark : Madara( + "MangaSpark", + "https://mangaspark.org", + "ar", + dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")), +) { + override val chapterUrlSuffix = "" - return manga - } + override val useNewChapterEndpoint = false - override val chapterUrlSuffix = "" + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" } diff --git a/multisrc/overrides/madara/mangastarz/src/MangaStarz.kt b/multisrc/overrides/madara/mangastarz/src/MangaStarz.kt index 330b4c595e..4001e58885 100644 --- a/multisrc/overrides/madara/mangastarz/src/MangaStarz.kt +++ b/multisrc/overrides/madara/mangastarz/src/MangaStarz.kt @@ -1,31 +1,18 @@ package eu.kanade.tachiyomi.extension.ar.mangastarz import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Headers -import org.jsoup.nodes.Element - -class MangaStarz : Madara("Manga Starz", "https://mangastarz.com", "ar") { - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", baseUrl) - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(popularMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - manga.title = it.ownText() - } - - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it)?.replace("mangastarz", "mangalek") - } - } +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaStarz : Madara( + "Manga Starz", + "https://mangastarz.org", + "ar", + dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")), +) { + override val chapterUrlSuffix = "" - return manga - } + override val useNewChapterEndpoint = false - override val chapterUrlSuffix = "" + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" } diff --git a/multisrc/overrides/madara/mangastk18/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangastk18/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index af02e5668c..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangastk18/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3145bc9a93..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangastk18/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 81f7927557..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangastk18/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7304c324d5..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangastk18/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e818270b6a..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/res/web_hi_res_512.png b/multisrc/overrides/madara/mangastk18/res/web_hi_res_512.png deleted file mode 100644 index 2393ecee06..0000000000 Binary files a/multisrc/overrides/madara/mangastk18/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangastk18/src/MangasTK18.kt b/multisrc/overrides/madara/mangastk18/src/MangasTK18.kt deleted file mode 100644 index 66f0f95437..0000000000 --- a/multisrc/overrides/madara/mangastk18/src/MangasTK18.kt +++ /dev/null @@ -1,54 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangastk18 - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangasTK18 : Madara( - "MangasTK18", - "https://mangastk18.com", - "es", - SimpleDateFormat("dd.MM.yyyy", Locale("es")), -) { - override fun popularMangaSelector() = "div#series-card:has(a:not([href*='bilibilicomics.com']))" - override val popularMangaUrlSelector = "a.series-link" - - override val mangaDetailsSelectorTag = "div.tags-content a.notUsed" // Source use this for the scanlator - override val mangaDetailsSelectorStatus = "div.post-status div.summary-content" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(popularMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - manga.title = it.attr("title") - } - - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it) - } - } - - return manga - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - with(element) { - select(chapterUrlSelector).first()?.let { urlElement -> - chapter.url = urlElement.attr("abs:href").let { - it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else "" - } - chapter.name = urlElement.select("p.chapter-manhwa-title").text() - chapter.date_upload = parseChapterDate(select("span.chapter-release-date").text()) - } - } - - return chapter - } -} diff --git a/multisrc/overrides/madara/mangatopsite/src/MangaTopSite.kt b/multisrc/overrides/madara/mangatopsite/src/MangaTopSite.kt new file mode 100644 index 0000000000..5a82c94afd --- /dev/null +++ b/multisrc/overrides/madara/mangatopsite/src/MangaTopSite.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.all.mangatopsite + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaTopSite : Madara( + "MangaTop.site", + "https://mangatop.site", + "all", + dateFormat = SimpleDateFormat("d MMM yyyy", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = false + override val chapterUrlSuffix = "" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangatxgg/src/MangaTxGg.kt b/multisrc/overrides/madara/mangatxgg/src/MangaTxGg.kt new file mode 100644 index 0000000000..2a3fc88267 --- /dev/null +++ b/multisrc/overrides/madara/mangatxgg/src/MangaTxGg.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangatxgg + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaTxGg : Madara("Manga Tx.gg (unoriginal)", "https://mangatx.gg", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangatyrant/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangatyrant/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..74743325ee Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangatyrant/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangatyrant/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..328d8b00a0 Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangatyrant/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangatyrant/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..a9f6a61a2c Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangatyrant/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangatyrant/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6f5b31b4e3 Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangatyrant/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangatyrant/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..af9506f012 Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangatyrant/res/web_hi_res_512.png b/multisrc/overrides/madara/mangatyrant/res/web_hi_res_512.png new file mode 100644 index 0000000000..be48a766b7 Binary files /dev/null and b/multisrc/overrides/madara/mangatyrant/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangatyrant/src/MangaTyrant.kt b/multisrc/overrides/madara/mangatyrant/src/MangaTyrant.kt new file mode 100644 index 0000000000..88557eba36 --- /dev/null +++ b/multisrc/overrides/madara/mangatyrant/src/MangaTyrant.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.mangatyrant + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaTyrant : Madara("MangaTyrant", "https://mangatyrant.com", "en") { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangaupdatestop/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d8bbacd490 Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ec35ead1d0 Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bd959ac04d Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..58989ca02b Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ae51ec0a2 Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaupdatestop/res/web_hi_res_512.png new file mode 100644 index 0000000000..a624bd17dd Binary files /dev/null and b/multisrc/overrides/madara/mangaupdatestop/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaupdatestop/src/MangaUpdatesTop.kt b/multisrc/overrides/madara/mangaupdatestop/src/MangaUpdatesTop.kt new file mode 100644 index 0000000000..e8319f8289 --- /dev/null +++ b/multisrc/overrides/madara/mangaupdatestop/src/MangaUpdatesTop.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.mangaupdatestop + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class MangaUpdatesTop : Madara("MangaUpdates.top (unoriginal)", "https://mangaupdates.top", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/mangauptocats/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangauptocats/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5fd175692a..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangauptocats/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a7e267d4e1..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangauptocats/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 739cf5a64d..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangauptocats/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0122839a85..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangauptocats/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1a6c8451cf..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/res/web_hi_res_512.png b/multisrc/overrides/madara/mangauptocats/res/web_hi_res_512.png deleted file mode 100644 index c92436b6f1..0000000000 Binary files a/multisrc/overrides/madara/mangauptocats/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mangauptocats/src/Mangauptocats.kt b/multisrc/overrides/madara/mangauptocats/src/Mangauptocats.kt deleted file mode 100644 index 2d52653831..0000000000 --- a/multisrc/overrides/madara/mangauptocats/src/Mangauptocats.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.th.mangauptocats - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangauptocats : Madara( - "Mangauptocats", - "https://manga-uptocats.com", - "th", - SimpleDateFormat("d MMMM yyyy", Locale("th")), -) diff --git a/multisrc/overrides/madara/mangaweebs/src/MangaWeebs.kt b/multisrc/overrides/madara/mangaweebs/src/MangaWeebs.kt index 2ae07b0488..c49ee8620f 100644 --- a/multisrc/overrides/madara/mangaweebs/src/MangaWeebs.kt +++ b/multisrc/overrides/madara/mangaweebs/src/MangaWeebs.kt @@ -1,12 +1,19 @@ package eu.kanade.tachiyomi.extension.en.mangaweebs import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit class MangaWeebs : Madara("Manga Weebs", "https://mangaweebs.in", "en", dateFormat = SimpleDateFormat("dd MMMM HH:mm", Locale.US)) { + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4, TimeUnit.SECONDS) + .build() + override val mangaDetailsSelectorTag = "" - override val pageListParseSelector = "div.page-break > div.loading, li.blocks-gallery-item, .reading-content .text-left:not(:has(.blocks-gallery-item)) :has(>img)" + override val pageListParseSelector = ".reading-content img:not([src*=\"logo.png\"])" } diff --git a/multisrc/overrides/madara/mangaxico/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mangaxico/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e50310ff03 Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaxico/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mangaxico/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..230aca423f Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaxico/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaxico/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..ad00c69c0b Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaxico/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaxico/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1e6bb92108 Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaxico/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mangaxico/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6241658923 Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mangaxico/res/web_hi_res_512.png b/multisrc/overrides/madara/mangaxico/res/web_hi_res_512.png new file mode 100644 index 0000000000..01a54a032a Binary files /dev/null and b/multisrc/overrides/madara/mangaxico/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mangaxico/src/Mangaxico.kt b/multisrc/overrides/madara/mangaxico/src/Mangaxico.kt new file mode 100644 index 0000000000..5b55efb1e7 --- /dev/null +++ b/multisrc/overrides/madara/mangaxico/src/Mangaxico.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.es.mangaxico + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class Mangaxico : Madara( + "Mangaxico", + "https://mangaxico.com", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val useNewChapterEndpoint = true + override val chapterUrlSuffix = "" +} diff --git a/multisrc/overrides/madara/manhastro/src/Manhastro.kt b/multisrc/overrides/madara/manhastro/src/Manhastro.kt new file mode 100644 index 0000000000..95f23a6964 --- /dev/null +++ b/multisrc/overrides/madara/manhastro/src/Manhastro.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.manhastro + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class Manhastro : Madara( + "Manhastro", + "https://manhastro.com", + "pt-BR", + SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/manhuadex/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadex/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..e7fae49725 Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuadex/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadex/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..fbf331407c Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuadex/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadex/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..98fdb1ddd7 Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuadex/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadex/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..21f4f29efd Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuadex/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadex/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..44116a142d Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuadex/res/web_hi_res_512.png b/multisrc/overrides/madara/manhuadex/res/web_hi_res_512.png new file mode 100644 index 0000000000..2bcad01a44 Binary files /dev/null and b/multisrc/overrides/madara/manhuadex/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhuadex/src/ManhuaDex.kt b/multisrc/overrides/madara/manhuadex/src/ManhuaDex.kt new file mode 100644 index 0000000000..38509251f6 --- /dev/null +++ b/multisrc/overrides/madara/manhuadex/src/ManhuaDex.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manhuadex + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhuaDex : Madara("ManhuaDex", "https://manhuadex.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhuadragon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadragon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 27c3b84ffd..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuadragon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadragon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e82ffd2b58..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuadragon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadragon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 394adcee29..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuadragon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadragon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 482e788b94..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuadragon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuadragon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 501b8ce1ac..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuadragon/res/web_hi_res_512.png b/multisrc/overrides/madara/manhuadragon/res/web_hi_res_512.png deleted file mode 100644 index 693b3e14b9..0000000000 Binary files a/multisrc/overrides/madara/manhuadragon/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhuafast/src/ManhuaFast.kt b/multisrc/overrides/madara/manhuafast/src/ManhuaFast.kt index 3550ef28b8..cfac96d24a 100644 --- a/multisrc/overrides/madara/manhuafast/src/ManhuaFast.kt +++ b/multisrc/overrides/madara/manhuafast/src/ManhuaFast.kt @@ -1,9 +1,37 @@ package eu.kanade.tachiyomi.extension.en.manhuafast import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.CacheControl +import okhttp3.OkHttpClient +import okhttp3.Request +import java.util.concurrent.TimeUnit class ManhuaFast : Madara("ManhuaFast", "https://manhuafast.com", "en") { + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4, TimeUnit.SECONDS) + .build() + // The website does not flag the content. override val filterNonMangaItems = false + + override fun popularMangaRequest(page: Int): Request { + return GET( + url = "$baseUrl/${searchPage(page)}?s&post_type=wp-manga&m_orderby=views", + headers = headers, + cache = CacheControl.FORCE_NETWORK, + ) + } + + override fun popularMangaSelector() = searchMangaSelector() + + override fun latestUpdatesRequest(page: Int): Request { + return GET( + url = "$baseUrl/${searchPage(page)}?s&post_type=wp-manga&m_orderby=latest", + headers = headers, + cache = CacheControl.FORCE_NETWORK, + ) + } } diff --git a/multisrc/overrides/madara/manhuafastnet/src/ManhuaFastNet.kt b/multisrc/overrides/madara/manhuafastnet/src/ManhuaFastNet.kt new file mode 100644 index 0000000000..cca61bb0c4 --- /dev/null +++ b/multisrc/overrides/madara/manhuafastnet/src/ManhuaFastNet.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manhuafastnet + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhuaFastNet : Madara("ManhuaFast.net (unoriginal)", "https://manhuafast.net", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhuaga/src/Manhuaga.kt b/multisrc/overrides/madara/manhuaga/src/Manhuaga.kt index f10b60c7eb..9e8ddf99db 100644 --- a/multisrc/overrides/madara/manhuaga/src/Manhuaga.kt +++ b/multisrc/overrides/madara/manhuaga/src/Manhuaga.kt @@ -2,12 +2,9 @@ package eu.kanade.tachiyomi.extension.en.manhuaga import eu.kanade.tachiyomi.multisrc.madara.Madara import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class Manhuaga : Madara("Manhuaga", "https://manhuaga.com", "en") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .addInterceptor { chain -> val originalRequest = chain.request() chain.proceed(originalRequest).let { response -> diff --git a/multisrc/overrides/madara/manhuamanhwa/src/ManhuaManhwa.kt b/multisrc/overrides/madara/manhuamanhwa/src/ManhuaManhwa.kt new file mode 100644 index 0000000000..ee089eb82e --- /dev/null +++ b/multisrc/overrides/madara/manhuamanhwa/src/ManhuaManhwa.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.en.manhuamanhwa + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class ManhuaManhwa : Madara( + "ManhuaManhwa", + "https://manhuamanhwa.com", + "en", + dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ROOT), +) { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhuamanhwaonline/src/ManhuaManhwaOnline.kt b/multisrc/overrides/madara/manhuamanhwaonline/src/ManhuaManhwaOnline.kt new file mode 100644 index 0000000000..81aea3a59d --- /dev/null +++ b/multisrc/overrides/madara/manhuamanhwaonline/src/ManhuaManhwaOnline.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manhuamanhwaonline + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhuaManhwaOnline : Madara("ManhuaManhwa.online", "https://manhuamanhwa.online", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhuascaninfo/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..efeb9ba0bb Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ceae6c6035 Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..ebbe319321 Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1d5c406d0e Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..77b6bef37f Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/res/web_hi_res_512.png b/multisrc/overrides/madara/manhuascaninfo/res/web_hi_res_512.png new file mode 100644 index 0000000000..79d6a8907d Binary files /dev/null and b/multisrc/overrides/madara/manhuascaninfo/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhuascaninfo/src/ManhuaScanInfo.kt b/multisrc/overrides/madara/manhuascaninfo/src/ManhuaScanInfo.kt new file mode 100644 index 0000000000..7a88dd1db6 --- /dev/null +++ b/multisrc/overrides/madara/manhuascaninfo/src/ManhuaScanInfo.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manhuascaninfo + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhuaScanInfo : Madara("ManhuaScan.info (unoriginal)", "https://manhuascan.info", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhuasnet/src/Manhuasnet.kt b/multisrc/overrides/madara/manhuasnet/src/Manhuasnet.kt new file mode 100644 index 0000000000..c29ceaa3e9 --- /dev/null +++ b/multisrc/overrides/madara/manhuasnet/src/Manhuasnet.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.en.manhuasnet + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Manhuasnet : Madara("Manhua Mix", "https://manhuamix.com", "en") { + override val id = 4574243309965401439 +} diff --git a/multisrc/overrides/madara/manhuazone/src/ManhuaZone.kt b/multisrc/overrides/madara/manhuazone/src/ManhuaZone.kt index 40ecca00de..9200087269 100644 --- a/multisrc/overrides/madara/manhuazone/src/ManhuaZone.kt +++ b/multisrc/overrides/madara/manhuazone/src/ManhuaZone.kt @@ -6,7 +6,7 @@ import java.util.Locale class ManhuaZone : Madara( "ManhuaZone", - "https://manhuazone.com", + "https://manhuazone.org", "en", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US), ) diff --git a/multisrc/overrides/madara/manhuazonghe/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhuazonghe/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..850514942e Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhuazonghe/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d9a552289e Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8d4402cd23 Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9fa5ca3503 Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..94188edc3f Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/res/web_hi_res_512.png b/multisrc/overrides/madara/manhuazonghe/res/web_hi_res_512.png new file mode 100644 index 0000000000..c01736a24a Binary files /dev/null and b/multisrc/overrides/madara/manhuazonghe/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhuazonghe/src/ManhuaZonghe.kt b/multisrc/overrides/madara/manhuazonghe/src/ManhuaZonghe.kt new file mode 100644 index 0000000000..212ec0eb0e --- /dev/null +++ b/multisrc/overrides/madara/manhuazonghe/src/ManhuaZonghe.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.en.manhuazonghe + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhuaZonghe : Madara("Manhua Zonghe", "https://manhuazonghe.com", "en") { + override val useNewChapterEndpoint = false + override val filterNonMangaItems = false + override val mangaSubString = "manhua" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhwa2read/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhwa2read/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..98e209fcec Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwa2read/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhwa2read/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b99e0beb64 Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwa2read/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwa2read/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..afa675f202 Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwa2read/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwa2read/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..01ed05c3b3 Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwa2read/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwa2read/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..71ef3de1cc Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwa2read/res/web_hi_res_512.png b/multisrc/overrides/madara/manhwa2read/res/web_hi_res_512.png new file mode 100644 index 0000000000..cfc34d4db7 Binary files /dev/null and b/multisrc/overrides/madara/manhwa2read/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhwa2read/src/Manhwa2Read.kt b/multisrc/overrides/madara/manhwa2read/src/Manhwa2Read.kt new file mode 100644 index 0000000000..fbd6a15717 --- /dev/null +++ b/multisrc/overrides/madara/manhwa2read/src/Manhwa2Read.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.manhwa2read + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class Manhwa2Read : Madara("Manhwa2Read", "https://manhwa2read.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhwadashraw/src/ManhwaDashRaw.kt b/multisrc/overrides/madara/manhwadashraw/src/ManhwaDashRaw.kt index 125fc5276e..934c25d3fa 100644 --- a/multisrc/overrides/madara/manhwadashraw/src/ManhwaDashRaw.kt +++ b/multisrc/overrides/madara/manhwadashraw/src/ManhwaDashRaw.kt @@ -11,4 +11,8 @@ class ManhwaDashRaw : Madara( dateFormat = SimpleDateFormat("dd/MM/yyy", Locale.ROOT), ) { override val useNewChapterEndpoint = true + + override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Status) > div.summary-content" + override val mangaDetailsSelectorDescription = "div.post-content_item:contains(Summary) div.summary-container" + override val pageListParseSelector = "div.page-break img.wp-manga-chapter-img" } diff --git a/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt b/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt index 9222c3e481..89924f5726 100644 --- a/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt +++ b/multisrc/overrides/madara/manhwalatino/src/ManhwaLatino.kt @@ -1,6 +1,11 @@ package eu.kanade.tachiyomi.extension.es.manhwalatino import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.ResponseBody.Companion.toResponseBody +import org.jsoup.nodes.Element import java.text.SimpleDateFormat import java.util.Locale @@ -11,11 +16,49 @@ class ManhwaLatino : Madara( SimpleDateFormat("dd/MM/yyyy", Locale("es")), ) { - override val supportsLatest = false + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor { chain -> + val request = chain.request() + val headers = request.headers.newBuilder() + .removeAll("Accept-Encoding") + .build() + val response = chain.proceed(request.newBuilder().headers(headers).build()) + if (response.headers("Content-Type").contains("application/octet-stream") && response.request.url.toString().endsWith(".jpg")) { + val orgBody = response.body.bytes() + val newBody = orgBody.toResponseBody("image/jpeg".toMediaTypeOrNull()) + response.newBuilder() + .body(newBody) + .build() + } else { + response + } + } + .build() override val useNewChapterEndpoint = true - override val chapterUrlSelector = "a:eq(1)" + override val chapterUrlSelector = "div.mini-letters > a" override val mangaDetailsSelectorStatus = "div.post-content_item:contains(Estado del comic) > div.summary-content" + override val mangaDetailsSelectorDescription = "div.post-content_item:contains(Resumen) div.summary-container" + override val pageListParseSelector = "div.page-break img.wp-manga-chapter-img" + + override fun chapterFromElement(element: Element): SChapter { + val chapter = SChapter.create() + + with(element) { + select(chapterUrlSelector).first()?.let { urlElement -> + chapter.url = urlElement.attr("abs:href").let { + it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else "" + } + chapter.name = urlElement.wholeText().substringAfter("\n") + } + + chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) } + ?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) } + ?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text()) + } + + return chapter + } } diff --git a/multisrc/overrides/madara/manhwamanhua/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhwamanhua/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b3f3b1a35c Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhwamanhua/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ab7cb5721d Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d88ac01357 Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c37ace6409 Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dfef5100c7 Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/res/web_hi_res_512.png b/multisrc/overrides/madara/manhwamanhua/res/web_hi_res_512.png new file mode 100644 index 0000000000..0b35f11598 Binary files /dev/null and b/multisrc/overrides/madara/manhwamanhua/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhwamanhua/src/ManhwaManhua.kt b/multisrc/overrides/madara/manhwamanhua/src/ManhwaManhua.kt new file mode 100644 index 0000000000..db15b5db2b --- /dev/null +++ b/multisrc/overrides/madara/manhwamanhua/src/ManhwaManhua.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.manhwamanhua + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhwaManhua : Madara("ManhwaManhua", "https://manhwamanhua.com", "en") { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhwanew/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhwanew/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..63c13ede7d Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwanew/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhwanew/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c75ee6c3ff Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwanew/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwanew/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..eab92c6bd3 Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwanew/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwanew/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8af570d5cb Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwanew/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwanew/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cc67d8eb86 Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/manhwanew/res/web_hi_res_512.png b/multisrc/overrides/madara/manhwanew/res/web_hi_res_512.png new file mode 100644 index 0000000000..7cf4f91729 Binary files /dev/null and b/multisrc/overrides/madara/manhwanew/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/manhwanew/src/ManhwaNew.kt b/multisrc/overrides/madara/manhwanew/src/ManhwaNew.kt new file mode 100644 index 0000000000..17be4d42d6 --- /dev/null +++ b/multisrc/overrides/madara/manhwanew/src/ManhwaNew.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.en.manhwanew + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class ManhwaNew : Madara( + "ManhwaNew", + "https://manhwanew.com", + "en", + dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ROOT), +) { + override val useNewChapterEndpoint = true + override val filterNonMangaItems = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/manhwatime/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/manhwatime/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f2d1811d7a..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwatime/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/manhwatime/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d3f374d6fe..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwatime/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwatime/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2245b31e53..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwatime/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwatime/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 51e88c01c7..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 771b5c7ddd..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwatime/res/web_hi_res_512.png b/multisrc/overrides/madara/manhwatime/res/web_hi_res_512.png deleted file mode 100644 index f3d7131af3..0000000000 Binary files a/multisrc/overrides/madara/manhwatime/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/manhwaworld/src/ManhwaWorld.kt b/multisrc/overrides/madara/manhwaworld/src/ManhwaWorld.kt new file mode 100644 index 0000000000..5432baef6f --- /dev/null +++ b/multisrc/overrides/madara/manhwaworld/src/ManhwaWorld.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.en.manhwaworld + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManhwaWorld : Madara("AQUA Scans", "https://aquascans.com", "en") { + override val id = 8857833474626810640 +} diff --git a/multisrc/overrides/madara/mantrazscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mantrazscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d2aa736cba Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mantrazscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mantrazscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..158cc31306 Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mantrazscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mantrazscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..67dc81edd5 Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mantrazscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mantrazscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0e76feae7c Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mantrazscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mantrazscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a91ea2d3a6 Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/mantrazscan/res/web_hi_res_512.png b/multisrc/overrides/madara/mantrazscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..0c24dc41c5 Binary files /dev/null and b/multisrc/overrides/madara/mantrazscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/mantrazscan/src/MantrazScan.kt b/multisrc/overrides/madara/mantrazscan/src/MantrazScan.kt new file mode 100644 index 0000000000..6b1ed6b149 --- /dev/null +++ b/multisrc/overrides/madara/mantrazscan/src/MantrazScan.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.es.mantrazscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class MantrazScan : Madara( + "Mantraz Scan", + "https://mantrazscan.com", + "es", + SimpleDateFormat("dd/MM/yyyy", Locale("es")), +) { + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/manytoonclub/src/ManyToonClub.kt b/multisrc/overrides/madara/manytoonclub/src/ManyToonClub.kt new file mode 100644 index 0000000000..db227c5328 --- /dev/null +++ b/multisrc/overrides/madara/manytoonclub/src/ManyToonClub.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.ko.manytoonclub + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ManyToonClub : Madara("ManyToonClub", "https://manytoon.club", "ko") { + + override val mangaSubString = "manhwa-raw" + + // The website does not flag the content. + override val filterNonMangaItems = false +} diff --git a/multisrc/overrides/madara/manytoonme/src/ManyToonMe.kt b/multisrc/overrides/madara/manytoonme/src/ManyToonMe.kt index fb4c05fbe5..b4446e55eb 100644 --- a/multisrc/overrides/madara/manytoonme/src/ManyToonMe.kt +++ b/multisrc/overrides/madara/manytoonme/src/ManyToonMe.kt @@ -4,8 +4,7 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara class ManyToonMe : Madara("ManyToon.me", "https://manytoon.me", "en") { - override val useNewChapterEndpoint: Boolean = true + override val mangaSubString = "comic" - // The website does not flag the content. - override val filterNonMangaItems = false + override val useNewChapterEndpoint: Boolean = true } diff --git a/multisrc/overrides/madara/mgkomik/src/MGKomik.kt b/multisrc/overrides/madara/mgkomik/src/MGKomik.kt index d0d08cd82c..30febe8ad5 100644 --- a/multisrc/overrides/madara/mgkomik/src/MGKomik.kt +++ b/multisrc/overrides/madara/mgkomik/src/MGKomik.kt @@ -1,13 +1,46 @@ package eu.kanade.tachiyomi.extension.id.mgkomik import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.Headers +import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit +import kotlin.random.Random -class MGKomik : Madara("MG Komik", "https://mgkomik.com", "id", SimpleDateFormat("dd MMM yy", Locale.US)) { +class MGKomik : Madara("MG Komik", "https://mgkomik.id", "id", SimpleDateFormat("dd MMM yy", Locale.US)) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5, TimeUnit.SECONDS) + .build() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + .add("Accept-Language", "en-US,en;q=0.9,id;q=0.8") + .add("Sec-Fetch-Dest", "document") + .add("Sec-Fetch-Mode", "navigate") + .add("Sec-Fetch-Site", "same-origin") + .add("Sec-Fetch-User", "?1") + .add("Upgrade-Insecure-Requests", "1") + .add("X-Requested-With", randomString) + + private fun generateRandomString(length: Int): String { + val charset = "HALOGaES.BCDFHIJKMNPQRTUVWXYZ.bcdefghijklmnopqrstuvwxyz0123456789" + return (1..length) + .map { charset.random() } + .joinToString("") + } + + override fun searchPage(page: Int): String = if (page > 1) "page/$page/" else "" + + private val randomLength = Random.Default.nextInt(13, 21) + + private val randomString = generateRandomString(randomLength) - override val chapterUrlSuffix = "" override val mangaSubString = "komik" override fun searchMangaNextPageSelector() = "a.page.larger" + + override val chapterUrlSuffix = "" } diff --git a/multisrc/overrides/madara/mhentais/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mhentais/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c0e871a856..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mhentais/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2ba963c405..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mhentais/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e703dac92c..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mhentais/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e444d3c6c9..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mhentais/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c5b58f2be4..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/res/web_hi_res_512.png b/multisrc/overrides/madara/mhentais/res/web_hi_res_512.png deleted file mode 100644 index cbd7a4e2e8..0000000000 Binary files a/multisrc/overrides/madara/mhentais/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mhentais/src/MHentais.kt b/multisrc/overrides/madara/mhentais/src/MHentais.kt deleted file mode 100644 index 11f59b8db2..0000000000 --- a/multisrc/overrides/madara/mhentais/src/MHentais.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mhentais - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MHentais : Madara( - "MHentais", - "https://mhentais.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/miradscanlator/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/miradscanlator/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bd2eac9693..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/miradscanlator/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fba0c24d7a..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/miradscanlator/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e3566e414c..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/miradscanlator/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 002d184571..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/miradscanlator/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index add4646828..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/res/web_hi_res_512.png b/multisrc/overrides/madara/miradscanlator/res/web_hi_res_512.png deleted file mode 100644 index d21da04a1e..0000000000 Binary files a/multisrc/overrides/madara/miradscanlator/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/miradscanlator/src/MiradScanlator.kt b/multisrc/overrides/madara/miradscanlator/src/MiradScanlator.kt deleted file mode 100644 index 747eb3d13a..0000000000 --- a/multisrc/overrides/madara/miradscanlator/src/MiradScanlator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.miradscanlator - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MiradScanlator : Madara( - "Mirad Scanlator", - "https://miradscanlator.site", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt-BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/mixedmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/mixedmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a1e0c966fa..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/mixedmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c521ce4271..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/mixedmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7eae140636..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/mixedmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index cb2a877820..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/mixedmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2ff3eddd82..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/mixedmanga/res/web_hi_res_512.png deleted file mode 100644 index ed3fce2a14..0000000000 Binary files a/multisrc/overrides/madara/mixedmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/mixedmanga/src/MixedManga.kt b/multisrc/overrides/madara/mixedmanga/src/MixedManga.kt deleted file mode 100644 index 13c9f89349..0000000000 --- a/multisrc/overrides/madara/mixedmanga/src/MixedManga.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mixedmanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.Headers -import java.text.SimpleDateFormat -import java.util.Locale - -class MixedManga : Madara("Mixed Manga", "https://mixedmanga.com", "en", SimpleDateFormat("d MMM yyyy", Locale.US)) { - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) -} diff --git a/multisrc/overrides/madara/mmscans/src/MMScans.kt b/multisrc/overrides/madara/mmscans/src/MMScans.kt index db859e4ba4..03252b7a70 100644 --- a/multisrc/overrides/madara/mmscans/src/MMScans.kt +++ b/multisrc/overrides/madara/mmscans/src/MMScans.kt @@ -1,8 +1,11 @@ package eu.kanade.tachiyomi.extension.en.mmscans import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.FormBody +import okhttp3.Request import org.jsoup.nodes.Element class MMScans : Madara("MMScans", "https://mm-scans.org", "en") { @@ -12,8 +15,47 @@ class MMScans : Madara("MMScans", "https://mm-scans.org", "en") { override val popularMangaUrlSelector = "div.item-summary a" override fun chapterListSelector() = "li.chapter-li" - override fun searchMangaSelector() = "a" + override fun searchMangaSelector() = ".search-wrap >.tab-content-wrap > a" + override fun searchMangaNextPageSelector(): String? = "body:not(:has(.no-posts))" + fun oldLoadMoreRequest(page: Int, metaKey: String): Request { + val form = FormBody.Builder() + .add("action", "madara_load_more") + .add("page", page.toString()) + .add("template", "madara-core/content/content-archive") + .add("vars[paged]", "1") + .add("vars[orderby]", "meta_value_num") + .add("vars[template]", "archive") + .add("vars[sidebar]", "right") + .add("vars[post_type]", "wp-manga") + .add("vars[post_status]", "publish") + .add("vars[meta_key]", metaKey) + .add("vars[meta_query][0][paged]", "1") + .add("vars[meta_query][0][orderby]", "meta_value_num") + .add("vars[meta_query][0][template]", "archive") + .add("vars[meta_query][0][sidebar]", "right") + .add("vars[meta_query][0][post_type]", "wp-manga") + .add("vars[meta_query][0][post_status]", "publish") + .add("vars[meta_query][0][meta_key]", metaKey) + .add("vars[meta_query][relation]", "AND") + .add("vars[manga_archives_item_layout]", "default") + .build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", form.contentLength().toString()) + .add("Content-Type", form.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form) + } + + override fun popularMangaRequest(page: Int): Request { + return oldLoadMoreRequest(page - 1, "_wp_manga_views") + } + override fun latestUpdatesRequest(page: Int): Request { + return oldLoadMoreRequest(page - 1, "_latest_update") + } override fun popularMangaFromElement(element: Element): SManga { val manga = SManga.create() diff --git a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/myuniversescanlator/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dbc382eae8..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/myuniversescanlator/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a3eade6e72..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index acd05f899a..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f22255ba8b..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b473a5828d..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/res/web_hi_res_512.png b/multisrc/overrides/madara/myuniversescanlator/res/web_hi_res_512.png deleted file mode 100644 index f4199f3c76..0000000000 Binary files a/multisrc/overrides/madara/myuniversescanlator/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/myuniversescanlator/src/MyUniverseScanlator.kt b/multisrc/overrides/madara/myuniversescanlator/src/MyUniverseScanlator.kt deleted file mode 100644 index 3ddee38a81..0000000000 --- a/multisrc/overrides/madara/myuniversescanlator/src/MyUniverseScanlator.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.myuniversescanlator - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MyUniverseScanlator : Madara( - "My Universe Scanlator", - "https://muscan.com.br", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/nekobreakerscan/src/NekoBreakerScan.kt b/multisrc/overrides/madara/nekobreakerscan/src/NekoBreakerScan.kt deleted file mode 100644 index 258cfc600a..0000000000 --- a/multisrc/overrides/madara/nekobreakerscan/src/NekoBreakerScan.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.nekobreakerscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale - -class NekoBreakerScan : Madara( - "NekoBreaker Scan", - "https://nekobreakerscan.com", - "pt-BR", - SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .build() -} diff --git a/multisrc/overrides/madara/nekopostco/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/nekopostco/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9a3861d3e0 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/nekopostco/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/nekopostco/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d6f802b833 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/nekopostco/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/nekopostco/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4a678c8e43 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/nekopostco/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/nekopostco/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a2ef8a6450 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/nekopostco/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/nekopostco/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..90feb1ac29 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/nekopostco/res/web_hi_res_512.png b/multisrc/overrides/madara/nekopostco/res/web_hi_res_512.png new file mode 100644 index 0000000000..8f8ae9aa78 Binary files /dev/null and b/multisrc/overrides/madara/nekopostco/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/nekopostco/src/NekoPostCo.kt b/multisrc/overrides/madara/nekopostco/src/NekoPostCo.kt new file mode 100644 index 0000000000..e6f41d5541 --- /dev/null +++ b/multisrc/overrides/madara/nekopostco/src/NekoPostCo.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.th.nekopostco + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class NekoPostCo : Madara( + "NekoPost.co (unoriginal)", + "https://www.nekopost.co", + "th", + dateFormat = SimpleDateFormat("d MMMM yyyy", Locale("th")), +) { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/ninjascan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/ninjascan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fe4fc4642f..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/ninjascan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5ff9fabb2e..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/ninjascan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d8840bfc65..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/ninjascan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1a5cf14e47..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/ninjascan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6dc40fdd12..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/res/web_hi_res_512.png b/multisrc/overrides/madara/ninjascan/res/web_hi_res_512.png deleted file mode 100644 index f7a2288a7c..0000000000 Binary files a/multisrc/overrides/madara/ninjascan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/ninjascan/src/NinjaScan.kt b/multisrc/overrides/madara/ninjascan/src/NinjaScan.kt deleted file mode 100644 index a74850c7e3..0000000000 --- a/multisrc/overrides/madara/ninjascan/src/NinjaScan.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.ninjascan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class NinjaScan : Madara( - "Ninja Scan", - "https://ninjascan.xyz", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/nitroscans/src/NitroScans.kt b/multisrc/overrides/madara/nitroscans/src/NitroScans.kt new file mode 100644 index 0000000000..a04aff3ed3 --- /dev/null +++ b/multisrc/overrides/madara/nitroscans/src/NitroScans.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.en.nitroscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class NitroScans : Madara("Nitro Manga", "https://nitromanga.com", "en") { + override val id = 1310352166897986481 + + override val mangaSubString = "mangas" + + override val filterNonMangaItems = false + + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/noblessetranslations/src/NoblesseTranslations.kt b/multisrc/overrides/madara/noblessetranslations/src/NoblesseTranslations.kt index e23f5005cb..da3b11061f 100644 --- a/multisrc/overrides/madara/noblessetranslations/src/NoblesseTranslations.kt +++ b/multisrc/overrides/madara/noblessetranslations/src/NoblesseTranslations.kt @@ -1,14 +1,57 @@ package eu.kanade.tachiyomi.extension.es.noblessetranslations import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import okhttp3.FormBody +import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale class NoblesseTranslations : Madara( "Noblesse Translations", - "https://www.noblessetranslations.com", + "https://noblessetranslations.com", "es", - dateFormat = SimpleDateFormat("dd/MM/yy", Locale.ROOT), + dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("es")), ) { override val useNewChapterEndpoint = true + override val mangaSubString = "proyecto" + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + + override val mangaDetailsSelectorStatus = "div.summary_content > div.post-content div.post-content_item:has(div.summary-heading:contains(Status)) div.summary-content" + override val mangaDetailsSelectorTag = "div.tags-content a.notUsed" // Site uses this for the scanlator + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "full") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[order]", "desc") + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "big_thumbnail") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } } diff --git a/multisrc/overrides/madara/onlymanhwa/src/OnlyManhwa.kt b/multisrc/overrides/madara/onlymanhwa/src/OnlyManhwa.kt new file mode 100644 index 0000000000..5013f8987f --- /dev/null +++ b/multisrc/overrides/madara/onlymanhwa/src/OnlyManhwa.kt @@ -0,0 +1,18 @@ +package eu.kanade.tachiyomi.extension.en.onlymanhwa + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class OnlyManhwa : Madara( + "OnlyManhwa", + "https://onlymanhwa.org", + "en", + dateFormat = SimpleDateFormat("d 'de' MMMM 'de' yyyy", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = true + override val mangaSubString = "manhwa" + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/painfulnightzscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b7265edbed..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/painfulnightzscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3b2b356e76..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 619a03792d..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ad77c4f45a..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 101ac61d6c..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/painfulnightzscan/res/web_hi_res_512.png b/multisrc/overrides/madara/painfulnightzscan/res/web_hi_res_512.png deleted file mode 100644 index 5c7b5998fb..0000000000 Binary files a/multisrc/overrides/madara/painfulnightzscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/paragonscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/paragonscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0cc5a3e4fb Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/paragonscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/paragonscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f5efd36d30 Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/paragonscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/paragonscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2c91a0d84e Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/paragonscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/paragonscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b0ae9e84be Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/paragonscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/paragonscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..30241a3c72 Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/paragonscans/res/web_hi_res_512.png b/multisrc/overrides/madara/paragonscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..916b36beee Binary files /dev/null and b/multisrc/overrides/madara/paragonscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/paragonscans/src/ParagonScans.kt b/multisrc/overrides/madara/paragonscans/src/ParagonScans.kt new file mode 100644 index 0000000000..ef4d7ba70d --- /dev/null +++ b/multisrc/overrides/madara/paragonscans/src/ParagonScans.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.extension.en.paragonscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class ParagonScans : Madara( + "Paragon Scans", + "https://paragonscans.com", + "en", + dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.ROOT), +) { + override val useNewChapterEndpoint = true + override val mangaSubString = "mangax" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" + + override fun parseChapterDate(date: String?): Long { + date ?: return 0 + + val splitDate = date.split(' ') + if (splitDate.size < 2) { + return super.parseChapterDate(date) + } + + val (amountStr, unit) = splitDate + val amount = amountStr.toIntOrNull() + ?: return super.parseChapterDate(date) + + val cal = Calendar.getInstance() + return when (unit) { + "s" -> cal.apply { add(Calendar.SECOND, -amount) }.timeInMillis // not observed + "m" -> cal.apply { add(Calendar.MINUTE, -amount) }.timeInMillis // not observed + "h" -> cal.apply { add(Calendar.HOUR_OF_DAY, -amount) }.timeInMillis + "d" -> cal.apply { add(Calendar.DAY_OF_MONTH, -amount) }.timeInMillis + else -> super.parseChapterDate(date) + } + } +} diff --git a/multisrc/overrides/madara/passamaoscan/src/PassaMaoScan.kt b/multisrc/overrides/madara/passamaoscan/src/PassaMaoScan.kt new file mode 100644 index 0000000000..7eb437fb9d --- /dev/null +++ b/multisrc/overrides/madara/passamaoscan/src/PassaMaoScan.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.passamaoscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class PassaMaoScan : Madara( + "Passa Mão Scan", + "https://passamaoscan.com", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/pawmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/pawmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0e5b55b8b7 Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pawmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/pawmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8d78e85b8a Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pawmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/pawmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8442eba822 Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pawmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/pawmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4b5b3cb52c Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pawmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/pawmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fa47aeac2a Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pawmanga/res/web_hi_res_512.png b/multisrc/overrides/madara/pawmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..72158ffb51 Binary files /dev/null and b/multisrc/overrides/madara/pawmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/pawmanga/src/PawManga.kt b/multisrc/overrides/madara/pawmanga/src/PawManga.kt new file mode 100644 index 0000000000..f82a6f9714 --- /dev/null +++ b/multisrc/overrides/madara/pawmanga/src/PawManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.pawmanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class PawManga : Madara("Paw Manga", "https://pawmanga.com", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/peachscan/src/PeachScan.kt b/multisrc/overrides/madara/peachscan/src/PeachScan.kt deleted file mode 100644 index 85051217f8..0000000000 --- a/multisrc/overrides/madara/peachscan/src/PeachScan.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.peachscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class PeachScan : Madara( - "Peach Scan", - "https://www.peachscan.com", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/phoenixfansub/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/phoenixfansub/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b6eec99d99..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/phoenixfansub/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d172832113..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/phoenixfansub/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2218cb154b..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 769a3a6cf3..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1498827298..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/res/web_hi_res_512.png b/multisrc/overrides/madara/phoenixfansub/res/web_hi_res_512.png deleted file mode 100644 index 5ed840fb00..0000000000 Binary files a/multisrc/overrides/madara/phoenixfansub/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/phoenixfansub/src/PhoenixFansub.kt b/multisrc/overrides/madara/phoenixfansub/src/PhoenixFansub.kt deleted file mode 100644 index 0437b0b69a..0000000000 --- a/multisrc/overrides/madara/phoenixfansub/src/PhoenixFansub.kt +++ /dev/null @@ -1,16 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.phoenixfansub - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class PhoenixFansub : Madara( - "Phoenix Fansub", - "https://phoenixmangas.com", - "es", - dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), -) { - // Site moved from MangaThemesia to Madara - override val versionId = 2 - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/pikiranwibu/PikiranWibu.kt b/multisrc/overrides/madara/pikiranwibu/PikiranWibu.kt new file mode 100644 index 0000000000..11119216cf --- /dev/null +++ b/multisrc/overrides/madara/pikiranwibu/PikiranWibu.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.id.pikiranwibu + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class PikiranWibu : Madara( + "Pikiran Wibu", + "https://pikiran-wibu.com", + "id", + SimpleDateFormat("dd MMM yy", Locale("en")), +) { + + // popular is the latest + override val supportsLatest = false + + override val filterNonMangaItems = false + + override val mangaSubString = "" +} diff --git a/multisrc/overrides/madara/pikiranwibu/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/pikiranwibu/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..3d9886c003 Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pikiranwibu/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/pikiranwibu/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3ad7fcdf22 Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pikiranwibu/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6bef845770 Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1ad84e514a Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a5dc99159d Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/pikiranwibu/res/web_hi_res_512.png b/multisrc/overrides/madara/pikiranwibu/res/web_hi_res_512.png new file mode 100644 index 0000000000..b8e2b20a9f Binary files /dev/null and b/multisrc/overrides/madara/pikiranwibu/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/pmscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/pmscans/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/madara/pmscans/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/pmscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/pmscans/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/madara/pmscans/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/pmscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/pmscans/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/madara/pmscans/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/pmscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/pmscans/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/madara/pmscans/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/pmscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/pmscans/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/madara/pmscans/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/pmscans/res/web_hi_res_512.png b/multisrc/overrides/madara/pmscans/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mangathemesia/pmscans/res/web_hi_res_512.png rename to multisrc/overrides/madara/pmscans/res/web_hi_res_512.png diff --git a/multisrc/overrides/madara/pmscans/src/PMScans.kt b/multisrc/overrides/madara/pmscans/src/PMScans.kt new file mode 100644 index 0000000000..98969144e5 --- /dev/null +++ b/multisrc/overrides/madara/pmscans/src/PMScans.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.en.pmscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class PMScans : Madara( + "PMScans", + "https://rackusreads.com", + "en", + dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ROOT), +) { + override val versionId = 2 + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/pojokmanga/src/PojokManga.kt b/multisrc/overrides/madara/pojokmanga/src/PojokManga.kt index 87d440a0c6..6cde6c452d 100644 --- a/multisrc/overrides/madara/pojokmanga/src/PojokManga.kt +++ b/multisrc/overrides/madara/pojokmanga/src/PojokManga.kt @@ -2,17 +2,26 @@ package eu.kanade.tachiyomi.extension.id.pojokmanga import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit class PojokManga : Madara("Pojok Manga", "https://pojokmanga.net", "id", SimpleDateFormat("MMM dd, yyyy", Locale.US)) { + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4, TimeUnit.SECONDS) + .build() + override val useNewChapterEndpoint = true + override val mangaSubString = "komik" + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { var url = "$baseUrl/${searchPage(page)}".toHttpUrlOrNull()!!.newBuilder() url.addQueryParameter("s", query) @@ -72,6 +81,8 @@ class PojokManga : Madara("Pojok Manga", "https://pojokmanga.net", "id", SimpleD override fun searchMangaSelector() = "div.c-tabs-item__content, div.page-item-detail" + override val mangaDetailsSelectorTag = "#toNotBeUsed" + protected class ProjectFilter : UriPartFilter( "Filter Project", arrayOf( diff --git a/multisrc/overrides/madara/ponymanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/ponymanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..5e9a385346 Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/ponymanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/ponymanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f594216ced Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/ponymanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/ponymanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..204a043d73 Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/ponymanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/ponymanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9b4a5c7062 Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/ponymanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/ponymanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8fa44a9ded Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/ponymanga/res/web_hi_res_512.png b/multisrc/overrides/madara/ponymanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..b86a13bad9 Binary files /dev/null and b/multisrc/overrides/madara/ponymanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/ponymanga/src/PonyManga.kt b/multisrc/overrides/madara/ponymanga/src/PonyManga.kt new file mode 100644 index 0000000000..559b8df951 --- /dev/null +++ b/multisrc/overrides/madara/ponymanga/src/PonyManga.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.ponymanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class PonyManga : Madara("Pony Manga", "https://ponymanga.com", "en") { + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/prismascans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/prismascans/res/mipmap-hdpi/ic_launcher.png index 3b60a9651d..f2ce0ea2a7 100644 Binary files a/multisrc/overrides/madara/prismascans/res/mipmap-hdpi/ic_launcher.png and b/multisrc/overrides/madara/prismascans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/prismascans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/prismascans/res/mipmap-mdpi/ic_launcher.png index 8dbc777650..a095dfa386 100644 Binary files a/multisrc/overrides/madara/prismascans/res/mipmap-mdpi/ic_launcher.png and b/multisrc/overrides/madara/prismascans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/prismascans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/prismascans/res/mipmap-xhdpi/ic_launcher.png index 9701fd78ea..1673491c7a 100644 Binary files a/multisrc/overrides/madara/prismascans/res/mipmap-xhdpi/ic_launcher.png and b/multisrc/overrides/madara/prismascans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/prismascans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/prismascans/res/mipmap-xxhdpi/ic_launcher.png index f8efd0676a..dd419c46ce 100644 Binary files a/multisrc/overrides/madara/prismascans/res/mipmap-xxhdpi/ic_launcher.png and b/multisrc/overrides/madara/prismascans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/prismascans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/prismascans/res/mipmap-xxxhdpi/ic_launcher.png index f4a6538b57..f77543914b 100644 Binary files a/multisrc/overrides/madara/prismascans/res/mipmap-xxxhdpi/ic_launcher.png and b/multisrc/overrides/madara/prismascans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/prismascans/res/web_hi_res_512.png b/multisrc/overrides/madara/prismascans/res/web_hi_res_512.png index c7fcf3b161..f2042137e5 100644 Binary files a/multisrc/overrides/madara/prismascans/res/web_hi_res_512.png and b/multisrc/overrides/madara/prismascans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/prismascans/src/DemonSect.kt b/multisrc/overrides/madara/prismascans/src/DemonSect.kt new file mode 100644 index 0000000000..878d1f4a08 --- /dev/null +++ b/multisrc/overrides/madara/prismascans/src/DemonSect.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.extension.pt.prismascans + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class DemonSect : Madara( + "Demon Sect", + "https://demonsect.com.br", + "pt-BR", + SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + // Changed their name from Prisma Scans to Demon Sect. + override val id: Long = 8168108118738519332 + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/prismascans/src/PrismaScans.kt b/multisrc/overrides/madara/prismascans/src/PrismaScans.kt deleted file mode 100644 index 94e8c79556..0000000000 --- a/multisrc/overrides/madara/prismascans/src/PrismaScans.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.prismascans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class PrismaScans : Madara( - "Prisma Scans", - "https://prismascans.net", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/randomscan/src/RandomScan.kt b/multisrc/overrides/madara/randomscan/src/RandomScan.kt index 6fd07aaea1..dd72ddad59 100644 --- a/multisrc/overrides/madara/randomscan/src/RandomScan.kt +++ b/multisrc/overrides/madara/randomscan/src/RandomScan.kt @@ -9,12 +9,14 @@ import java.util.concurrent.TimeUnit class RandomScan : Madara( "Random Scan", - "https://randomscans.com", + "https://randomscanlators.net", "pt-BR", - SimpleDateFormat("dd 'de' MMMM 'de' yyyy", Locale("pt", "BR")), + SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), ) { override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .build() + + override val useNewChapterEndpoint = true } diff --git a/multisrc/overrides/madara/readergen/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/readergen/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..46076aec52 Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/readergen/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/readergen/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..bb77a4f8ca Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/readergen/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/readergen/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..26f49e502e Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/readergen/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/readergen/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..48a855d254 Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/readergen/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/readergen/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ecaf51d162 Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/readergen/res/web_hi_res_512.png b/multisrc/overrides/madara/readergen/res/web_hi_res_512.png new file mode 100644 index 0000000000..d79614b274 Binary files /dev/null and b/multisrc/overrides/madara/readergen/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/readergen/src/ReaderGen.kt b/multisrc/overrides/madara/readergen/src/ReaderGen.kt new file mode 100644 index 0000000000..4127e93bf9 --- /dev/null +++ b/multisrc/overrides/madara/readergen/src/ReaderGen.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.fr.readergen + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.GET +import okhttp3.Request + +class ReaderGen : Madara("ReaderGen", "https://fr.readergen.fr", "fr") { + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/?s&post_type=wp-manga&m_orderby=views", headers) + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/page/$page/?s&post_type=wp-manga&m_orderby=latest", headers) + override fun popularMangaSelector() = searchMangaSelector() +} diff --git a/multisrc/overrides/madara/remangas/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/remangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1346a739fe..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/remangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5c70036bbe..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/remangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e6644355f8..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/remangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ff09c37f1a..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/remangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 09c8c51acb..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/res/web_hi_res_512.png b/multisrc/overrides/madara/remangas/res/web_hi_res_512.png deleted file mode 100644 index 586e6f610a..0000000000 Binary files a/multisrc/overrides/madara/remangas/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/remangas/src/Remangas.kt b/multisrc/overrides/madara/remangas/src/Remangas.kt deleted file mode 100644 index 3d4d10d4c8..0000000000 --- a/multisrc/overrides/madara/remangas/src/Remangas.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.remangas - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale - -class Remangas : Madara( - "Remangas", - "https://remangas.net", - "pt-BR", - SimpleDateFormat("dd 'de' MMM 'de' yyy", Locale("pt", "BR")), -) { - override val versionId = 2 - - override val useNewChapterEndpoint = true - - override val client: OkHttpClient = super.client.newBuilder() - .build() -} diff --git a/multisrc/overrides/madara/rh2plusmanga/src/Rh2PlusManga.kt b/multisrc/overrides/madara/rh2plusmanga/src/Rh2PlusManga.kt index 7970a9b5aa..baddef60e6 100644 --- a/multisrc/overrides/madara/rh2plusmanga/src/Rh2PlusManga.kt +++ b/multisrc/overrides/madara/rh2plusmanga/src/Rh2PlusManga.kt @@ -6,4 +6,6 @@ import java.util.Locale class Rh2PlusManga : Madara("Rh2PlusManga", "https://www.rh2plusmanga.com", "th", SimpleDateFormat("d MMMM yyyy", Locale("th"))) { override val filterNonMangaItems = false + + override val pageListParseSelector = ".reading-content img" } diff --git a/multisrc/overrides/madara/richtoscan/src/RichtoScan.kt b/multisrc/overrides/madara/richtoscan/src/RichtoScan.kt new file mode 100644 index 0000000000..904d2f9a4f --- /dev/null +++ b/multisrc/overrides/madara/richtoscan/src/RichtoScan.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.extension.es.richtoscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.FormBody +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class RichtoScan : Madara( + "RichtoScan", + "https://richtoscan.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.ROOT), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "right") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[order]", "desc") + add("vars[meta_query][relation]", "AND") + add("vars[manga_archives_item_layout]", "default") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } +} diff --git a/multisrc/overrides/madara/rightdarkscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/rightdarkscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..80bbe4c250 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/rightdarkscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ebdff01657 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..a5e115d915 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..733a0120e2 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a1784a3fd4 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/res/web_hi_res_512.png b/multisrc/overrides/madara/rightdarkscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..4e58fd4736 Binary files /dev/null and b/multisrc/overrides/madara/rightdarkscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/rightdarkscan/src/RightdarkScan.kt b/multisrc/overrides/madara/rightdarkscan/src/RightdarkScan.kt new file mode 100644 index 0000000000..9037550801 --- /dev/null +++ b/multisrc/overrides/madara/rightdarkscan/src/RightdarkScan.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.es.rightdarkscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale + +class RightdarkScan : Madara( + "Rightdark Scan", + "https://rightdark-scan.com", + "es", + SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/rwbyscan/src/RwbyScan.kt b/multisrc/overrides/madara/rwbyscan/src/RwbyScan.kt deleted file mode 100644 index c0b5fa91b0..0000000000 --- a/multisrc/overrides/madara/rwbyscan/src/RwbyScan.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.rwbyscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class RwbyScan : Madara( - "RWBY Scan", - "https://rwbyscan.site", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/s2manga/src/S2Manga.kt b/multisrc/overrides/madara/s2manga/src/S2Manga.kt new file mode 100644 index 0000000000..0179493181 --- /dev/null +++ b/multisrc/overrides/madara/s2manga/src/S2Manga.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.en.s2manga + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class S2Manga : Madara("S2Manga", "https://www.s2manga.com", "en") { + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + override val pageListParseSelector = "div.page-break img[src*=\"https\"]" +} diff --git a/multisrc/overrides/madara/samuraiscan/src/SamuraiScan.kt b/multisrc/overrides/madara/samuraiscan/src/SamuraiScan.kt index a7a3081193..d08135f516 100644 --- a/multisrc/overrides/madara/samuraiscan/src/SamuraiScan.kt +++ b/multisrc/overrides/madara/samuraiscan/src/SamuraiScan.kt @@ -4,4 +4,14 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara import java.text.SimpleDateFormat import java.util.Locale -class SamuraiScan : Madara("SamuraiScan", "https://samuraiscan.com", "es", SimpleDateFormat("MMMM d, yyyy", Locale("es"))) +class SamuraiScan : Madara( + "SamuraiScan", + "https://samuraiscan.com", + "es", + SimpleDateFormat("MMMM d, yyyy", Locale("es")), +) { + override val useNewChapterEndpoint = true + + override val mangaDetailsSelectorDescription = "div.summary_content div.manga-summary" + override val mangaDetailsSelectorStatus = "div.summary_content div.manga-authors" +} diff --git a/multisrc/overrides/madara/scansraw/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/scansraw/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cd8d4db487..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/scansraw/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index aee571c1f5..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/scansraw/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6cb5781084..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/scansraw/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9dc4058e89..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/scansraw/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 853b908744..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/res/web_hi_res_512.png b/multisrc/overrides/madara/scansraw/res/web_hi_res_512.png deleted file mode 100644 index 9db0a7a6dd..0000000000 Binary files a/multisrc/overrides/madara/scansraw/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/scansraw/src/ScansRaw.kt b/multisrc/overrides/madara/scansraw/src/ScansRaw.kt deleted file mode 100644 index b8d9a47419..0000000000 --- a/multisrc/overrides/madara/scansraw/src/ScansRaw.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.scansraw - -import eu.kanade.tachiyomi.multisrc.madara.Madara - -class ScansRaw : Madara("Scans Raw", "https://scansraw.com", "en") { - override val useNewChapterEndpoint: Boolean = true -} diff --git a/multisrc/overrides/madara/sensainayuri/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/sensainayuri/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 68f6c16708..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/sensainayuri/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd9546f059..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/sensainayuri/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 65615a9d40..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/sensainayuri/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5fb86b9584..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/sensainayuri/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 183d25fcd6..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/res/web_hi_res_512.png b/multisrc/overrides/madara/sensainayuri/res/web_hi_res_512.png deleted file mode 100644 index 630676d26d..0000000000 Binary files a/multisrc/overrides/madara/sensainayuri/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/sensainayuri/src/SensainaYuri.kt b/multisrc/overrides/madara/sensainayuri/src/SensainaYuri.kt deleted file mode 100644 index cc374ffe19..0000000000 --- a/multisrc/overrides/madara/sensainayuri/src/SensainaYuri.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.sensainayuri - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class SensainaYuri : Madara( - "Sensaina Yuri", - "https://sensainayuri.dropescan.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/shadowtrad/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/shadowtrad/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..c751ecee60 Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shadowtrad/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/shadowtrad/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b87862687a Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shadowtrad/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/shadowtrad/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..55c42162ba Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shadowtrad/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/shadowtrad/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7ccc160b0f Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shadowtrad/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/shadowtrad/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..26ada4639c Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shadowtrad/res/web_hi_res_512.png b/multisrc/overrides/madara/shadowtrad/res/web_hi_res_512.png new file mode 100644 index 0000000000..a9e1b31ef5 Binary files /dev/null and b/multisrc/overrides/madara/shadowtrad/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/shadowtrad/src/Shadowtrad.kt b/multisrc/overrides/madara/shadowtrad/src/Shadowtrad.kt new file mode 100644 index 0000000000..8a3250f318 --- /dev/null +++ b/multisrc/overrides/madara/shadowtrad/src/Shadowtrad.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.fr.shadowtrad + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class Shadowtrad : Madara("Shadowtrad", "https://shadowtrad.net", "fr", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.FRANCE)) { + override val useNewChapterEndpoint = true + override val mangaDetailsSelectorAuthor = "div.manga-authors > a" + override val mangaDetailsSelectorDescription = "div.manga-summary > .description, div.manga-summary" + override val chapterUrlSuffix = "" +} diff --git a/multisrc/overrides/madara/shibamanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/shibamanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ae815a305c Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shibamanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/shibamanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c98bb95076 Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shibamanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/shibamanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6d5ce5e477 Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shibamanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/shibamanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cb79d239f6 Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shibamanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/shibamanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0be1e33a89 Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/shibamanga/res/web_hi_res_512.png b/multisrc/overrides/madara/shibamanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..ddb05c2892 Binary files /dev/null and b/multisrc/overrides/madara/shibamanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/shibamanga/src/ShibaManga.kt b/multisrc/overrides/madara/shibamanga/src/ShibaManga.kt new file mode 100644 index 0000000000..e37cf7b743 --- /dev/null +++ b/multisrc/overrides/madara/shibamanga/src/ShibaManga.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.extension.en.shibamanga + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class ShibaManga : Madara( + "Shiba Manga", + "https://shibamanga.com", + "en", + SimpleDateFormat("MM/dd/yyyy", Locale.US), +) { + override val filterNonMangaItems = false + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } +} diff --git a/multisrc/overrides/madara/shieldmanga/src/ShieldManga.kt b/multisrc/overrides/madara/shieldmanga/src/ShieldManga.kt index 6aa6a9ef9b..e33716ae35 100644 --- a/multisrc/overrides/madara/shieldmanga/src/ShieldManga.kt +++ b/multisrc/overrides/madara/shieldmanga/src/ShieldManga.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.shieldmanga import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class ShieldManga : Madara("Shield Manga", "https://shieldmanga.io", "en") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1) .build() diff --git a/multisrc/overrides/madara/shimadascans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/shimadascans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c30dac6777..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/shimadascans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index be2885dd9e..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/shimadascans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 586834e569..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/shimadascans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 616d02f392..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/shimadascans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 41ebb88b74..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/res/web_hi_res_512.png b/multisrc/overrides/madara/shimadascans/res/web_hi_res_512.png deleted file mode 100644 index 72046496d9..0000000000 Binary files a/multisrc/overrides/madara/shimadascans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/shimadascans/src/ShimadaScans.kt b/multisrc/overrides/madara/shimadascans/src/ShimadaScans.kt deleted file mode 100644 index e3f10b1937..0000000000 --- a/multisrc/overrides/madara/shimadascans/src/ShimadaScans.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.shimadascans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class ShimadaScans : Madara("Shimada Scans", "https://shimadascans.com", "en", dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)) { - override val versionId = 2 -} diff --git a/multisrc/overrides/madara/shinigami/src/Shinigami.kt b/multisrc/overrides/madara/shinigami/src/Shinigami.kt index db53a74acb..1e3f2788b0 100644 --- a/multisrc/overrides/madara/shinigami/src/Shinigami.kt +++ b/multisrc/overrides/madara/shinigami/src/Shinigami.kt @@ -1,15 +1,41 @@ package eu.kanade.tachiyomi.extension.id.shinigami import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Headers +import okhttp3.OkHttpClient import org.jsoup.nodes.Element +import java.util.concurrent.TimeUnit +import kotlin.random.Random -class Shinigami : Madara("Shinigami", "https://shinigami.id", "id") { +class Shinigami : Madara("Shinigami", "https://shinigami.sh", "id") { // moved from Reaper Scans (id) to Shinigami (id) override val id = 3411809758861089969 - override val mangaSubString = "series" + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(4, 1, TimeUnit.SECONDS) + .build() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Sec-Fetch-Dest", "document") + .add("Sec-Fetch-Mode", "navigate") + .add("Sec-Fetch-Site", "same-origin") + .add("Upgrade-Insecure-Requests", "1") + .add("X-Requested-With", randomString) + + private fun generateRandomString(length: Int): String { + val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz.0123456789" + return (1..length) + .map { charset.random() } + .joinToString("") + } + + private val randomLength = Random.Default.nextInt(13, 21) + + private val randomString = generateRandomString(randomLength) + + override val mangaSubString = "semua-series" // Tags are useless as they are just SEO keywords. override val mangaDetailsSelectorTag = "" @@ -22,10 +48,7 @@ class Shinigami : Madara("Shinigami", "https://shinigami.id", "id") { date_upload = urlElement.selectFirst("span.chapter-release-date > i")?.text() .let { parseChapterDate(it) } - val fixedUrl = urlElement.attr("abs:href").toHttpUrl().newBuilder() - .removeAllQueryParameters("style") - .addQueryParameter("style", "list") - .toString() + val fixedUrl = urlElement.attr("abs:href") setUrlWithoutDomain(fixedUrl) } diff --git a/multisrc/overrides/madara/shiraiscans/src/ShiraiScans.kt b/multisrc/overrides/madara/shiraiscans/src/ShiraiScans.kt deleted file mode 100644 index 6c03d38774..0000000000 --- a/multisrc/overrides/madara/shiraiscans/src/ShiraiScans.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.shiraiscans - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class ShiraiScans : Madara( - "Shirai Scans", - "https://shiraiscans.com.br", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/sinensis/src/SinensisScan.kt b/multisrc/overrides/madara/sinensis/src/SinensisScan.kt index b5b697f43d..26174475ef 100644 --- a/multisrc/overrides/madara/sinensis/src/SinensisScan.kt +++ b/multisrc/overrides/madara/sinensis/src/SinensisScan.kt @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit class SinensisScan : Madara( "Sinensis Scan", - "https://sinensisscans.com", + "https://sinensisscan.net", "pt-BR", SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), ) { diff --git a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/sleepingknightscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d8fd84c84f..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/sleepingknightscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 75146c2e39..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5d3544d05d..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 67494f5b9d..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4a08843952..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sleepingknightscans/res/web_hi_res_512.png b/multisrc/overrides/madara/sleepingknightscans/res/web_hi_res_512.png deleted file mode 100644 index 586c2dc869..0000000000 Binary files a/multisrc/overrides/madara/sleepingknightscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/sodascan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ac0ed378e5..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/sodascan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 572562aa8f..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/sodascan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 141ece0e28..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/sodascan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1d55a099ea..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/sodascan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5a72d8304c..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/res/web_hi_res_512.png b/multisrc/overrides/madara/sodascan/res/web_hi_res_512.png deleted file mode 100644 index 38c16efe91..0000000000 Binary files a/multisrc/overrides/madara/sodascan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/sodascan/src/SodaScan.kt b/multisrc/overrides/madara/sodascan/src/SodaScan.kt deleted file mode 100644 index ceb6b370f0..0000000000 --- a/multisrc/overrides/madara/sodascan/src/SodaScan.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.sodascan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class SodaScan : Madara( - "SodaScan", - "https://sodascan.xyz", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/sweetdesirescan/src/SweetDesireScan.kt b/multisrc/overrides/madara/sweetdesirescan/src/SweetDesireScan.kt deleted file mode 100644 index d5bff18edb..0000000000 --- a/multisrc/overrides/madara/sweetdesirescan/src/SweetDesireScan.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.sweetdesirescan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class SweetDesireScan : Madara( - "Sweet Desire Scan", - "https://sweetdesire.com.br", - "pt-BR", - SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/taberumangas/src/TaberuMangas.kt b/multisrc/overrides/madara/taberumangas/src/TaberuMangas.kt new file mode 100644 index 0000000000..ae5e4232ae --- /dev/null +++ b/multisrc/overrides/madara/taberumangas/src/TaberuMangas.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.pt.taberumangas + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class TaberuMangas : Madara( + "Taberu Mangás", + "https://taberu.org", + "pt-BR", + SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/taurusfansub/src/TaurusFansub.kt b/multisrc/overrides/madara/taurusfansub/src/TaurusFansub.kt index b4a38e8783..aeb7b69283 100644 --- a/multisrc/overrides/madara/taurusfansub/src/TaurusFansub.kt +++ b/multisrc/overrides/madara/taurusfansub/src/TaurusFansub.kt @@ -1,14 +1,60 @@ package eu.kanade.tachiyomi.extension.es.taurusfansub import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.FormBody +import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit class TaurusFansub : Madara( "Taurus Fansub", - "https://taurusfansub.com", + "https://taurusmanga.com", "es", dateFormat = SimpleDateFormat("dd/MM/yyy", Locale.ROOT), ) { + override val client = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() + override val useNewChapterEndpoint = true + + override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))" + override val mangaDetailsSelectorDescription = "div.tab-summary > div.tab-content > div#tab-reducir > div.contenedor" + + private fun loadMoreRequest(page: Int, metaKey: String): Request { + val formBody = FormBody.Builder().apply { + add("action", "madara_load_more") + add("page", page.toString()) + add("template", "madara-core/content/content-archive") + add("vars[paged]", "1") + add("vars[orderby]", "meta_value_num") + add("vars[template]", "archive") + add("vars[sidebar]", "right") + add("vars[post_type]", "wp-manga") + add("vars[post_status]", "publish") + add("vars[meta_key]", metaKey) + add("vars[order]", "desc") + add("vars[meta_query][relation]", "OR") + add("vars[manga_archives_item_layout]", "big_thumbnail") + }.build() + + val xhrHeaders = headersBuilder() + .add("Content-Length", formBody.contentLength().toString()) + .add("Content-Type", formBody.contentType().toString()) + .add("X-Requested-With", "XMLHttpRequest") + .build() + + return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody) + } + + override fun popularMangaRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_wp_manga_views") + } + + override fun latestUpdatesRequest(page: Int): Request { + return loadMoreRequest(page - 1, "_latest_update") + } } diff --git a/multisrc/overrides/madara/tecnoscan/src/TecnoScan.kt b/multisrc/overrides/madara/tecnoscan/src/TecnoScan.kt deleted file mode 100644 index be1f4b983e..0000000000 --- a/multisrc/overrides/madara/tecnoscan/src/TecnoScan.kt +++ /dev/null @@ -1,16 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.tecnoscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class TecnoScan : Madara( - "Tecno Scan", - "https://tecnoscann.com", - "es", - dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), -) { - // Site moved from MangaThemesia to Madara - override val versionId = 2 - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/teenmanhua/src/TeenManhua.kt b/multisrc/overrides/madara/teenmanhua/src/TeenManhua.kt index 49ee684078..b010c54ee4 100644 --- a/multisrc/overrides/madara/teenmanhua/src/TeenManhua.kt +++ b/multisrc/overrides/madara/teenmanhua/src/TeenManhua.kt @@ -9,4 +9,6 @@ class TeenManhua : Madara( "https://teenmanhua.com", "en", dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US), -) +) { + override val filterNonMangaItems = false +} diff --git a/multisrc/overrides/madara/templescan/src/TempleScan.kt b/multisrc/overrides/madara/templescan/src/TempleScan.kt deleted file mode 100644 index a7537980af..0000000000 --- a/multisrc/overrides/madara/templescan/src/TempleScan.kt +++ /dev/null @@ -1,47 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.templescan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.OkHttpClient -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class TempleScan : Madara( - "Temple Scan", - "https://templescan.net", - "en", - SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH), -) { - override val mangaSubString = "comic" - override val useNewChapterEndpoint = true - override fun popularMangaSelector() = "div.c-tabs-item > div > div" - override val popularMangaUrlSelector = "div.series-box a" - override val mangaDetailsSelectorStatus = ".post-content_item:contains(Status) .summary-content" - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1) - .build() - - override fun popularMangaFromElement(element: Element): SManga { - return super.popularMangaFromElement(element).apply { - title = element.select(popularMangaUrlSelector).text() - } - } - - override fun searchPage(page: Int): String { - return if (page > 1) { - "page/$page/" - } else { - "" - } - } - - override fun chapterFromElement(element: Element): SChapter { - return super.chapterFromElement(element).apply { - name = element.select(".chapter-manhwa-title").text() - } - } -} diff --git a/multisrc/overrides/madara/theblank/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/theblank/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..88c253755d Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/theblank/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/theblank/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4c10fb3402 Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/theblank/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/theblank/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1810a81fc2 Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/theblank/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/theblank/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a57fc1069b Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/theblank/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/theblank/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a2765f05cb Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/theblank/res/web_hi_res_512.png b/multisrc/overrides/madara/theblank/res/web_hi_res_512.png new file mode 100644 index 0000000000..7df497287a Binary files /dev/null and b/multisrc/overrides/madara/theblank/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/theblank/src/TheBlank.kt b/multisrc/overrides/madara/theblank/src/TheBlank.kt new file mode 100644 index 0000000000..a28fb40754 --- /dev/null +++ b/multisrc/overrides/madara/theblank/src/TheBlank.kt @@ -0,0 +1,47 @@ +package eu.kanade.tachiyomi.extension.en.theblank + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.MangasPage +import okhttp3.Response +import java.text.SimpleDateFormat +import java.util.Locale + +class TheBlank : Madara( + "The Blank Scanlation", + "https://theblank.net", + "en", + dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US), +) { + + override val client = super.client.newBuilder() + .rateLimit(1) + .build() + + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String { + return if (page > 1) { + "page/$page/" + } else { + "" + } + } + + override fun popularMangaParse(response: Response) = + super.popularMangaParse(response).fixNextPage() + + override fun latestUpdatesParse(response: Response) = + super.latestUpdatesParse(response).fixNextPage() + + override fun searchMangaParse(response: Response) = + super.searchMangaParse(response).fixNextPage() + + private fun MangasPage.fixNextPage(): MangasPage { + return if (mangas.size < 12) { + MangasPage(mangas, false) + } else { + this + } + } +} diff --git a/multisrc/overrides/madara/thesugar/src/TheSugar.kt b/multisrc/overrides/madara/thesugar/src/TheSugar.kt deleted file mode 100644 index e233ab3672..0000000000 --- a/multisrc/overrides/madara/thesugar/src/TheSugar.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.thesugar - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TheSugar : Madara( - "The Sugar", - "https://thesugarscan.com", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val useNewChapterEndpoint = true -} diff --git a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/threequeensscanlator/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4970c44c4d..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/threequeensscanlator/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index be647813f4..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3d07e88274..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 671de7d7a7..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7448561607..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/res/web_hi_res_512.png b/multisrc/overrides/madara/threequeensscanlator/res/web_hi_res_512.png deleted file mode 100644 index b0992b3cf2..0000000000 Binary files a/multisrc/overrides/madara/threequeensscanlator/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/threequeensscanlator/src/ThreeQueensScanlator.kt b/multisrc/overrides/madara/threequeensscanlator/src/ThreeQueensScanlator.kt deleted file mode 100644 index a211c93b15..0000000000 --- a/multisrc/overrides/madara/threequeensscanlator/src/ThreeQueensScanlator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.threequeensscanlator - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class ThreeQueensScanlator : Madara( - "Three Queens Scanlator", - "https://tqscan.com.br", - "pt-BR", - SimpleDateFormat("dd 'de' MMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/toongod/src/ToonGod.kt b/multisrc/overrides/madara/toongod/src/ToonGod.kt index 54a6d271fa..9f8bbb4099 100644 --- a/multisrc/overrides/madara/toongod/src/ToonGod.kt +++ b/multisrc/overrides/madara/toongod/src/ToonGod.kt @@ -1,17 +1,12 @@ package eu.kanade.tachiyomi.extension.en.toongod import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Page -import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale -class ToonGod : Madara("ToonGod", "https://www.toongod.com", "en", SimpleDateFormat("dd MMM yyyy", Locale.US)) { - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/webtoons/page/$page/?m_orderby=views", headers) - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/webtoons/page/$page/?m_orderby=latest", headers) +class ToonGod : Madara("ToonGod", "https://www.toongod.org", "en", SimpleDateFormat("d MMM yyyy", Locale.US)) { override val mangaSubString = "webtoons" - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!, headers) - } + override val useNewChapterEndpoint = false + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" } diff --git a/multisrc/overrides/madara/toonizy/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/toonizy/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..85890f979a Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/toonizy/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/toonizy/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c78e313f04 Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/toonizy/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/toonizy/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2d21e7e900 Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/toonizy/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/toonizy/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..edcac38ca9 Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/toonizy/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/toonizy/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9c722cb0ee Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/toonizy/res/web_hi_res_512.png b/multisrc/overrides/madara/toonizy/res/web_hi_res_512.png new file mode 100644 index 0000000000..c378308af6 Binary files /dev/null and b/multisrc/overrides/madara/toonizy/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/toonizy/src/Toonizy.kt b/multisrc/overrides/madara/toonizy/src/Toonizy.kt new file mode 100644 index 0000000000..fce888281e --- /dev/null +++ b/multisrc/overrides/madara/toonizy/src/Toonizy.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.en.toonizy + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class Toonizy : Madara( + "Toonizy", + "https://toonizy.com", + "en", + dateFormat = SimpleDateFormat("MMM d, yy", Locale.ENGLISH), +) { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/topmanhua/src/TopManhua.kt b/multisrc/overrides/madara/topmanhua/src/TopManhua.kt index c27d6e19c9..4a8b4a3663 100644 --- a/multisrc/overrides/madara/topmanhua/src/TopManhua.kt +++ b/multisrc/overrides/madara/topmanhua/src/TopManhua.kt @@ -1,13 +1,10 @@ package eu.kanade.tachiyomi.extension.en.topmanhua import eu.kanade.tachiyomi.multisrc.madara.Madara -import okhttp3.Headers import java.text.SimpleDateFormat import java.util.Locale class TopManhua : Madara("Top Manhua", "https://topmanhua.com", "en", SimpleDateFormat("MM/dd/yy", Locale.US)) { - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - // The website does not flag the content. override val filterNonMangaItems = false } diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..08728fdd4e Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c14c3fc3ea Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..75c19dac29 Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ffd3932795 Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d661e5611f Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/res/web_hi_res_512.png b/multisrc/overrides/madara/traduccionesmoonlight/res/web_hi_res_512.png new file mode 100644 index 0000000000..bdfc28d88a Binary files /dev/null and b/multisrc/overrides/madara/traduccionesmoonlight/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/traduccionesmoonlight/src/TraduccionesMoonlight.kt b/multisrc/overrides/madara/traduccionesmoonlight/src/TraduccionesMoonlight.kt new file mode 100644 index 0000000000..6d20405564 --- /dev/null +++ b/multisrc/overrides/madara/traduccionesmoonlight/src/TraduccionesMoonlight.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.es.traduccionesmoonlight + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class TraduccionesMoonlight : Madara( + "Traducciones Moonlight", + "https://traduccionesmoonlight.com", + "es", + SimpleDateFormat("dd 'de' MMMM 'de' yyyy", Locale("es")), +) { + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/trapscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/trapscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 31170ff21c..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/trapscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a18873c8c8..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/trapscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 65e67f049d..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/trapscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 06e893ab23..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/trapscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5cbd28b4ac..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/res/web_hi_res_512.png b/multisrc/overrides/madara/trapscans/res/web_hi_res_512.png deleted file mode 100644 index 54599cf1f6..0000000000 Binary files a/multisrc/overrides/madara/trapscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/trapscans/src/TrapScans.kt b/multisrc/overrides/madara/trapscans/src/TrapScans.kt deleted file mode 100644 index 144657062d..0000000000 --- a/multisrc/overrides/madara/trapscans/src/TrapScans.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.trapscans - -import eu.kanade.tachiyomi.multisrc.madara.Madara - -class TrapScans : Madara("Trap Scans", "https://trapscans.com", "en") { - - override val mangaDetailsSelectorDescription = ".description-summary p" -} diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8e167e74b7..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e1e9fd3479..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 75e15f740e..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 120c9864a6..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 42e557dfa9..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/res/web_hi_res_512.png b/multisrc/overrides/madara/tudoquadrinhos/res/web_hi_res_512.png deleted file mode 100644 index 8318e2f8d3..0000000000 Binary files a/multisrc/overrides/madara/tudoquadrinhos/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/tudoquadrinhos/src/TudoQuadrinhos.kt b/multisrc/overrides/madara/tudoquadrinhos/src/TudoQuadrinhos.kt deleted file mode 100644 index 3d7541bda4..0000000000 --- a/multisrc/overrides/madara/tudoquadrinhos/src/TudoQuadrinhos.kt +++ /dev/null @@ -1,23 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.tudoquadrinhos - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TudoQuadrinhos : Madara( - "Tudo Quadrinhos", - "https://tudoquadrinhos.com.br", - "pt-BR", - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - // The tags are just SEO keywords. - override val mangaDetailsSelectorTag: String = "" -} diff --git a/multisrc/overrides/madara/turkcemanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/turkcemanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 667b137ec1..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/turkcemanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5c5c1894bd..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/turkcemanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5ae5a8f1e6..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/turkcemanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 519a65c6c9..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/turkcemanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4733f566ef..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/res/web_hi_res_512.png b/multisrc/overrides/madara/turkcemanga/res/web_hi_res_512.png deleted file mode 100644 index 17ce9c54c9..0000000000 Binary files a/multisrc/overrides/madara/turkcemanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/turkcemanga/src/TurkceManga.kt b/multisrc/overrides/madara/turkcemanga/src/TurkceManga.kt deleted file mode 100644 index 1d0714960a..0000000000 --- a/multisrc/overrides/madara/turkcemanga/src/TurkceManga.kt +++ /dev/null @@ -1,16 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.turkcemanga - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Element - -class TurkceManga : Madara("Türkçe Manga", "https://turkcemanga.com", "tr") { - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/?s&post_type=wp-manga&m_orderby=views", headers) - override fun popularMangaSelector() = searchMangaSelector() - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/page/$page/?s&post_type=wp-manga&m_orderby=latest", headers) - override fun latestUpdatesSelector() = searchMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) -} diff --git a/multisrc/overrides/madara/unitoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/unitoon/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..289df82873 Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/unitoon/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3769279907 Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoon/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..f63ace60c3 Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoon/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7d1709c7fa Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoon/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ce25768b1b Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoon/res/web_hi_res_512.png b/multisrc/overrides/madara/unitoon/res/web_hi_res_512.png new file mode 100644 index 0000000000..262c279157 Binary files /dev/null and b/multisrc/overrides/madara/unitoon/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/unitoon/src/Unitoon.kt b/multisrc/overrides/madara/unitoon/src/Unitoon.kt new file mode 100644 index 0000000000..4f6c3fb279 --- /dev/null +++ b/multisrc/overrides/madara/unitoon/src/Unitoon.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.es.unitoon + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale + +class Unitoon : Madara( + "Unitoon", + "https://lectorunitoon.com", + "es", + SimpleDateFormat("dd/MM/yyyy", Locale("es")), +) { + override val client = super.client.newBuilder() + .rateLimit(2, 1) + .build() + + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/unitoonoficial/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/unitoonoficial/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9197367c76 Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/unitoonoficial/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..6fb6d41030 Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..0d1d4ac36a Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..21a2f11b0a Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..35cd506f51 Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/res/web_hi_res_512.png b/multisrc/overrides/madara/unitoonoficial/res/web_hi_res_512.png new file mode 100644 index 0000000000..7bde7ec0e0 Binary files /dev/null and b/multisrc/overrides/madara/unitoonoficial/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/unitoonoficial/src/UnitoonOficial.kt b/multisrc/overrides/madara/unitoonoficial/src/UnitoonOficial.kt new file mode 100644 index 0000000000..428f4f1c3c --- /dev/null +++ b/multisrc/overrides/madara/unitoonoficial/src/UnitoonOficial.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.es.unitoonoficial + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import java.text.SimpleDateFormat +import java.util.Locale + +class UnitoonOficial : Madara( + "Unitoon Oficial", + "https://unitoonoficial.com", + "es", + SimpleDateFormat("dd/MM/yyyy", Locale("es")), +) { + override val useNewChapterEndpoint = true +} diff --git a/multisrc/overrides/madara/visbellum/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/visbellum/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cd777dc5fa..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/visbellum/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a5874ec461..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/visbellum/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 52f273a417..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/visbellum/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3ae6be76f5..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/visbellum/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 56ebb3db57..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/res/web_hi_res_512.png b/multisrc/overrides/madara/visbellum/res/web_hi_res_512.png deleted file mode 100644 index b98919c32b..0000000000 Binary files a/multisrc/overrides/madara/visbellum/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/visbellum/src/Visbellum.kt b/multisrc/overrides/madara/visbellum/src/Visbellum.kt deleted file mode 100644 index 1421998d23..0000000000 --- a/multisrc/overrides/madara/visbellum/src/Visbellum.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.visbellum - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Visbellum : Madara( - "Visbellum", - "https://visbellum.com", - "pt-BR", - SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/madara/voircomic/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/voircomic/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 63777d07e9..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/voircomic/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 393cf245d6..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/voircomic/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1ed4ba3927..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/voircomic/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6142066c46..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/voircomic/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 24b220cf46..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/res/web_hi_res_512.png b/multisrc/overrides/madara/voircomic/res/web_hi_res_512.png deleted file mode 100644 index 4eb393d0b6..0000000000 Binary files a/multisrc/overrides/madara/voircomic/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/voircomic/src/VoirComic.kt b/multisrc/overrides/madara/voircomic/src/VoirComic.kt deleted file mode 100644 index a5782c0ccb..0000000000 --- a/multisrc/overrides/madara/voircomic/src/VoirComic.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.voircomic - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import java.text.SimpleDateFormat -import java.util.Locale - -class VoirComic : Madara("VoirComic", "https://voircomic.com", "fr", dateFormat = SimpleDateFormat("d MMMM yyyy", Locale.FRANCE)) diff --git a/multisrc/overrides/madara/vortcescan/src/VortceScan.kt b/multisrc/overrides/madara/vortcescan/src/VortceScan.kt new file mode 100644 index 0000000000..bfc19c29ba --- /dev/null +++ b/multisrc/overrides/madara/vortcescan/src/VortceScan.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.pt.vortcescan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class VortceScan : Madara( + "Vórtce Scan", + "https://vortcescan.com.br", + "pt-BR", + SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/madara/wakascan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/wakascan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e487bc0d72..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/wakascan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/wakascan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f0bdb8e294..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/wakascan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/wakascan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bb4e0e986f..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/wakascan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/wakascan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ff1136c176..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/wakascan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/wakascan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 391dc5f4a5..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/wakascan/res/web_hi_res_512.png b/multisrc/overrides/madara/wakascan/res/web_hi_res_512.png deleted file mode 100644 index 027a94f808..0000000000 Binary files a/multisrc/overrides/madara/wakascan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/warqueenscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 325d68a081..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/warqueenscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2d60fa9473..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/warqueenscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1ec905994b..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/warqueenscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 047d04596c..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/warqueenscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ff8f468ada..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/res/web_hi_res_512.png b/multisrc/overrides/madara/warqueenscan/res/web_hi_res_512.png deleted file mode 100644 index 90ec98cea8..0000000000 Binary files a/multisrc/overrides/madara/warqueenscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/madara/warqueenscan/src/WarQueenScan.kt b/multisrc/overrides/madara/warqueenscan/src/WarQueenScan.kt deleted file mode 100644 index 3c485e0d54..0000000000 --- a/multisrc/overrides/madara/warqueenscan/src/WarQueenScan.kt +++ /dev/null @@ -1,23 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.warqueenscan - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class WarQueenScan : Madara( - "War Queen Scan", - "https://wqscan.com", - "pt-BR", - SimpleDateFormat("yyyy-MM-dd", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() -} diff --git a/multisrc/overrides/madara/webdexscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/webdexscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..2eeb1cb6da Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/webdexscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/webdexscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..82d9ae249d Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/webdexscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/webdexscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d29474fad4 Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/webdexscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/webdexscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dc4cc9c2ff Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/webdexscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/webdexscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b20b7dfc48 Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/webdexscans/res/web_hi_res_512.png b/multisrc/overrides/madara/webdexscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..92757565e2 Binary files /dev/null and b/multisrc/overrides/madara/webdexscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/webdexscans/src/WebdexScans.kt b/multisrc/overrides/madara/webdexscans/src/WebdexScans.kt new file mode 100644 index 0000000000..76a5446485 --- /dev/null +++ b/multisrc/overrides/madara/webdexscans/src/WebdexScans.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.webdexscans + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class WebdexScans : Madara("Webdex Scans", "https://webdexscans.com", "en") { + override val useNewChapterEndpoint = true + override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/webtooncity/src/WebtoonCity.kt b/multisrc/overrides/madara/webtooncity/src/WebtoonCity.kt new file mode 100644 index 0000000000..9418a5078b --- /dev/null +++ b/multisrc/overrides/madara/webtooncity/src/WebtoonCity.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.webtooncity + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class WebtoonCity : Madara("Webtoon City", "https://webtooncity.com", "en") { + override val useNewChapterEndpoint = false + override val mangaSubString = "webtoon" + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/madara/wickedwitchscan/src/WickedWitchScan.kt b/multisrc/overrides/madara/wickedwitchscan/src/WickedWitchScan.kt new file mode 100644 index 0000000000..50e48664a1 --- /dev/null +++ b/multisrc/overrides/madara/wickedwitchscan/src/WickedWitchScan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.pt.wickedwitchscan + +import eu.kanade.tachiyomi.multisrc.madara.Madara +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale + +class WickedWitchScan : Madara( + "Wicked Witch Scan", + "https://wickedwitchscan.com", + "pt-BR", + SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .build() +} diff --git a/multisrc/overrides/madara/yanpfansub/src/YANPFansub.kt b/multisrc/overrides/madara/yanpfansub/src/YANPFansub.kt index 1963f693f3..6b468f1efd 100644 --- a/multisrc/overrides/madara/yanpfansub/src/YANPFansub.kt +++ b/multisrc/overrides/madara/yanpfansub/src/YANPFansub.kt @@ -20,7 +20,7 @@ class YANPFansub : Madara( // Scanlator changed the theme from WpMangaReader to Madara. override val versionId: Int = 2 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .addInterceptor(::checkPasswordProtectedIntercept) .build() diff --git a/multisrc/overrides/madara/yaoitoshokan/src/YaoiToshokan.kt b/multisrc/overrides/madara/yaoitoshokan/src/YaoiToshokan.kt index 5855a24f7a..cb76122dad 100644 --- a/multisrc/overrides/madara/yaoitoshokan/src/YaoiToshokan.kt +++ b/multisrc/overrides/madara/yaoitoshokan/src/YaoiToshokan.kt @@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Page -import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document @@ -19,10 +18,7 @@ class YaoiToshokan : Madara( SimpleDateFormat("dd MMM yyyy", Locale("pt", "BR")), ) { - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .removeAll("User-Agent") - - override val client: OkHttpClient = network.client.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .build() diff --git a/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt b/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt deleted file mode 100644 index dc62d586a6..0000000000 --- a/multisrc/overrides/madara/yugenmangas/src/YugenMangas.kt +++ /dev/null @@ -1,45 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.yugenmangas - -import eu.kanade.tachiyomi.multisrc.madara.Madara -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Headers -import okhttp3.OkHttpClient -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class YugenMangas : Madara( - "YugenMangas", - "https://yugenmangas.com.br", - "pt-BR", - SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(uaIntercept) - .rateLimit(1, 3, TimeUnit.SECONDS) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/") - - override val useNewChapterEndpoint: Boolean = true - - override val mangaSubString = "series" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("p.chapter-manhwa-title")!!.text() - date_upload = parseChapterDate(element.selectFirst("span.chapter-release-date i")?.text()) - - val chapterUrl = element.selectFirst("a")!!.attr("abs:href") - setUrlWithoutDomain( - chapterUrl.substringBefore("?style=paged") + - if (!chapterUrl.endsWith(chapterUrlSuffix)) chapterUrlSuffix else "", - ) - } - - override val useRandomUserAgentByDefault: Boolean = true -} diff --git a/multisrc/overrides/madara/zinmangatop/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/madara/zinmangatop/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..97b1749cfe Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/zinmangatop/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/madara/zinmangatop/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..660e2fda74 Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/zinmangatop/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/madara/zinmangatop/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..12b451c468 Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/zinmangatop/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/madara/zinmangatop/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..677d119bce Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/zinmangatop/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/madara/zinmangatop/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ffc766fa0a Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/madara/zinmangatop/res/web_hi_res_512.png b/multisrc/overrides/madara/zinmangatop/res/web_hi_res_512.png new file mode 100644 index 0000000000..bdbde6818d Binary files /dev/null and b/multisrc/overrides/madara/zinmangatop/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/madara/zinmangatop/src/ZinMangaTop.kt b/multisrc/overrides/madara/zinmangatop/src/ZinMangaTop.kt new file mode 100644 index 0000000000..445a22ebe4 --- /dev/null +++ b/multisrc/overrides/madara/zinmangatop/src/ZinMangaTop.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.en.zinmangatop + +import eu.kanade.tachiyomi.multisrc.madara.Madara + +class ZinMangaTop : Madara("ZinManga.top (unoriginal)", "https://zinmanga.top", "en") { + override val useNewChapterEndpoint = true + + override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/" +} diff --git a/multisrc/overrides/mangacatalog/readblackclovermangaonline/src/ReadBlackCloverMangaOnline.kt b/multisrc/overrides/mangacatalog/readblackclovermangaonline/src/ReadBlackCloverMangaOnline.kt new file mode 100644 index 0000000000..1efd430498 --- /dev/null +++ b/multisrc/overrides/mangacatalog/readblackclovermangaonline/src/ReadBlackCloverMangaOnline.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.en.readblackclovermangaonline + +import eu.kanade.tachiyomi.multisrc.mangacatalog.MangaCatalog + +class ReadBlackCloverMangaOnline : MangaCatalog("Read Black Clover Manga Online", "https://ww7.readblackclover.com", "en") { + override val sourceList = listOf( + Pair("Black Clover", "$baseUrl/manga/black-clover"), + Pair("Black Clover Gainden Quartet Knights", "$baseUrl/manga/black-clover-gaiden-quartet-knights"), + Pair("Fan Colored", "$baseUrl/manga/black-clover-colored"), + Pair("Hungry Joker", "$baseUrl/manga/hungry-joker"), + ) +} diff --git a/multisrc/overrides/mangadventure/assortedscans/AndroidManifest.xml b/multisrc/overrides/mangadventure/assortedscans/AndroidManifest.xml index 227102ee96..0a89d8d68b 100644 --- a/multisrc/overrides/mangadventure/assortedscans/AndroidManifest.xml +++ b/multisrc/overrides/mangadventure/assortedscans/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + element.text() } - .toMutableSet() - - manga.genre = genres.toList().joinToString(", ") - - return manga - } - - override fun Document.getSanitizedDetails(): Element = this - override fun chapterListSelector() = ".chapter-l a" - override fun String.sanitizeChapter() = substringAfterLast(" – ").substringBeforeLast("漫画") - - override fun pageSelector(): Evaluator { - return Evaluator.Tag("img") - } - - override fun pageListParse(document: Document): List { - val position = 32 - val parser = ImageListParser(document.html(), position) - - return parser.getImageList().orEmpty().mapIndexed { i, imageUrl -> - Page(i, imageUrl = imageUrl) - } - } -} diff --git a/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt b/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt index 262ded34c7..06d32517d6 100644 --- a/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt +++ b/multisrc/overrides/mangareader/mangafire/src/MangaFire.kt @@ -85,15 +85,15 @@ open class MangaFire( return GET(urlBuilder.build(), headers) } - override fun searchMangaSelector() = ".mangas.items .inner" - override fun searchMangaNextPageSelector() = ".page-item.active + .page-item .page-link" + override fun searchMangaSelector() = ".original.card-lg .unit .inner" + override fun searchMangaFromElement(element: Element) = SManga.create().apply { - element.selectFirst("a.color-light")!!.let { - url = it.attr("href") - title = it.attr("title") + element.selectFirst(".info > a")!!.let { + setUrlWithoutDomain(it.attr("href")) + title = it.ownText() } element.selectFirst(Evaluator.Tag("img"))!!.let { thumbnail_url = it.attr("src") @@ -101,25 +101,26 @@ open class MangaFire( } override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val root = document.selectFirst(".detail .top .wrapper")!! - val mangaTitle = root.selectFirst(Evaluator.Class("name"))!!.ownText() + val root = document.selectFirst(".info")!! + val mangaTitle = root.child(1).ownText() title = mangaTitle description = document.run { - val description = selectFirst(Evaluator.Class("summary"))!!.ownText() - when (val altTitle = root.selectFirst(Evaluator.Class("al-name"))!!.ownText()) { + val description = selectFirst(Evaluator.Class("description"))!!.ownText() + when (val altTitle = root.child(2).ownText()) { "", mangaTitle -> description else -> "$description\n\nAlternative Title: $altTitle" } } - thumbnail_url = root.selectFirst(Evaluator.Tag("img"))!!.attr("src") - status = when (root.selectFirst(Evaluator.Class("status"))!!.ownText()) { + thumbnail_url = document.selectFirst(".poster")!! + .selectFirst("img")!!.attr("src") + status = when (root.child(0).ownText()) { "Completed" -> SManga.COMPLETED "Releasing" -> SManga.ONGOING "On_hiatus" -> SManga.ON_HIATUS "Discontinued" -> SManga.CANCELLED else -> SManga.UNKNOWN } - with(root.selectFirst(Evaluator.Class("more-info"))!!) { + with(document.selectFirst(Evaluator.Class("meta"))!!) { author = selectFirst("span:contains(Author:) + span")?.text() val type = selectFirst("span:contains(Type:) + span")?.text() val genres = selectFirst("span:contains(Genres:) + span")?.text() @@ -132,20 +133,39 @@ open class MangaFire( override fun chapterListRequest(mangaUrl: String, type: String): Request { val id = mangaUrl.substringAfterLast('.') - return GET("$baseUrl/ajax/read/$id/list?viewby=$type", headers) + return GET("$baseUrl/ajax/manga/$id/$type/$langCode", headers) } override fun parseChapterElements(response: Response, isVolume: Boolean): List { - val result = json.decodeFromString>(response.body.string()).result - val container = result.parseHtml(if (isVolume) volumeType else chapterType) - ?.selectFirst(".numberlist[data-lang=$langCode]") - ?: return emptyList() - return container.children().map { it.child(0) } + val result = json.decodeFromString>(response.body.string()).result + val document = Jsoup.parse(result) + + val elements = document.select("ul li") + if (elements.size > 0) { + val linkToFirstChapter = elements[0].selectFirst(Evaluator.Tag("a"))!!.attr("href") + val mangaId = linkToFirstChapter.toString().substringAfter('.').substringBefore('/') + + val request = GET("$baseUrl/ajax/read/$mangaId/chapter/$langCode", headers) + val response = client.newCall(request).execute() + val res = json.decodeFromString>(response.body.string()).result.html + val chapterInfoDocument = Jsoup.parse(res) + val chapters = chapterInfoDocument.select("ul li") + for ((i, it) in elements.withIndex()) { + it.attr("data-id", chapters[i].select("a").attr("data-id")) + } + } + return elements.toList() } + @Serializable + class ChapterIdsDto( + val html: String, + val title_format: String, + ) + override fun updateChapterList(manga: SManga, chapters: List) { val document = client.newCall(mangaDetailsRequest(manga)).execute().asJsoup() - val elements = document.selectFirst(".chapter-list[data-name=$langCode]")!!.children() + val elements = document.selectFirst(".scroll-sm")!!.children() val chapterCount = chapters.size if (elements.size != chapterCount) throw Exception("Chapter count doesn't match. Try updating again.") val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.US) @@ -180,6 +200,20 @@ open class MangaFire( } } + @Serializable + class PageListDto(private val images: List>) { + val pages get() = images.map { + Image(it[0].content, it[2].int) + } + } + class Image(val url: String, val offset: Int) + + @Serializable + class ResponseDto( + val result: T, + val status: Int, + ) + override fun getFilterList() = FilterList( Filter.Header("NOTE: Ignored if using text search!"), @@ -191,22 +225,4 @@ open class MangaFire( ChapterCountFilter(), SortFilter(), ) - - @Serializable - class ChapterListDto(private val html: String, private val link_format: String) { - fun parseHtml(type: String): Document? { - if ("LANG/$type-NUMBER" !in link_format) return null - return Jsoup.parseBodyFragment(html) - } - } - - @Serializable - class PageListDto(private val images: List>) { - val pages get() = images.map { Image(it[0].content, it[2].int) } - } - - class Image(val url: String, val offset: Int) - - @Serializable - class ResponseDto(val result: T) } diff --git a/multisrc/overrides/mangasar/default/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangasar/default/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5f3c83a4c9..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/default/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangasar/default/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index aa7edff855..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/default/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangasar/default/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 0dfe9e0759..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/default/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangasar/default/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1690d709ea..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/default/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangasar/default/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 39f7dda6ac..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/default/res/web_hi_res_512.png b/multisrc/overrides/mangasar/default/res/web_hi_res_512.png deleted file mode 100644 index 5b26d20e55..0000000000 Binary files a/multisrc/overrides/mangasar/default/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangasar/mangasup/src/MangasUp.kt b/multisrc/overrides/mangasar/mangasup/src/MangasUp.kt deleted file mode 100644 index ff4addb2db..0000000000 --- a/multisrc/overrides/mangasar/mangasup/src/MangasUp.kt +++ /dev/null @@ -1,32 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangasup - -import eu.kanade.tachiyomi.multisrc.mangasar.MangaSar -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element - -class MangasUp : MangaSar( - "MangásUp", - "https://mangasup.net", - "pt-BR", -) { - - override fun chapterListPaginatedRequest(mangaUrl: String, page: Int): Request { - return GET(baseUrl + mangaUrl, headers) - } - - override fun chapterListParse(response: Response): List { - return response.asJsoup() - .select("ul.full-chapters-list > li > a") - .map(::chapterFromElement) - } - - private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("span.cap-text")!!.text() - date_upload = element.selectFirst("span.chapter-date")?.text()?.toDate() ?: 0L - setUrlWithoutDomain(element.attr("href")) - } -} diff --git a/multisrc/overrides/mangasar/mangazim/src/Mangazim.kt b/multisrc/overrides/mangasar/mangazim/src/Mangazim.kt deleted file mode 100644 index ca0299f24a..0000000000 --- a/multisrc/overrides/mangasar/mangazim/src/Mangazim.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangazim - -import eu.kanade.tachiyomi.multisrc.mangasar.MangaSar -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element - -class Mangazim : MangaSar("Mangazim", "https://mangazim.com", "pt-BR") { - - override fun chapterListPaginatedRequest(mangaUrl: String, page: Int): Request { - return GET(baseUrl + mangaUrl, headers) - } - - override fun chapterListParse(response: Response): List { - return response.asJsoup() - .select("ul.full-chapters-list > li > a") - .map(::chapterFromElement) - } - - private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("span.cap-text")!!.text() - date_upload = element.selectFirst("span.chapter-date")?.text()?.toDate() ?: 0L - setUrlWithoutDomain(element.attr("href")) - } -} diff --git a/multisrc/overrides/mangasar/seemangas/src/Seemangas.kt b/multisrc/overrides/mangasar/seemangas/src/Seemangas.kt deleted file mode 100644 index 246e3820f7..0000000000 --- a/multisrc/overrides/mangasar/seemangas/src/Seemangas.kt +++ /dev/null @@ -1,136 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.seemangas - -import eu.kanade.tachiyomi.multisrc.mangasar.MangaSar -import eu.kanade.tachiyomi.multisrc.mangasar.MangaSarLatestDto -import eu.kanade.tachiyomi.multisrc.mangasar.MangaSarReaderDto -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.FormBody -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element - -class Seemangas : MangaSar( - "Seemangas", - "https://seemangas.com", - "pt-BR", -) { - - override fun popularMangaSelector() = "ul.sidebar-popular li.popular-treending" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("h4.title")!!.text() - thumbnail_url = element.selectFirst("div.tumbl img")!!.attr("data-lazy-src") - setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) - } - - override fun latestUpdatesRequest(page: Int): Request { - val payload = FormBody.Builder() - .add("action", "get_lancamentos") - .add("pagina", page.toString()) - .build() - - val newHeaders = headersBuilder() - .add("Content-Length", payload.contentLength().toString()) - .add("Content-Type", payload.contentType().toString()) - .add("X-Requested-With", "XMLHttpRequest") - .build() - - return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, payload) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs() - - val latestMangas = result.releases - .map(::latestUpdatesFromObject) - .distinctBy { it.url } - - return MangasPage(latestMangas, hasNextPage = result.releases.isNotEmpty()) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - val infoElement = document.selectFirst("div.box-single:has(div.mangapage)")!! - - return SManga.create().apply { - title = infoElement.selectFirst("h1.kw-title")!!.text() - author = infoElement.selectFirst("div.mdq.author")!!.text().trim() - description = infoElement.selectFirst("div.sinopse-page")!!.text() - genre = infoElement.select("div.generos a.widget-btn")!!.joinToString { it.text() } - status = infoElement.selectFirst("span.mdq")!!.text().toStatus() - thumbnail_url = infoElement.selectFirst("div.thumb img")!!.attr("abs:data-lazy-src") - } - } - override fun chapterListPaginatedRequest(mangaUrl: String, page: Int): Request { - return GET(baseUrl + mangaUrl, headers) - } - - override fun chapterListParse(response: Response): List { - return response.asJsoup() - .select("ul.full-chapters-list > li > a") - .map(::chapterFromElement) - } - - private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("span.cap-text")!!.text() - date_upload = element.selectFirst("span.chapter-date")?.text()?.toDate() ?: 0L - setUrlWithoutDomain(element.attr("href")) - } - - override fun pageListApiRequest(chapterUrl: String, serieId: String, token: String): Request { - val chapterId = CHAPTER_ID_REGEX.find(chapterUrl)!!.groupValues[1] - - val payload = FormBody.Builder() - .add("action", "get_image_list") - .add("id_serie", chapterId) - .add("secury", token) - .build() - - val newHeaders = apiHeadersBuilder() - .add("Content-Length", payload.contentLength().toString()) - .add("Content-Type", payload.contentType().toString()) - .set("Referer", chapterUrl) - .build() - - return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, payload) - } - - override fun pageListParse(response: Response): List { - val document = response.asJsoup() - val apiParams = document.selectFirst("script:containsData(id_serie)")?.data() - ?: throw Exception(TOKEN_NOT_FOUND) - - val chapterUrl = response.request.url.toString() - val infoReader = apiParams - .substringAfter("{") - .substringBeforeLast("}") - val readerParams = json.parseToJsonElement("{$infoReader}").jsonObject - val serieId = readerParams["id_serie"]!!.jsonPrimitive.content - val token = readerParams["token"]!!.jsonPrimitive.content - - val apiRequest = pageListApiRequest(chapterUrl, serieId, token) - val apiResponse = client.newCall(apiRequest).execute().parseAs() - - return apiResponse.images - .filter { it.url.startsWith("http") } - .mapIndexed { i, page -> Page(i, chapterUrl, page.url) } - } - - private fun String.toStatus(): Int = when (this) { - "Em andamento" -> SManga.ONGOING - "Completo" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - companion object { - private val CHAPTER_ID_REGEX = "(\\d+)$".toRegex() - } -} diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4869653323 Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..e903ad2974 Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..39220b47bb Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fd3dc0ce47 Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c22fe79652 Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/ainzscansid/res/web_hi_res_512.png new file mode 100644 index 0000000000..a4b1a071d6 Binary files /dev/null and b/multisrc/overrides/mangathemesia/ainzscansid/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/ainzscansid/src/AinzScansID.kt b/multisrc/overrides/mangathemesia/ainzscansid/src/AinzScansID.kt new file mode 100644 index 0000000000..6bf16b233a --- /dev/null +++ b/multisrc/overrides/mangathemesia/ainzscansid/src/AinzScansID.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.extension.id.ainzscansid + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia + +class AinzScansID : MangaThemesia("Ainz Scans ID", "https://ainzscans.site", "id", "/series") { + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/madara/aiyumanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/aiyumanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/aiyumanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/aiyumanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/aiyumanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/aiyumanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/aiyumanga/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/aiyumanga/res/web_hi_res_512.png rename to multisrc/overrides/mangathemesia/aiyumanga/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangathemesia/aiyumanga/src/AiYuManga.kt b/multisrc/overrides/mangathemesia/aiyumanga/src/AiYuManga.kt new file mode 100644 index 0000000000..7258d67d77 --- /dev/null +++ b/multisrc/overrides/mangathemesia/aiyumanga/src/AiYuManga.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.extension.es.aiyumanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class AiYuManga : MangaThemesia( + "AiYuManga", + "https://aiyumanhua.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + // Site moved from ZeistManga to MangaThemesia + override val versionId = 3 + + override val client = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/mangathemesia/alceascan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..dc4ade72cf Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..1df5b7a1ff Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2f539dbd78 Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8354ada108 Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..959dee2fbe Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/alceascan/res/web_hi_res_512.png new file mode 100644 index 0000000000..4c88e6f345 Binary files /dev/null and b/multisrc/overrides/mangathemesia/alceascan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/alceascan/src/Alceascan.kt b/multisrc/overrides/mangathemesia/alceascan/src/Alceascan.kt new file mode 100644 index 0000000000..e21265f45b --- /dev/null +++ b/multisrc/overrides/mangathemesia/alceascan/src/Alceascan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.id.alceascan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class Alceascan : MangaThemesia("Alceascan", "https://alceascan.my.id", "id") { + + // Website theme changed from zManga to WPMangaThemesia. + override val versionId = 2 + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4) + .build() + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/animatedglitchedscans/src/AnimatedGlitchedScans.kt b/multisrc/overrides/mangathemesia/animatedglitchedscans/src/AnimatedGlitchedScans.kt index d70c658f1d..5821b5f014 100644 --- a/multisrc/overrides/mangathemesia/animatedglitchedscans/src/AnimatedGlitchedScans.kt +++ b/multisrc/overrides/mangathemesia/animatedglitchedscans/src/AnimatedGlitchedScans.kt @@ -2,4 +2,4 @@ package eu.kanade.tachiyomi.extension.en.animatedglitchedscans import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -class AnimatedGlitchedScans : MangaThemesia("Animated Glitched Scans", "https://anigliscans.com", "en", mangaUrlDirectory = "/series") +class AnimatedGlitchedScans : MangaThemesia("Animated Glitched Scans", "https://anigliscans.xyz", "en", mangaUrlDirectory = "/series") diff --git a/multisrc/overrides/mangathemesia/arcanescan/src/Arcanescan.kt b/multisrc/overrides/mangathemesia/arcanescan/src/Arcanescan.kt deleted file mode 100644 index 835f254408..0000000000 --- a/multisrc/overrides/mangathemesia/arcanescan/src/Arcanescan.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.arcanescan - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import java.text.SimpleDateFormat -import java.util.Locale - -class Arcanescan : MangaThemesia( - "Arcane scan", - "https://arcanescan.fr", - "fr", - dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale.FRANCE), -) diff --git a/multisrc/overrides/mangathemesia/areamanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7df6a260f8 Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..cfb55ab6ce Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dac425a959 Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c1aae1158e Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..378c39bdfa Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/areamanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..83b42018cd Binary files /dev/null and b/multisrc/overrides/mangathemesia/areamanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/areamanga/src/AreaManga.kt b/multisrc/overrides/mangathemesia/areamanga/src/AreaManga.kt new file mode 100644 index 0000000000..ea18cb04a7 --- /dev/null +++ b/multisrc/overrides/mangathemesia/areamanga/src/AreaManga.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.extension.ar.areamanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.SManga +import java.text.SimpleDateFormat +import java.util.Locale + +class AreaManga : MangaThemesia( + "أريا مانجا", + "https://www.areascans.net", + "ar", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), +) { + override val seriesArtistSelector = + ".tsinfo .imptdt:contains(الرسام) i, ${super.seriesArtistSelector}" + override val seriesAuthorSelector = + ".tsinfo .imptdt:contains(المؤلف) i, ${super.seriesAuthorSelector}" + override val seriesStatusSelector = + ".tsinfo .imptdt:contains(الحالة) i, ${super.seriesStatusSelector}" + override val seriesTypeSelector = + ".tsinfo .imptdt:contains(النوع) i, ${super.seriesTypeSelector}" + + override fun String?.parseStatus() = when { + this == null -> SManga.UNKNOWN + this.contains("مستمر", ignoreCase = true) -> SManga.ONGOING + this.contains("مكتمل", ignoreCase = true) -> SManga.COMPLETED + this.contains("متوقف", ignoreCase = true) -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } +} diff --git a/multisrc/overrides/mangathemesia/arkhamscan/src/ArkhamScan.kt b/multisrc/overrides/mangathemesia/arkhamscan/src/ArkhamScan.kt new file mode 100644 index 0000000000..868c765448 --- /dev/null +++ b/multisrc/overrides/mangathemesia/arkhamscan/src/ArkhamScan.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.pt.arkhamscan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale + +class ArkhamScan : MangaThemesia( + "Arkham Scan", + "https://arkhamscan.com", + "pt-BR", + dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .build() + + override val altNamePrefix = "Nomes alternativos: " +} diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ba568b04b2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..25832490aa Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..74335d56c5 Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c6f496dee0 Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a7c865dc88 Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/arvenscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..fdf8ff92d5 Binary files /dev/null and b/multisrc/overrides/mangathemesia/arvenscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/arvenscans/src/ArvenScans.kt b/multisrc/overrides/mangathemesia/arvenscans/src/ArvenScans.kt new file mode 100644 index 0000000000..3c3c5bcb58 --- /dev/null +++ b/multisrc/overrides/mangathemesia/arvenscans/src/ArvenScans.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.en.arvenscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +class ArvenScans : MangaThemesia("Arven Scans", "https://arvenscans.com", "en", "/series") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansEn.kt b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansEn.kt index c9b473d19f..9de7310af5 100644 --- a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansEn.kt +++ b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansEn.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -28,22 +29,28 @@ import java.util.concurrent.TimeUnit class AsuraScansEn : MangaThemesia( "Asura Scans", - "https://www.asurascans.com", + "https://asuratoon.com", "en", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US), ) { - private val preferences = Injekt.get().getSharedPreferences("source_$id", 0x0000) + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override val baseUrl by lazy { + preferences.baseUrlHost.let { "https://$it" } + } - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .addInterceptor(::urlChangeInterceptor) - .addInterceptor(uaIntercept) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(::domainChangeIntercept) .rateLimit(1, 3, TimeUnit.SECONDS) .build() override val seriesDescriptionSelector = "div.desc p, div.entry-content p, div[itemprop=description]:not(:has(p))" + override val seriesArtistSelector = ".fmed b:contains(artist)+span, .infox span:contains(artist)" + override val seriesAuthorSelector = ".fmed b:contains(author)+span, .infox span:contains(author)" override val pageSelector = "div.rdminimal > img, div.rdminimal > p > img, div.rdminimal > a > img, div.rdminimal > p > a > img, " + "div.rdminimal > noscript > img, div.rdminimal > p > noscript > img, div.rdminimal > a > noscript > img, div.rdminimal > p > a > noscript > img" @@ -61,6 +68,22 @@ class AsuraScansEn : MangaThemesia( return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded() } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val request = super.searchMangaRequest(page, query, filters) + if (query.isBlank()) return request + + val url = request.url.newBuilder() + .addPathSegment("page/$page/") + .removeAllQueryParameters("page") + .removeAllQueryParameters("title") + .addQueryParameter("s", query) + .build() + + return request.newBuilder() + .url(url) + .build() + } + // Temp Url for manga/chapter override fun fetchChapterList(manga: SManga): Observable> { val newManga = manga.titleToUrlFrag() @@ -80,7 +103,7 @@ class AsuraScansEn : MangaThemesia( .removeSuffix("/") .substringAfterLast("/") - val storedSlug = getSlugMap()[dbSlug] ?: dbSlug + val storedSlug = preferences.slugMap[dbSlug] ?: dbSlug return "$baseUrl$mangaUrlDirectory/$storedSlug/" } @@ -89,7 +112,7 @@ class AsuraScansEn : MangaThemesia( override fun pageListParse(document: Document): List { return document.select(pageSelector) .filterNot { it.attr("src").isNullOrEmpty() } - .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) } + .mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) } } override fun Element.imgAttr(): String = when { @@ -111,7 +134,7 @@ class AsuraScansEn : MangaThemesia( private fun SManga.tempUrlToPermIfNeeded(): SManga { if (!preferences.permaUrlPref) return this - val slugMap = getSlugMap().toMutableMap() + val slugMap = preferences.slugMap val sMangaTitleFirstWord = this.title.split(" ")[0] if (!this.url.contains("/$sMangaTitleFirstWord", ignoreCase = true)) { @@ -125,7 +148,7 @@ class AsuraScansEn : MangaThemesia( this.url = "$mangaUrlDirectory/$permaSlug/" } - putSlugMap(slugMap) + preferences.slugMap = slugMap return this } @@ -154,7 +177,7 @@ class AsuraScansEn : MangaThemesia( .removeSuffix("/") .substringAfterLast("/") - val slugMap = getSlugMap().toMutableMap() + val slugMap = preferences.slugMap val storedSlug = slugMap[dbSlug] ?: dbSlug @@ -171,7 +194,7 @@ class AsuraScansEn : MangaThemesia( ?: throw IOException("Migrate from Asura to Asura") slugMap[dbSlug] = newSlug - putSlugMap(slugMap) + preferences.slugMap = slugMap return chain.proceed( request.newBuilder() @@ -183,10 +206,12 @@ class AsuraScansEn : MangaThemesia( return response } - private fun getNewSlug(existingSlug: String, search: String): String? { + private fun getNewSlug(existingSlug: String, frag: String): String? { val permaSlug = existingSlug .replaceFirst(TEMP_TO_PERM_REGEX, "") + val search = frag.substringBefore("#") + val mangas = client.newCall(searchMangaRequest(1, search, FilterList())) .execute() .use { @@ -201,22 +226,6 @@ class AsuraScansEn : MangaThemesia( ?.substringAfterLast("/") } - private fun putSlugMap(slugMap: MutableMap) { - val serialized = json.encodeToString(slugMap) - - preferences.edit().putString(PREF_URL_MAP, serialized).commit() - } - - private fun getSlugMap(): Map { - val serialized = preferences.getString(PREF_URL_MAP, null) ?: return emptyMap() - - return try { - json.decodeFromString(serialized) - } catch (e: Exception) { - emptyMap() - } - } - private fun String.toSearchQuery(): String { return this.trim() .lowercase() @@ -224,6 +233,48 @@ class AsuraScansEn : MangaThemesia( .replace(trailingPlusRegex, "") } + private var lastDomain = "" + + private fun domainChangeIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (request.url.host !in listOf(preferences.baseUrlHost, lastDomain)) { + return chain.proceed(request) + } + + if (lastDomain.isNotEmpty()) { + val newUrl = request.url.newBuilder() + .host(preferences.baseUrlHost) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + + val response = chain.proceed(request) + + if (request.url.host == response.request.url.host) return response + + response.close() + + preferences.baseUrlHost = response.request.url.host + + lastDomain = request.url.host + + val newUrl = request.url.newBuilder() + .host(response.request.url.host) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + override fun setupPreferenceScreen(screen: PreferenceScreen) { SwitchPreferenceCompat(screen.context).apply { key = PREF_PERM_MANGA_URL_KEY_PREFIX + lang @@ -232,17 +283,40 @@ class AsuraScansEn : MangaThemesia( setDefaultValue(true) }.also(screen::addPreference) - addRandomAndCustomUserAgentPreferences(screen) + super.setupPreferenceScreen(screen) } private val SharedPreferences.permaUrlPref get() = getBoolean(PREF_PERM_MANGA_URL_KEY_PREFIX + lang, true) + private var SharedPreferences.slugMap: MutableMap + get() { + val serialized = getString(PREF_URL_MAP, null) ?: return mutableMapOf() + + return try { + json.decodeFromString(serialized) + } catch (e: Exception) { + mutableMapOf() + } + } + set(slugMap) { + val serialized = json.encodeToString(slugMap) + edit().putString(PREF_URL_MAP, serialized).commit() + } + + private var SharedPreferences.baseUrlHost + get() = getString(BASE_URL_PREF, defaultBaseUrlHost) ?: defaultBaseUrlHost + set(newHost) { + edit().putString(BASE_URL_PREF, newHost).commit() + } + companion object { private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_2_" private const val PREF_PERM_MANGA_URL_TITLE = "Permanent Manga URL" private const val PREF_PERM_MANGA_URL_SUMMARY = "Turns all manga urls into permanent ones." private const val PREF_URL_MAP = "pref_url_map" + private const val BASE_URL_PREF = "pref_base_url_host" + private const val defaultBaseUrlHost = "asuratoon.com" private val TEMP_TO_PERM_REGEX = Regex("""^\d+-""") private val titleSpecialCharactersRegex = Regex("""[^a-z0-9]+""") private val trailingPlusRegex = Regex("""\++$""") diff --git a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansTr.kt b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansTr.kt index 433569bf33..929af6c76e 100644 --- a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansTr.kt +++ b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansTr.kt @@ -15,14 +15,11 @@ import java.util.concurrent.TimeUnit class AsuraScansTr : MangaThemesia( "Asura Scans", - "https://asurascanstr.com", + "https://armoniscans.com", "tr", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr")), ) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(uaIntercept) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 3, TimeUnit.SECONDS) .build() @@ -53,7 +50,7 @@ class AsuraScansTr : MangaThemesia( val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");") val tsReader = json.decodeFromString(jsonString) val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() - return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) } } @Serializable diff --git a/multisrc/overrides/mangathemesia/beastscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..45a794664b Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3fe369faa3 Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..0ff76471d7 Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dbd6ebc111 Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cd8f8e96a7 Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/beastscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..b90ad2e820 Binary files /dev/null and b/multisrc/overrides/mangathemesia/beastscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/beastscans/src/BeastScans.kt b/multisrc/overrides/mangathemesia/beastscans/src/BeastScans.kt new file mode 100644 index 0000000000..8447dee413 --- /dev/null +++ b/multisrc/overrides/mangathemesia/beastscans/src/BeastScans.kt @@ -0,0 +1,31 @@ +package eu.kanade.tachiyomi.extension.ar.beastscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.SManga +import java.text.SimpleDateFormat +import java.util.Locale + +class BeastScans : MangaThemesia( + "Beast Scans", + "https://beastscans.net", + "ar", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), +) { + override val seriesArtistSelector = + ".infox .fmed:contains(الرسام) span, ${super.seriesArtistSelector}" + override val seriesAuthorSelector = + ".infox .fmed:contains(المؤلف) span, ${super.seriesAuthorSelector}" + override val seriesStatusSelector = + ".tsinfo .imptdt:contains(الحالة) i, ${super.seriesStatusSelector}" + override val seriesTypeSelector = + ".tsinfo .imptdt:contains(النوع) i, ${super.seriesTypeSelector}" + + override fun String?.parseStatus() = when { + this == null -> SManga.UNKNOWN + listOf("مستمر", "ongoing", "publishing").any { this.contains(it, ignoreCase = true) } -> SManga.ONGOING + listOf("متوقف", "hiatus").any { this.contains(it, ignoreCase = true) } -> SManga.ON_HIATUS + listOf("مكتمل", "completed").any { this.contains(it, ignoreCase = true) } -> SManga.COMPLETED + listOf("dropped", "cancelled").any { this.contains(it, ignoreCase = true) } -> SManga.CANCELLED + else -> SManga.UNKNOWN + } +} diff --git a/multisrc/overrides/mangathemesia/boosei/src/Boosei.kt b/multisrc/overrides/mangathemesia/boosei/src/Boosei.kt index 20a9a49895..33d0a61690 100644 --- a/multisrc/overrides/mangathemesia/boosei/src/Boosei.kt +++ b/multisrc/overrides/mangathemesia/boosei/src/Boosei.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.boosei import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class Boosei : MangaThemesia("Boosei", "https://boosei.net", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/clayrer/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c1c4b906df..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/clayrer/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index de64ce5990..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5d7ec338b3..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 33a3df730c..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6be55bbd06..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/clayrer/res/web_hi_res_512.png deleted file mode 100644 index 9ab90fa0d7..0000000000 Binary files a/multisrc/overrides/mangathemesia/clayrer/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/clayrer/src/Clayrer.kt b/multisrc/overrides/mangathemesia/clayrer/src/Clayrer.kt deleted file mode 100644 index 033cec99ec..0000000000 --- a/multisrc/overrides/mangathemesia/clayrer/src/Clayrer.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.clayrer - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import java.text.SimpleDateFormat -import java.util.Locale - -class Clayrer : MangaThemesia("Clayrer", "https://clayrer.net", "es", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("es"))) diff --git a/multisrc/overrides/mangathemesia/constellarscans/additional.gradle b/multisrc/overrides/mangathemesia/constellarscans/additional.gradle deleted file mode 100644 index 2505114b36..0000000000 --- a/multisrc/overrides/mangathemesia/constellarscans/additional.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation(project(':lib-dataimage')) -} \ No newline at end of file diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 37a749336d..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 910e4e5359..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a6e9204965..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 85c05d0670..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d9eaf09202..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/constellarscans/res/web_hi_res_512.png deleted file mode 100644 index a8dd214cc9..0000000000 Binary files a/multisrc/overrides/mangathemesia/constellarscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/constellarscans/src/ConstellarScans.kt b/multisrc/overrides/mangathemesia/constellarscans/src/ConstellarScans.kt deleted file mode 100644 index 7e96a214da..0000000000 --- a/multisrc/overrides/mangathemesia/constellarscans/src/ConstellarScans.kt +++ /dev/null @@ -1,157 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.constellarscans - -import android.annotation.SuppressLint -import android.app.Application -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.view.View -import android.webkit.ConsoleMessage -import android.webkit.JavascriptInterface -import android.webkit.WebChromeClient -import android.webkit.WebView -import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.Request -import org.jsoup.nodes.Document -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.concurrent.CountDownLatch - -class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarscans.com", "en") { - - override val client = super.client.newBuilder() - .addInterceptor(DataImageInterceptor()) - .rateLimit(1, 3) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") - .add("Accept-Language", "en-US,en;q=0.9") - .add("DNT", "1") - .add("User-Agent", mobileUserAgent) - .add("Upgrade-Insecure-Requests", "1") - - override val seriesStatusSelector = ".status" - - private val mobileUserAgent by lazy { - val req = GET(UA_DB_URL) - val data = client.newCall(req).execute().body.use { - json.parseToJsonElement(it.string()).jsonArray - }.mapNotNull { - it.jsonObject["user-agent"]?.jsonPrimitive?.content?.takeIf { ua -> - ua.startsWith("Mozilla/5.0") && - ( - ua.contains("iPhone") && - (ua.contains("FxiOS") || ua.contains("CriOS")) || - ua.contains("Android") && - (ua.contains("EdgA") || ua.contains("Chrome") || ua.contains("Firefox")) - ) - } - } - data.random() - } - - override fun pageListRequest(chapter: SChapter): Request = - super.pageListRequest(chapter).newBuilder() - .header( - "Accept", - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", - ) - .header("Sec-Fetch-Site", "same-origin") - .header("Sec-Fetch-Mode", "navigate") - .header("Sec-Fetch-Dest", "document") - .header("Sec-Fetch-User", "?1") - .cacheControl(CacheControl.FORCE_NETWORK) - .build() - - internal class JsObject(val imageList: MutableList = mutableListOf()) { - @JavascriptInterface - fun passSingleImage(url: String) { - Log.d("constellarscans", "received image: $url") - imageList.add(url) - } - } - - private fun randomString(length: Int = 10): String { - val charPool = ('a'..'z') + ('A'..'Z') - return List(length) { charPool.random() }.joinToString("") - } - - private val funkyScript by lazy { - client.newCall(GET(FUNKY_SCRIPT_URL)).execute().body.string() - } - - @SuppressLint("SetJavaScriptEnabled") - override fun pageListParse(document: Document): List { - val interfaceName = randomString() - document.body().prepend("") - - val handler = Handler(Looper.getMainLooper()) - val latch = CountDownLatch(1) - val jsInterface = JsObject() - var webView: WebView? = null - handler.post { - val webview = WebView(Injekt.get()) - webView = webview - webview.settings.javaScriptEnabled = true - webview.settings.domStorageEnabled = true - webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - webview.settings.useWideViewPort = false - webview.settings.loadWithOverviewMode = false - webview.settings.userAgentString = mobileUserAgent - webview.addJavascriptInterface(jsInterface, interfaceName) - - webview.webChromeClient = object : WebChromeClient() { - override fun onProgressChanged(view: WebView?, newProgress: Int) { - if (newProgress == 100) { - latch.countDown() - } - } - - override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { - if (consoleMessage == null) { return false } - val logContent = "wv: ${consoleMessage.message()} ${consoleMessage.sourceId()}, line ${consoleMessage.lineNumber()}" - when (consoleMessage.messageLevel()) { - ConsoleMessage.MessageLevel.DEBUG -> Log.d("constellarscans", logContent) - ConsoleMessage.MessageLevel.ERROR -> Log.e("constellarscans", logContent) - ConsoleMessage.MessageLevel.LOG -> Log.i("constellarscans", logContent) - ConsoleMessage.MessageLevel.TIP -> Log.i("constellarscans", logContent) - ConsoleMessage.MessageLevel.WARNING -> Log.w("constellarscans", logContent) - else -> Log.d("constellarscans", logContent) - } - - return true - } - } - Log.d("constellarscans", "starting webview shenanigans") - webview.loadDataWithBaseURL(baseUrl, document.toString(), "text/html", "UTF-8", null) - } - - latch.await() - handler.post { webView?.destroy() } - return jsInterface.imageList.mapIndexed { idx, it -> Page(idx, imageUrl = it) } - } - - override fun imageRequest(page: Page): Request = super.imageRequest(page).newBuilder() - .header("Accept", "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8") - .header("Sec-Fetch-Dest", "image") - .header("Sec-Fetch-Mode", "no-cors") - .header("Sec-Fetch-Site", "same-origin") - .build() - - companion object { - const val UA_DB_URL = - "https://cdn.jsdelivr.net/gh/mimmi20/browscap-helper@30a83c095688f40b9eaca0165a479c661e5a7fbe/tests/0002999.json" - val FUNKY_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/@beerpsi/funky-script@latest/constellar.js" - } -} diff --git a/multisrc/overrides/mangathemesia/cosmicscans/src/CosmicScans.kt b/multisrc/overrides/mangathemesia/cosmicscans/src/CosmicScans.kt index f4c8868487..b56b4e6b6e 100644 --- a/multisrc/overrides/mangathemesia/cosmicscans/src/CosmicScans.kt +++ b/multisrc/overrides/mangathemesia/cosmicscans/src/CosmicScans.kt @@ -2,6 +2,6 @@ package eu.kanade.tachiyomi.extension.en.cosmicscans import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -class CosmicScans : MangaThemesia("Cosmic Scans", "https://cosmicscans.com", "en") { +class CosmicScans : MangaThemesia("Cosmic Scans", "https://cosmic-scans.com", "en") { override val pageSelector = "div#readerarea img[data-src]" } diff --git a/multisrc/overrides/mangathemesia/cosmicscansid/src/CosmicScansID.kt b/multisrc/overrides/mangathemesia/cosmicscansid/src/CosmicScansID.kt index 26a5a578cd..4df60eb891 100644 --- a/multisrc/overrides/mangathemesia/cosmicscansid/src/CosmicScansID.kt +++ b/multisrc/overrides/mangathemesia/cosmicscansid/src/CosmicScansID.kt @@ -1,8 +1,61 @@ package eu.kanade.tachiyomi.extension.id.cosmicscansid import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import java.util.concurrent.TimeUnit -class CosmicScansID : MangaThemesia("CosmicScans.id", "https://cosmicscans.id", "id") { +class CosmicScansID : MangaThemesia("CosmicScans.id", "https://cosmicscans.id", "id", "/semua-komik") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4, TimeUnit.SECONDS) + .build() override val hasProjectPage = true + override val projectPageString = "/semua-komik" + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl" + if (page > 1) "/page/$page" else "", headers) + + // search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegments("page/$page/") + .addQueryParameter("s", query) + + filters.forEach { filter -> + when (filter) { + // if site has project page, default value "hasProjectPage" = false + is ProjectFilter -> { + if (filter.selectedValue() == "project-filter-on") { + url.setPathSegment(0, projectPageString.substring(1)) + } + } + else -> { /* Do Nothing */ } + } + } + return GET(url.toString()) + } + + override fun searchMangaSelector() = ".bixbox:not(.hothome):has(.hpage) .utao .uta .imgu, .bixbox:not(.hothome) .listupd .bs .bsx" + + override fun getFilterList(): FilterList { + val filters = mutableListOf>( + Filter.Separator(), + Filter.Header("$name Project List page"), + ProjectFilter(), + OrderByFilter(), + ) + return FilterList(filters) + } + + // manga details + override val seriesDescriptionSelector = ".entry-content[itemprop=description] :not(a,p:has(a))" + + // pages + override val pageSelector = "div#readerarea img:not(noscript img)" } diff --git a/multisrc/overrides/mangathemesia/default/AndroidManifest.xml b/multisrc/overrides/mangathemesia/default/AndroidManifest.xml index ded43d296e..3db1038222 100644 --- a/multisrc/overrides/mangathemesia/default/AndroidManifest.xml +++ b/multisrc/overrides/mangathemesia/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - \ No newline at end of file + diff --git a/multisrc/overrides/mangathemesia/default/additional.gradle b/multisrc/overrides/mangathemesia/default/additional.gradle new file mode 100644 index 0000000000..57807a7d07 --- /dev/null +++ b/multisrc/overrides/mangathemesia/default/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation(project(":lib-randomua")) +} diff --git a/multisrc/overrides/mangathemesia/diskusscan/src/DiskusScan.kt b/multisrc/overrides/mangathemesia/diskusscan/src/DiskusScan.kt index bb45ae6281..17bf4b5c03 100644 --- a/multisrc/overrides/mangathemesia/diskusscan/src/DiskusScan.kt +++ b/multisrc/overrides/mangathemesia/diskusscan/src/DiskusScan.kt @@ -1,8 +1,13 @@ package eu.kanade.tachiyomi.extension.pt.diskusscan import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Headers import okhttp3.OkHttpClient +import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -18,6 +23,38 @@ class DiskusScan : MangaThemesia( override val versionId = 2 override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) + .rateLimit(2, 1, TimeUnit.SECONDS) .build() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") + .set("Accept-Language", "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5") + .set("Dnt", "1") + .set("Sec-Fetch-Dest", "document") + .set("Sec-Fetch-Mode", "navigate") + .set("Sec-Fetch-Site", "same-origin") + .set("Sec-Fetch-User", "?1") + + override fun mangaDetailsRequest(manga: SManga): Request { + val newHeaders = headersBuilder() + .set("Referer", baseUrl + mangaUrlDirectory) + .build() + + return GET(baseUrl + manga.url, newHeaders) + } + + override val seriesAuthorSelector = ".infotable tr:contains(Autor) td:last-child" + override val seriesDescriptionSelector = ".entry-content[itemprop=description] > *:not([class^=disku])" + + override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) + + override fun imageUrlRequest(page: Page): Request { + val newHeaders = super.imageUrlRequest(page).headers.newBuilder() + .set("Sec-Fetch-Dest", "image") + .set("Sec-Fetch-Mode", "no-cors") + .set("Sec-Fetch-Site", "cross-site") + .build() + + return GET(page.imageUrl!!, newHeaders) + } } diff --git a/multisrc/overrides/mangathemesia/dojingnet/src/DojingNet.kt b/multisrc/overrides/mangathemesia/dojingnet/src/DojingNet.kt index bd326bbf52..60e0003152 100644 --- a/multisrc/overrides/mangathemesia/dojingnet/src/DojingNet.kt +++ b/multisrc/overrides/mangathemesia/dojingnet/src/DojingNet.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.dojingnet import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class DojingNet : MangaThemesia("Dojing.net", "https://dojing.net", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 144f3530d7..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d30f74496d..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7e25b185ab..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 70a89d4607..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d4ec8796b1..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/dragontranslation/res/web_hi_res_512.png deleted file mode 100644 index b39ae07d2e..0000000000 Binary files a/multisrc/overrides/mangathemesia/dragontranslation/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/dragontranslation/src/DragonTranslation.kt b/multisrc/overrides/mangathemesia/dragontranslation/src/DragonTranslation.kt deleted file mode 100644 index 1f80427371..0000000000 --- a/multisrc/overrides/mangathemesia/dragontranslation/src/DragonTranslation.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.dragontranslation - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import java.text.SimpleDateFormat -import java.util.Locale - -class DragonTranslation : MangaThemesia( - "DragonTranslation", - "https://dragontranslation.com", - "es", - mangaUrlDirectory = "/manga", - dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("en")), -) diff --git a/multisrc/overrides/mangathemesia/duniakomikid/src/DuniaKomikId.kt b/multisrc/overrides/mangathemesia/duniakomikid/src/DuniaKomikId.kt index 24352c5cf3..1c1bc531a5 100644 --- a/multisrc/overrides/mangathemesia/duniakomikid/src/DuniaKomikId.kt +++ b/multisrc/overrides/mangathemesia/duniakomikid/src/DuniaKomikId.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.extension.id.duniakomikid import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale -class DuniaKomikId : MangaThemesia("DuniaKomik.id", "https://duniakomik.id", "id") { +class DuniaKomikId : MangaThemesia("DuniaKomik.id", "https://duniakomik.org", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id", "ID"))) { override val hasProjectPage = true } diff --git a/multisrc/overrides/mangathemesia/elarcpage/src/ElarcPage.kt b/multisrc/overrides/mangathemesia/elarcpage/src/ElarcPage.kt index 18cfa12e14..192a8be3c5 100644 --- a/multisrc/overrides/mangathemesia/elarcpage/src/ElarcPage.kt +++ b/multisrc/overrides/mangathemesia/elarcpage/src/ElarcPage.kt @@ -2,4 +2,11 @@ package eu.kanade.tachiyomi.extension.en.elarcpage import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -class ElarcPage : MangaThemesia("ElarcPage", "https://elarcpage.com", "en", "/series") +class ElarcPage : MangaThemesia( + "Elarc Reader", + "https://elarcreader.com", + "en", + "/series", +) { + override val id = 5482125641807211052 +} diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..47370da2ff Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..e7091505f2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..ca9fb349ba Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f1fc6417ea Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e2ab65b8cb Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/enryumanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..e966355906 Binary files /dev/null and b/multisrc/overrides/mangathemesia/enryumanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/enryumanga/src/EnryuManga.kt b/multisrc/overrides/mangathemesia/enryumanga/src/EnryuManga.kt new file mode 100644 index 0000000000..d43a904961 --- /dev/null +++ b/multisrc/overrides/mangathemesia/enryumanga/src/EnryuManga.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.extension.en.enryumanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia + +class EnryuManga : MangaThemesia("EnryuManga", "https://enryumanga.com", "en") diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..71ff37e5aa Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..347eeb7378 Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..69834f77a3 Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b462e985ff Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5eac76645d Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/evilproduction/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/evilproduction/res/web_hi_res_512.png new file mode 100644 index 0000000000..8b820b2f75 Binary files /dev/null and b/multisrc/overrides/mangathemesia/evilproduction/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/flamescans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/flamecomics/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/flamecomics/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/flamescans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/flamecomics/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/flamecomics/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/flamescans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/flamescans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/flamescans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/flamecomics/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mangathemesia/flamescans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/flamecomics/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mangathemesia/flamescans/res/web_hi_res_512.png rename to multisrc/overrides/mangathemesia/flamecomics/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangathemesia/flamecomics/src/FlameComics.kt b/multisrc/overrides/mangathemesia/flamecomics/src/FlameComics.kt new file mode 100644 index 0000000000..8e7fe96e66 --- /dev/null +++ b/multisrc/overrides/mangathemesia/flamecomics/src/FlameComics.kt @@ -0,0 +1,240 @@ +package eu.kanade.tachiyomi.extension.en.flamecomics + +import android.app.Application +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Rect +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.jsoup.nodes.Document +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.ByteArrayOutputStream + +class FlameComics : MangaThemesia( + "Flame Comics", + "https://flamecomics.com", + "en", + mangaUrlDirectory = "/series", +) { + + // Flame Scans -> Flame Comics + override val id = 6350607071566689772 + + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override val client = super.client.newBuilder() + .rateLimit(2, 7) + .addInterceptor(::composedImageIntercept) + .build() + + // Split Image Fixer Start + private val composedSelector: String = "#readerarea div.figure_container div.composed_figure" + + override fun pageListParse(document: Document): List { + val hasSplitImages = document + .select(composedSelector) + .firstOrNull() != null + + if (!hasSplitImages) { + return super.pageListParse(document) + } + + return document.select("#readerarea p:has(img), $composedSelector").toList() + .filter { + it.select("img").all { imgEl -> + imgEl.attr("abs:src").isNullOrEmpty().not() + } + } + .mapIndexed { i, el -> + if (el.tagName() == "p") { + Page(i, "", el.select("img").attr("abs:src")) + } else { + val imageUrls = el.select("img") + .joinToString("|") { it.attr("abs:src") } + + Page(i, document.location(), imageUrls + COMPOSED_SUFFIX) + } + } + } + + private fun composedImageIntercept(chain: Interceptor.Chain): Response { + if (!chain.request().url.toString().endsWith(COMPOSED_SUFFIX)) { + return chain.proceed(chain.request()) + } + + val imageUrls = chain.request().url.toString() + .removeSuffix(COMPOSED_SUFFIX) + .split("%7C") + + var width = 0 + var height = 0 + + val imageBitmaps = imageUrls.map { imageUrl -> + val request = chain.request().newBuilder().url(imageUrl).build() + val response = chain.proceed(request) + + val bitmap = BitmapFactory.decodeStream(response.body.byteStream()) + + width += bitmap.width + height = bitmap.height + + bitmap + } + + val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(result) + + var left = 0 + + imageBitmaps.forEach { bitmap -> + val srcRect = Rect(0, 0, bitmap.width, bitmap.height) + val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) + + canvas.drawBitmap(bitmap, srcRect, dstRect, null) + + left += bitmap.width + } + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.PNG, 100, output) + + val responseBody = output.toByteArray().toResponseBody(MEDIA_TYPE) + + return Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .request(chain.request()) + .message("OK") + .body(responseBody) + .build() + } + // Split Image Fixer End + + // Permanent Url start + override fun fetchPopularManga(page: Int): Observable { + return super.fetchPopularManga(page).tempUrlToPermIfNeeded() + } + + override fun fetchLatestUpdates(page: Int): Observable { + return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded() + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded() + } + + private fun Observable.tempUrlToPermIfNeeded(): Observable { + return this.map { mangasPage -> + MangasPage( + mangasPage.mangas.map { it.tempUrlToPermIfNeeded() }, + mangasPage.hasNextPage, + ) + } + } + + private fun SManga.tempUrlToPermIfNeeded(): SManga { + val turnTempUrlToPerm = preferences.getBoolean(getPermanentMangaUrlPreferenceKey(), true) + if (!turnTempUrlToPerm) return this + + val path = this.url.removePrefix("/").removeSuffix("/").split("/") + path.lastOrNull()?.let { slug -> this.url = "$mangaUrlDirectory/${deobfuscateSlug(slug)}/" } + + return this + } + + override fun fetchChapterList(manga: SManga) = super.fetchChapterList(manga.tempUrlToPermIfNeeded()) + .map { sChapterList -> sChapterList.map { it.tempUrlToPermIfNeeded() } } + + private fun SChapter.tempUrlToPermIfNeeded(): SChapter { + val turnTempUrlToPerm = preferences.getBoolean(getPermanentChapterUrlPreferenceKey(), true) + if (!turnTempUrlToPerm) return this + + val path = this.url.removePrefix("/").removeSuffix("/").split("/") + path.lastOrNull()?.let { slug -> this.url = "/${deobfuscateSlug(slug)}/" } + return this + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val permanentMangaUrlPref = SwitchPreferenceCompat(screen.context).apply { + key = getPermanentMangaUrlPreferenceKey() + title = PREF_PERM_MANGA_URL_TITLE + summary = PREF_PERM_MANGA_URL_SUMMARY + setDefaultValue(true) + + setOnPreferenceChangeListener { _, newValue -> + val checkValue = newValue as Boolean + preferences.edit() + .putBoolean(getPermanentMangaUrlPreferenceKey(), checkValue) + .commit() + } + } + val permanentChapterUrlPref = SwitchPreferenceCompat(screen.context).apply { + key = getPermanentChapterUrlPreferenceKey() + title = PREF_PERM_CHAPTER_URL_TITLE + summary = PREF_PERM_CHAPTER_URL_SUMMARY + setDefaultValue(true) + + setOnPreferenceChangeListener { _, newValue -> + val checkValue = newValue as Boolean + preferences.edit() + .putBoolean(getPermanentChapterUrlPreferenceKey(), checkValue) + .commit() + } + } + screen.addPreference(permanentMangaUrlPref) + screen.addPreference(permanentChapterUrlPref) + } + + private fun getPermanentMangaUrlPreferenceKey(): String { + return PREF_PERM_MANGA_URL_KEY_PREFIX + lang + } + + private fun getPermanentChapterUrlPreferenceKey(): String { + return PREF_PERM_CHAPTER_URL_KEY_PREFIX + lang + } + // Permanent Url for Manga/Chapter End + + companion object { + private const val COMPOSED_SUFFIX = "?comp" + + private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_" + private const val PREF_PERM_MANGA_URL_TITLE = "Permanent Manga URL" + private const val PREF_PERM_MANGA_URL_SUMMARY = "Turns all manga urls into permanent ones." + + private const val PREF_PERM_CHAPTER_URL_KEY_PREFIX = "pref_permanent_chapter_url" + private const val PREF_PERM_CHAPTER_URL_TITLE = "Permanent Chapter URL" + private const val PREF_PERM_CHAPTER_URL_SUMMARY = "Turns all chapter urls into permanent ones." + + /** + * + * De-obfuscates the slug of a series or chapter to the permanent slug + * * For a series: "12345678-this-is-a-series" -> "this-is-a-series" + * * For a chapter: "12345678-this-is-a-series-chapter-1" -> "this-is-a-series-chapter-1" + * + * @param obfuscated_slug the obfuscated slug of a series or chapter + * + * @return + */ + private fun deobfuscateSlug(obfuscated_slug: String) = obfuscated_slug + .replaceFirst(Regex("""^\d+-"""), "") + + private val MEDIA_TYPE = "image/png".toMediaType() + } +} diff --git a/multisrc/overrides/mangathemesia/flamescans/src/FlameScans.kt b/multisrc/overrides/mangathemesia/flamescans/src/FlameScans.kt deleted file mode 100644 index a36616de87..0000000000 --- a/multisrc/overrides/mangathemesia/flamescans/src/FlameScans.kt +++ /dev/null @@ -1,250 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.flamescans - -import android.app.Application -import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreferenceCompat -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import org.jsoup.nodes.Document -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -open class FlameScans( - override val baseUrl: String, - override val lang: String, - mangaUrlDirectory: String, - dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US), -) : MangaThemesia( - "Flame Scans", - baseUrl, - lang, - mangaUrlDirectory = mangaUrlDirectory, - dateFormat = dateFormat, -), - ConfigurableSource { - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(::composedImageIntercept) - .build() - - // Split Image Fixer Start - private val composedSelector: String = "#readerarea div.figure_container div.composed_figure" - - override fun pageListParse(document: Document): List { - val hasSplitImages = document - .select(composedSelector) - .firstOrNull() != null - - if (!hasSplitImages) { - return super.pageListParse(document) - } - - return document.select("#readerarea p:has(img), $composedSelector").toList() - .filter { - it.select("img").all { imgEl -> - imgEl.attr("abs:src").isNullOrEmpty().not() - } - } - .mapIndexed { i, el -> - if (el.tagName() == "p") { - Page(i, "", el.select("img").attr("abs:src")) - } else { - val imageUrls = el.select("img") - .joinToString("|") { it.attr("abs:src") } - - Page(i, "", imageUrls + COMPOSED_SUFFIX) - } - } - } - - private fun composedImageIntercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(COMPOSED_SUFFIX)) { - return chain.proceed(chain.request()) - } - - val imageUrls = chain.request().url.toString() - .removeSuffix(COMPOSED_SUFFIX) - .split("%7C") - - var width = 0 - var height = 0 - - val imageBitmaps = imageUrls.map { imageUrl -> - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body.byteStream()) - - width += bitmap.width - height = bitmap.height - - bitmap - } - - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - var left = 0 - - imageBitmaps.forEach { bitmap -> - val srcRect = Rect(0, 0, bitmap.width, bitmap.height) - val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) - - canvas.drawBitmap(bitmap, srcRect, dstRect, null) - - left += bitmap.width - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - - val responseBody = output.toByteArray().toResponseBody(MEDIA_TYPE) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() - } - // Split Image Fixer End - - // Permanent Url start - override fun fetchPopularManga(page: Int): Observable { - return super.fetchPopularManga(page).tempUrlToPermIfNeeded() - } - - override fun fetchLatestUpdates(page: Int): Observable { - return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded() - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded() - } - - private fun Observable.tempUrlToPermIfNeeded(): Observable { - return this.map { mangasPage -> - MangasPage( - mangasPage.mangas.map { it.tempUrlToPermIfNeeded() }, - mangasPage.hasNextPage, - ) - } - } - - private fun SManga.tempUrlToPermIfNeeded(): SManga { - val turnTempUrlToPerm = preferences.getBoolean(getPermanentMangaUrlPreferenceKey(), true) - if (!turnTempUrlToPerm) return this - - val path = this.url.removePrefix("/").removeSuffix("/").split("/") - path.lastOrNull()?.let { slug -> this.url = "$mangaUrlDirectory/${deobfuscateSlug(slug)}/" } - - return this - } - - override fun fetchChapterList(manga: SManga) = super.fetchChapterList(manga.tempUrlToPermIfNeeded()) - .map { sChapterList -> sChapterList.map { it.tempUrlToPermIfNeeded() } } - - private fun SChapter.tempUrlToPermIfNeeded(): SChapter { - val turnTempUrlToPerm = preferences.getBoolean(getPermanentChapterUrlPreferenceKey(), true) - if (!turnTempUrlToPerm) return this - - val path = this.url.removePrefix("/").removeSuffix("/").split("/") - path.lastOrNull()?.let { slug -> this.url = "/${deobfuscateSlug(slug)}/" } - return this - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val permanentMangaUrlPref = SwitchPreferenceCompat(screen.context).apply { - key = getPermanentMangaUrlPreferenceKey() - title = PREF_PERM_MANGA_URL_TITLE - summary = PREF_PERM_MANGA_URL_SUMMARY - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit() - .putBoolean(getPermanentMangaUrlPreferenceKey(), checkValue) - .commit() - } - } - val permanentChapterUrlPref = SwitchPreferenceCompat(screen.context).apply { - key = getPermanentChapterUrlPreferenceKey() - title = PREF_PERM_CHAPTER_URL_TITLE - summary = PREF_PERM_CHAPTER_URL_SUMMARY - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit() - .putBoolean(getPermanentChapterUrlPreferenceKey(), checkValue) - .commit() - } - } - screen.addPreference(permanentMangaUrlPref) - screen.addPreference(permanentChapterUrlPref) - } - - private fun getPermanentMangaUrlPreferenceKey(): String { - return PREF_PERM_MANGA_URL_KEY_PREFIX + lang - } - - private fun getPermanentChapterUrlPreferenceKey(): String { - return PREF_PERM_CHAPTER_URL_KEY_PREFIX + lang - } - // Permanent Url for Manga/Chapter End - - companion object { - private const val COMPOSED_SUFFIX = "?comp" - - private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_" - private const val PREF_PERM_MANGA_URL_TITLE = "Permanent Manga URL" - private const val PREF_PERM_MANGA_URL_SUMMARY = "Turns all manga urls into permanent ones." - - private const val PREF_PERM_CHAPTER_URL_KEY_PREFIX = "pref_permanent_chapter_url" - private const val PREF_PERM_CHAPTER_URL_TITLE = "Permanent Chapter URL" - private const val PREF_PERM_CHAPTER_URL_SUMMARY = "Turns all chapter urls into permanent ones." - - /** - * - * De-obfuscates the slug of a series or chapter to the permanent slug - * * For a series: "12345678-this-is-a-series" -> "this-is-a-series" - * * For a chapter: "12345678-this-is-a-series-chapter-1" -> "this-is-a-series-chapter-1" - * - * @param obfuscated_slug the obfuscated slug of a series or chapter - * - * @return - */ - private fun deobfuscateSlug(obfuscated_slug: String) = obfuscated_slug - .replaceFirst(Regex("""^\d+-"""), "") - - private val MEDIA_TYPE = "image/png".toMediaType() - } -} diff --git a/multisrc/overrides/mangathemesia/flamescans/src/FlameScansFactory.kt b/multisrc/overrides/mangathemesia/flamescans/src/FlameScansFactory.kt deleted file mode 100644 index a617de108e..0000000000 --- a/multisrc/overrides/mangathemesia/flamescans/src/FlameScansFactory.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.flamescans - -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.SourceFactory -import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit - -class FlameScansFactory : SourceFactory { - override fun createSources() = listOf( - FlameScansEn(), - ) -} - -class FlameScansEn : FlameScans("https://flamescans.org", "en", "/series") { - override val client: OkHttpClient = super.client.newBuilder() - .rateLimit(2, 7, TimeUnit.SECONDS) - .build() -} diff --git a/multisrc/overrides/mangathemesia/franxxmangas/src/FranxxMangas.kt b/multisrc/overrides/mangathemesia/franxxmangas/src/FranxxMangas.kt index 1c5733419b..1baec64a98 100644 --- a/multisrc/overrides/mangathemesia/franxxmangas/src/FranxxMangas.kt +++ b/multisrc/overrides/mangathemesia/franxxmangas/src/FranxxMangas.kt @@ -14,7 +14,7 @@ class FranxxMangas : MangaThemesia( dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), ) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .build() diff --git a/multisrc/overrides/mangathemesia/freakscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ec9aa71ea4 Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/freakscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..785df315da Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e4dafca7c6 Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9438d6cc8b Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9d2308118f Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/freakscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/freakscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..910ae2e466 Binary files /dev/null and b/multisrc/overrides/mangathemesia/freakscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..6ddc46b2c4 Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..40f1e4c67a Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..50b0015b26 Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ae4d9b0686 Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c40afe39dc Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/gloryscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..0a055b362a Binary files /dev/null and b/multisrc/overrides/mangathemesia/gloryscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/gloryscans/src/GloryScans.kt b/multisrc/overrides/mangathemesia/gloryscans/src/GloryScans.kt new file mode 100644 index 0000000000..68341a68bd --- /dev/null +++ b/multisrc/overrides/mangathemesia/gloryscans/src/GloryScans.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.fr.gloryscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class GloryScans : MangaThemesia("Glory Scans", "https://gloryscans.fr", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRENCH)) diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..c062736329 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..bae7699182 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..a8b079d0cf Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ca514203cd Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a032912972 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/inarimanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..98d3d08fc8 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inarimanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/inarimanga/src/InariManga.kt b/multisrc/overrides/mangathemesia/inarimanga/src/InariManga.kt new file mode 100644 index 0000000000..87be16d3de --- /dev/null +++ b/multisrc/overrides/mangathemesia/inarimanga/src/InariManga.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.es.inarimanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import okhttp3.HttpUrl.Companion.toHttpUrl +import java.text.SimpleDateFormat +import java.util.Locale + +class InariManga : MangaThemesia( + "InariManga", + "https://inarimanga.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("en")), +) { + + // Site moved from Madara to MangaThemesia + override val versionId = 2 + + override val client = super.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 4, 1) + .build() +} diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ed114ef136 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..cf25681544 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c9a90ee0ed Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bc84ea60e7 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..227934af6c Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/inazumanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..371275e294 Binary files /dev/null and b/multisrc/overrides/mangathemesia/inazumanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/inazumanga/src/YumeKomik.kt b/multisrc/overrides/mangathemesia/inazumanga/src/YumeKomik.kt new file mode 100644 index 0000000000..6e73a3c4ca --- /dev/null +++ b/multisrc/overrides/mangathemesia/inazumanga/src/YumeKomik.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.id.inazumanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class YumeKomik : MangaThemesia("YumeKomik", "https://yumekomik.com", "id") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(3) + .build() + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7fc58c5b0d Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8c71a27bfb Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..24f6d60433 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b55f3f3413 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..16a1f8ec19 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/kaiscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..b869c62d60 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kaiscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/kaiscans/src/KaiScans.kt b/multisrc/overrides/mangathemesia/kaiscans/src/KaiScans.kt new file mode 100644 index 0000000000..a79d6c9176 --- /dev/null +++ b/multisrc/overrides/mangathemesia/kaiscans/src/KaiScans.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.extension.en.kaiscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia + +class KaiScans : MangaThemesia("Kai Scans", "https://www.kaiscans.com", "en", mangaUrlDirectory = "/series") diff --git a/multisrc/overrides/mangathemesia/kanzenin/src/Kanzenin.kt b/multisrc/overrides/mangathemesia/kanzenin/src/Kanzenin.kt new file mode 100644 index 0000000000..5371524dbe --- /dev/null +++ b/multisrc/overrides/mangathemesia/kanzenin/src/Kanzenin.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.id.kanzenin + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class Kanzenin : MangaThemesia( + "Kanzenin", + "https://kanzenin.info", + "id", + dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("id")), +) diff --git a/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ab3cf45972 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..05f996bdc2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e4c5b10da1 Binary files /dev/null and b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fa79d12c9d Binary files /dev/null and b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..883586188e Binary files /dev/null and b/multisrc/overrides/mangathemesia/kingofshojo/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/kingofshojo/src/KingofShojo.kt b/multisrc/overrides/mangathemesia/kingofshojo/src/KingofShojo.kt new file mode 100644 index 0000000000..9f4696ecf5 --- /dev/null +++ b/multisrc/overrides/mangathemesia/kingofshojo/src/KingofShojo.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.ar.kingofshojo + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class KingofShojo : MangaThemesia("King of Shojo", "https://kingofshojo.com", "ar", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) { + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/kiryuu/src/Kiryuu.kt b/multisrc/overrides/mangathemesia/kiryuu/src/Kiryuu.kt index 74d2c613a4..1d5df8691d 100644 --- a/multisrc/overrides/mangathemesia/kiryuu/src/Kiryuu.kt +++ b/multisrc/overrides/mangathemesia/kiryuu/src/Kiryuu.kt @@ -6,15 +6,12 @@ import okhttp3.OkHttpClient import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class Kiryuu : MangaThemesia("Kiryuu", "https://kiryuu.id", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id"))) { // Formerly "Kiryuu (WP Manga Stream)" override val id = 3639673976007021338 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/komikav/src/KomikAV.kt b/multisrc/overrides/mangathemesia/komikav/src/KomikAV.kt index c2c5770367..1802c33989 100644 --- a/multisrc/overrides/mangathemesia/komikav/src/KomikAV.kt +++ b/multisrc/overrides/mangathemesia/komikav/src/KomikAV.kt @@ -8,7 +8,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class KomikAV : MangaThemesia( "Komik AV (WP Manga Stream)", @@ -19,9 +18,7 @@ class KomikAV : MangaThemesia( // Formerly "Komik AV (WP Manga Stream)" override val id = 7875815514004535629 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/komikcast/src/KomikCast.kt b/multisrc/overrides/mangathemesia/komikcast/src/KomikCast.kt index 7a0fbdd217..27c080a6f9 100644 --- a/multisrc/overrides/mangathemesia/komikcast/src/KomikCast.kt +++ b/multisrc/overrides/mangathemesia/komikcast/src/KomikCast.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import okhttp3.Headers @@ -17,27 +18,20 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import java.util.Calendar -import java.util.concurrent.TimeUnit - -class KomikCast : MangaThemesia( - "Komik Cast", - baseUrl = "https://komikcast.io", - "id", - mangaUrlDirectory = "/daftar-komik", -) { +import java.util.Locale + +class KomikCast : MangaThemesia("Komik Cast", "https://komikcast.lol", "id", "/daftar-komik") { + // Formerly "Komik Cast (WP Manga Stream)" override val id = 972717448578983812 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(3) .build() - override fun headersBuilder(): Headers.Builder = Headers.Builder() + override fun headersBuilder(): Headers.Builder = super.headersBuilder() .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") .add("Accept-language", "en-US,en;q=0.9,id;q=0.8") - .add("Referer", baseUrl) override fun imageRequest(page: Page): Request { val newHeaders = headersBuilder() @@ -71,12 +65,43 @@ class KomikCast : MangaThemesia( override val seriesThumbnailSelector = ".komik_info-content-thumbnail img" override val seriesStatusSelector = ".komik_info-content-info:contains(Status)" + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + document.selectFirst(seriesDetailsSelector)?.let { seriesDetails -> + title = seriesDetails.selectFirst(seriesTitleSelector)?.text() + ?.replace("bahasa indonesia", "", ignoreCase = true)?.trim().orEmpty() + artist = seriesDetails.selectFirst(seriesArtistSelector)?.ownText().removeEmptyPlaceholder() + author = seriesDetails.selectFirst(seriesAuthorSelector)?.ownText().removeEmptyPlaceholder() + description = seriesDetails.select(seriesDescriptionSelector).joinToString("\n") { it.text() }.trim() + // Add alternative name to manga description + val altName = seriesDetails.selectFirst(seriesAltNameSelector)?.ownText().takeIf { it.isNullOrBlank().not() } + altName?.let { + description = "$description\n\n$altNamePrefix$altName".trim() + } + val genres = seriesDetails.select(seriesGenreSelector).map { it.text() }.toMutableList() + // Add series type (manga/manhwa/manhua/other) to genre + seriesDetails.selectFirst(seriesTypeSelector)?.ownText().takeIf { it.isNullOrBlank().not() }?.let { genres.add(it) } + genre = genres.map { genre -> + genre.lowercase(Locale.forLanguageTag(lang)).replaceFirstChar { char -> + if (char.isLowerCase()) { + char.titlecase(Locale.forLanguageTag(lang)) + } else { + char.toString() + } + } + } + .joinToString { it.trim() } + + status = seriesDetails.selectFirst(seriesStatusSelector)?.text().parseStatus() + thumbnail_url = seriesDetails.select(seriesThumbnailSelector).imgAttr() + } + } + override fun chapterListSelector() = "div.komik_info-chapters li" override fun chapterFromElement(element: Element) = SChapter.create().apply { val urlElements = element.select("a") setUrlWithoutDomain(urlElements.attr("href")) - name = element.select(".lch a, .chapternum")!!.text().ifBlank { urlElements.first()!!.text() } + name = element.select(".chapter-link-item").text() date_upload = parseChapterDate2(element.select(".chapter-link-time").text()) } @@ -133,7 +158,7 @@ class KomikCast : MangaThemesia( } return doc.select(cssQuery) - .mapIndexed { i, img -> Page(i, "", img.imgAttr()) } + .mapIndexed { i, img -> Page(i, document.location(), img.imgAttr()) } } override val hasProjectPage: Boolean = true @@ -218,17 +243,11 @@ class KomikCast : MangaThemesia( OrderByFilter(), Filter.Header("Genre exclusion is not available for all sources"), GenreListFilter(getGenreList()), + Filter.Separator(), + Filter.Header("NOTE: Can't be used with other filter!"), + Filter.Header("$name Project List page"), + ProjectFilter(), ) - if (hasProjectPage) { - filters.addAll( - mutableListOf>( - Filter.Separator(), - Filter.Header("NOTE: Can't be used with other filter!"), - Filter.Header("$name Project List page"), - ProjectFilter(), - ), - ) - } return FilterList(filters) } } diff --git a/multisrc/overrides/mangathemesia/komikdewasa/src/KomikDewasa.kt b/multisrc/overrides/mangathemesia/komikdewasa/src/KomikDewasa.kt index 98380340de..75b799fddb 100644 --- a/multisrc/overrides/mangathemesia/komikdewasa/src/KomikDewasa.kt +++ b/multisrc/overrides/mangathemesia/komikdewasa/src/KomikDewasa.kt @@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.komikdewasa import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class KomikDewasa : MangaThemesia("KomikDewasa", "https://komikdewasa.org", "id", "/komik") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/komikindoco/src/KomikindoCo.kt b/multisrc/overrides/mangathemesia/komikindoco/src/KomikindoCo.kt index 9a6c96240e..1744757e7e 100644 --- a/multisrc/overrides/mangathemesia/komikindoco/src/KomikindoCo.kt +++ b/multisrc/overrides/mangathemesia/komikindoco/src/KomikindoCo.kt @@ -5,15 +5,12 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class KomikindoCo : MangaThemesia("KomikIndo.co", "https://komikindo.co", "id", dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale("id"))) { // Formerly "Komikindo.co" override val id = 734619124437406170 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/komiklab/src/KomikLabFactory.kt b/multisrc/overrides/mangathemesia/komiklab/src/KomikLabFactory.kt deleted file mode 100644 index 5382a21bb9..0000000000 --- a/multisrc/overrides/mangathemesia/komiklab/src/KomikLabFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.komiklab - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import eu.kanade.tachiyomi.source.SourceFactory - -class KomikLabFactory : SourceFactory { - override fun createSources() = listOf( - KomikLabEn(), - KomikLabId(), - ) -} - -class KomikLabEn : MangaThemesia("KomikLab Scans", "https://komiklab.com", "en") - -class KomikLabId : MangaThemesia("Komik Lab", "https://komiklab.net", "id") { - override val hasProjectPage = true - - override val seriesDetailsSelector = ".seriestucon" -} diff --git a/multisrc/overrides/mangathemesia/komikmanhwa/src/KomikManhwa.kt b/multisrc/overrides/mangathemesia/komikmanhwa/src/KomikManhwa.kt index 65105ec823..bfd0fc6e17 100644 --- a/multisrc/overrides/mangathemesia/komikmanhwa/src/KomikManhwa.kt +++ b/multisrc/overrides/mangathemesia/komikmanhwa/src/KomikManhwa.kt @@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.komikmanhwa import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class KomikManhwa : MangaThemesia("KomikManhwa", "https://komikmanhwa.me", "id", "/series") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() } diff --git a/multisrc/overrides/mangathemesia/komiksan/src/Komiksan.kt b/multisrc/overrides/mangathemesia/komiksan/src/Komiksan.kt new file mode 100644 index 0000000000..af81466498 --- /dev/null +++ b/multisrc/overrides/mangathemesia/komiksan/src/Komiksan.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.id.komiksan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import org.jsoup.nodes.Document + +class Komiksan : MangaThemesia("Komiksan", "https://komiksan.link", "id", "/list") { + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(4) + .build() + + override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply { + title = document.selectFirst(seriesThumbnailSelector)!!.attr("alt") + } +} diff --git a/multisrc/overrides/mangathemesia/komikstation/src/KomikStation.kt b/multisrc/overrides/mangathemesia/komikstation/src/KomikStation.kt index 51437f642a..c7b912d122 100644 --- a/multisrc/overrides/mangathemesia/komikstation/src/KomikStation.kt +++ b/multisrc/overrides/mangathemesia/komikstation/src/KomikStation.kt @@ -2,19 +2,31 @@ package eu.kanade.tachiyomi.extension.id.komikstation import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.SManga import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class KomikStation : MangaThemesia("Komik Station", "https://komikstation.co", "id") { // Formerly "Komik Station (WP Manga Stream)" override val id = 6148605743576635261 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() + override val seriesTitleSelector = ".ts-breadcrumb li[itemprop]:last-child span" + override val seriesAuthorSelector = ".infox .fmed:has(.fa-pen-fancy) span" + override val seriesArtistSelector = ".infox .fmed:has(.fa-paintbrush) span" + override val seriesTypeSelector = ".tsinfo .imptdt:has(a[href*=\"type\"]) a" + override val seriesStatusSelector = ".tsinfo .imptdt:first-child i" + + override fun String?.parseStatus(): Int = when { + this == null -> SManga.UNKNOWN + listOf("ongoing", "berjalan").any { this.contains(it, ignoreCase = true) } -> SManga.ONGOING + this.contains("hiatus", ignoreCase = true) -> SManga.ON_HIATUS + this.contains("tamat", ignoreCase = true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + override val projectPageString = "/project-list" override val hasProjectPage = true diff --git a/multisrc/overrides/mangathemesia/kumapoi/src/KumaPoi.kt b/multisrc/overrides/mangathemesia/kumapoi/src/KumaPoi.kt index 316d091b76..795a2d03e1 100644 --- a/multisrc/overrides/mangathemesia/kumapoi/src/KumaPoi.kt +++ b/multisrc/overrides/mangathemesia/kumapoi/src/KumaPoi.kt @@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.kumapoi import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit -class KumaPoi : MangaThemesia("KumaPoi", "https://kumapoi.club", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) +class KumaPoi : MangaThemesia("KumaPoi", "https://kumapoi.info", "id") { + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/kumascans/src/KumaScans.kt b/multisrc/overrides/mangathemesia/kumascans/src/KumaScans.kt index 4a0bde5802..2c2bdf41ce 100644 --- a/multisrc/overrides/mangathemesia/kumascans/src/KumaScans.kt +++ b/multisrc/overrides/mangathemesia/kumascans/src/KumaScans.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.kumascans import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class KumaScans : MangaThemesia("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f6b98f6f3e Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..114e338101 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..9b2d22544e Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d0c92d4d9a Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ffe56d3656 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/lelmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..e5a6e78529 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lelmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/lelmanga/src/Lelmanga.kt b/multisrc/overrides/mangathemesia/lelmanga/src/Lelmanga.kt new file mode 100644 index 0000000000..7e0ca7deef --- /dev/null +++ b/multisrc/overrides/mangathemesia/lelmanga/src/Lelmanga.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.fr.lelmanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class Lelmanga : MangaThemesia("Lelmanga", "https://lelmanga.com", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)) { + override val altNamePrefix = "Nom alternatif: " + override val seriesAuthorSelector = ".imptdt:contains(Auteur) i" + override val seriesArtistSelector = ".imptdt:contains(Artiste) i" +} diff --git a/multisrc/overrides/mangathemesia/lianscans/src/LianScans.kt b/multisrc/overrides/mangathemesia/lianscans/src/LianScans.kt index 3b6a2a1198..7bdb98e228 100644 --- a/multisrc/overrides/mangathemesia/lianscans/src/LianScans.kt +++ b/multisrc/overrides/mangathemesia/lianscans/src/LianScans.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.lianscans import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class LianScans : MangaThemesia("LianScans", "https://www.lianscans.my.id", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/lunarscans/src/LunarScans.kt b/multisrc/overrides/mangathemesia/lunarscans/src/LunarScans.kt index e2f4e9b503..6290d30913 100644 --- a/multisrc/overrides/mangathemesia/lunarscans/src/LunarScans.kt +++ b/multisrc/overrides/mangathemesia/lunarscans/src/LunarScans.kt @@ -21,9 +21,6 @@ class LunarScans : MangaThemesia( .rateLimit(1) .build() - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { return if (query.isEmpty()) { super.searchMangaRequest(page, query, filters) @@ -61,7 +58,7 @@ class LunarScans : MangaThemesia( val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");") val tsReader = json.decodeFromString(jsonString) val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() - return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) } } @Serializable diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..51faf999b2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..50a5fb1e24 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6f0cf0b3c4 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ea55342e70 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bab441b497 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/lynxscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..3a35efa06c Binary files /dev/null and b/multisrc/overrides/mangathemesia/lynxscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/lynxscans/src/LynxScans.kt b/multisrc/overrides/mangathemesia/lynxscans/src/LynxScans.kt new file mode 100644 index 0000000000..9fb2156a66 --- /dev/null +++ b/multisrc/overrides/mangathemesia/lynxscans/src/LynxScans.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.en.lynxscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.Interceptor +import okhttp3.Response + +class LynxScans : MangaThemesia("LynxScans", "https://lynxscans.com", "en", "/comics") { + override val versionId = 3 + override val hasProjectPage = true + + override val client = super.client.newBuilder() + .addNetworkInterceptor(::pageSpeedRedirectIntercept) + .rateLimit(2) + .build() + + private fun pageSpeedRedirectIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (request.url.pathSegments.contains("wp-content") || request.method == "POST") { + return chain.proceed(request) + } + + val newUrl = request.url.newBuilder() + .setQueryParameter("PageSpeed", "noscript") + .build() + + val newRequest = request.newBuilder() + .url(newUrl) + .build() + + return chain.proceed(newRequest) + } +} diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b80b5f2e80 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..980c300f62 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..07c1996137 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c88e4d13f9 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ed602bac79 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/lyrascans/res/web_hi_res_512.png new file mode 100644 index 0000000000..8f080717f8 Binary files /dev/null and b/multisrc/overrides/mangathemesia/lyrascans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/lyrascans/src/LyraScans.kt b/multisrc/overrides/mangathemesia/lyrascans/src/LyraScans.kt new file mode 100644 index 0000000000..9ae916eb8f --- /dev/null +++ b/multisrc/overrides/mangathemesia/lyrascans/src/LyraScans.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.en.lyrascans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class LyraScans : MangaThemesia("Lyra Scans", "https://lyrascans.com", "en") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5) + .build() +} diff --git a/multisrc/overrides/mangathemesia/magusmanga/src/MagusManga.kt b/multisrc/overrides/mangathemesia/magusmanga/src/MagusManga.kt index 2c7f38e543..e67189dfa7 100644 --- a/multisrc/overrides/mangathemesia/magusmanga/src/MagusManga.kt +++ b/multisrc/overrides/mangathemesia/magusmanga/src/MagusManga.kt @@ -1,7 +1,20 @@ -package eu.kanade.tachiyomi.extension.ar.magusmanga +package eu.kanade.tachiyomi.extension.en.magusmanga import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit -class MagusManga : MangaThemesia("Magus Manga", "https://magusmanga.com", "ar", dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar"))) +class MagusManga : MangaThemesia( + "Magus Manga", + "https://magusmanga.com", + "en", + dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("en")), +) { + override val id = 7792477462646075400 + + override val client = super.client.newBuilder() + .rateLimit(1, 1, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/mangathemesia/mangakita/src/MangaKita.kt b/multisrc/overrides/mangathemesia/mangakita/src/MangaKita.kt index 5b26a5b24b..492311c005 100644 --- a/multisrc/overrides/mangathemesia/mangakita/src/MangaKita.kt +++ b/multisrc/overrides/mangathemesia/mangakita/src/MangaKita.kt @@ -2,6 +2,6 @@ package eu.kanade.tachiyomi.extension.id.mangakita import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -class MangaKita : MangaThemesia("MangaKita", "https://mangakita.net", "id") { +class MangaKita : MangaThemesia("MangaKita", "https://mangakita.id", "id") { override val hasProjectPage = true } diff --git a/multisrc/overrides/mangathemesia/mangakyo/src/Mangakyo.kt b/multisrc/overrides/mangathemesia/mangakyo/src/Mangakyo.kt index 1bf4ea3d8c..ee9de6b8e8 100644 --- a/multisrc/overrides/mangathemesia/mangakyo/src/Mangakyo.kt +++ b/multisrc/overrides/mangathemesia/mangakyo/src/Mangakyo.kt @@ -3,16 +3,22 @@ package eu.kanade.tachiyomi.extension.id.mangakyo import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit +import java.text.SimpleDateFormat +import java.util.Locale -class Mangakyo : MangaThemesia("Mangakyo", "https://mangakyo.id", "id") { +class Mangakyo : MangaThemesia( + "Mangakyo", + "https://mangakyo.org", + "id", + "/komik", + SimpleDateFormat("MMM d, yyyy", Locale("id")), +) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() + override val seriesTitleSelector = ".ts-breadcrumb li:last-child span" override val seriesAuthorSelector = ".infotable tr:contains(Pengarang) td:last-child" override val seriesDescriptionSelector = ".entry-content[itemprop=description] p:not(:contains(melapor ke fanspage))" override val seriesAltNameSelector = ".infotable tr:contains(Alternatif) td:last-child" diff --git a/multisrc/overrides/mangathemesia/mangaraworg/src/MangaRawOrg.kt b/multisrc/overrides/mangathemesia/mangaraworg/src/MangaRawOrg.kt index 224359a25b..4cfed65b60 100644 --- a/multisrc/overrides/mangathemesia/mangaraworg/src/MangaRawOrg.kt +++ b/multisrc/overrides/mangathemesia/mangaraworg/src/MangaRawOrg.kt @@ -15,14 +15,11 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import java.util.concurrent.TimeUnit class MangaRawOrg : MangaThemesia("Manga Raw.org", "https://mangaraw.org", "ja") { override val id = 6223520752496636410 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/mangaschan/src/MangasChan.kt b/multisrc/overrides/mangathemesia/mangaschan/src/MangasChan.kt index ebbeed5989..76620649fc 100644 --- a/multisrc/overrides/mangathemesia/mangaschan/src/MangasChan.kt +++ b/multisrc/overrides/mangathemesia/mangaschan/src/MangasChan.kt @@ -7,17 +7,17 @@ import java.util.Locale class MangasChan : MangaThemesia( "Mangás Chan", - "https://mangaschan.com", + "https://mangaschan.net", "pt-BR", dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), ) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .build() override val altNamePrefix = "Nomes alternativos: " - override val seriesArtistSelector = ".infotable tr:contains(Artista) td:last-child" - override val seriesAuthorSelector = ".infotable tr:contains(Autor) td:last-child" - override val seriesTypeSelector = ".infotable tr:contains(Tipo) td:last-child" + override val seriesArtistSelector = ".tsinfo .imptdt:contains(Artista) > i" + override val seriesAuthorSelector = ".tsinfo .imptdt:contains(Autor) > i" + override val seriesTypeSelector = ".tsinfo .imptdt:contains(Tipo) > a" } diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0bde288891 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ce8507766c Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dd2d028201 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b710dfa1fc Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d00e6a4a84 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/mangashiina/res/web_hi_res_512.png new file mode 100644 index 0000000000..8411e149d8 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiina/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiina/src/MangaShiina.kt b/multisrc/overrides/mangathemesia/mangashiina/src/MangaShiina.kt new file mode 100644 index 0000000000..87d66ff089 --- /dev/null +++ b/multisrc/overrides/mangathemesia/mangashiina/src/MangaShiina.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.es.mangashiina + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaShiina : MangaThemesia( + "MangaShiina", + "https://mangashiina.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..30cfe4fe64 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..db5c9d51da Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..7ee214e90c Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b25b2bbe21 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9b096fb2e5 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/mangashiro/res/web_hi_res_512.png new file mode 100644 index 0000000000..3328f3aa36 Binary files /dev/null and b/multisrc/overrides/mangathemesia/mangashiro/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/mangashiro/src/MangaShiro.kt b/multisrc/overrides/mangathemesia/mangashiro/src/MangaShiro.kt new file mode 100644 index 0000000000..ffa761ec23 --- /dev/null +++ b/multisrc/overrides/mangathemesia/mangashiro/src/MangaShiro.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.id.mangashiro + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +class MangaShiro : MangaThemesia("MangaShiro", "https://mangashiro.me", "id") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5, TimeUnit.SECONDS) + .build() + + override val hasProjectPage = true + + override val projectPageString = "/project-manga" + + override val seriesTitleSelector = ".ts-breadcrumb li:last-child span" +} diff --git a/multisrc/overrides/mangathemesia/mangaswat/src/MangaSwat.kt b/multisrc/overrides/mangathemesia/mangaswat/src/MangaSwat.kt index 220816e7e4..e53a9094ec 100644 --- a/multisrc/overrides/mangathemesia/mangaswat/src/MangaSwat.kt +++ b/multisrc/overrides/mangathemesia/mangaswat/src/MangaSwat.kt @@ -7,12 +7,15 @@ import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString -import okhttp3.Headers import okhttp3.OkHttpClient +import okhttp3.Request import org.jsoup.nodes.Document +import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat @@ -20,11 +23,11 @@ import java.util.Locale class MangaSwat : MangaThemesia( "MangaSwat", - "https://swatmanga.me", + "https://goldragon.me", "ar", - dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US), + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), ) { - private val defaultBaseUrl = "https://swatmanga.me" + private val defaultBaseUrl = "https://goldragon.me" override val baseUrl by lazy { getPrefBaseUrl() } @@ -36,8 +39,22 @@ class MangaSwat : MangaThemesia( .rateLimit(1) .build() - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", "$baseUrl/") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val request = super.searchMangaRequest(page, query, filters) + if (query.isBlank()) return request + + val url = request.url.newBuilder() + .removePathSegment(0) + .removeAllQueryParameters("title") + .addQueryParameter("s", query) + .build() + + return request.newBuilder() + .url(url) + .build() + } + + override fun searchMangaNextPageSelector() = "a[rel=next]" override val seriesArtistSelector = "span:contains(الناشر) i" override val seriesAuthorSelector = "span:contains(المؤلف) i" @@ -50,11 +67,18 @@ class MangaSwat : MangaThemesia( val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");") val tsReader = json.decodeFromString(jsonString) val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() - return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) } } override fun chapterListSelector() = "div.bxcl li, ul div:has(span.lchx)" + override fun chapterFromElement(element: Element) = SChapter.create().apply { + val urlElements = element.select("a") + setUrlWithoutDomain(urlElements.attr("href")) + name = element.select(".lch a, .chapternum").text().ifBlank { urlElements.last()!!.text() } + date_upload = element.selectFirst(".chapter-date")?.text().parseChapterDate() + } + @Serializable data class TSReader( val sources: List, diff --git a/multisrc/overrides/mangathemesia/mangatale/src/MangaTale.kt b/multisrc/overrides/mangathemesia/mangatale/src/MangaTale.kt new file mode 100644 index 0000000000..56b0a9cfc6 --- /dev/null +++ b/multisrc/overrides/mangathemesia/mangatale/src/MangaTale.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.id.mangatale + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class MangaTale : MangaThemesia("MangaTale", "https://mangatale.co", "id") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5) + .build() + + override val seriesTitleSelector = ".ts-breadcrumb li:last-child span" +} diff --git a/multisrc/overrides/mangathemesia/mangayaro/src/Mangayaro.kt b/multisrc/overrides/mangathemesia/mangayaro/src/Mangayaro.kt index 34fd4e1586..912e2c6609 100644 --- a/multisrc/overrides/mangathemesia/mangayaro/src/Mangayaro.kt +++ b/multisrc/overrides/mangathemesia/mangayaro/src/Mangayaro.kt @@ -3,13 +3,12 @@ package eu.kanade.tachiyomi.extension.id.mangayaro import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit -class Mangayaro : MangaThemesia("Mangayaro", "https://mangayaro.net", "id") { +class Mangayaro : MangaThemesia("Mangayaro", "https://www.mangayaro.id", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() + + override val seriesAuthorSelector = ".tsinfo .imptdt:contains(seniman) i" } diff --git a/multisrc/overrides/mangathemesia/mangceh/src/Mareceh.kt b/multisrc/overrides/mangathemesia/mangceh/src/Mareceh.kt index 891845d19d..8af8604347 100644 --- a/multisrc/overrides/mangathemesia/mangceh/src/Mareceh.kt +++ b/multisrc/overrides/mangathemesia/mangceh/src/Mareceh.kt @@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.extension.id.mangceh import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class Mareceh : MangaThemesia("Mareceh", "https://mareceh.com", "id") { override val versionId = 2 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/mangkomik/src/MangKomik.kt b/multisrc/overrides/mangathemesia/mangkomik/src/MangKomik.kt index 29181369d2..69f450d716 100644 --- a/multisrc/overrides/mangathemesia/mangkomik/src/MangKomik.kt +++ b/multisrc/overrides/mangathemesia/mangkomik/src/MangKomik.kt @@ -5,12 +5,14 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Page import org.jsoup.nodes.Document -class MangKomik : MangaThemesia("MangKomik", "https://mangkomik.net", "id") { +class MangKomik : MangaThemesia("Siren Komik", "https://sirenkomik.my.id", "id") { + override val id = 8457447675410081142 + override val hasProjectPage = true override fun pageListParse(document: Document): List { // Get external JS for image urls - val scriptEl = document.selectFirst("script[data-minify]")!! + val scriptEl = document.selectFirst("script[data-minify]") val scriptUrl = scriptEl?.attr("src") if (scriptUrl.isNullOrEmpty()) { return super.pageListParse(document) diff --git a/multisrc/overrides/mangathemesia/manhwadesu/src/ManhwaDesu.kt b/multisrc/overrides/mangathemesia/manhwadesu/src/ManhwaDesu.kt index 56c0de2750..13ec37cbf3 100644 --- a/multisrc/overrides/mangathemesia/manhwadesu/src/ManhwaDesu.kt +++ b/multisrc/overrides/mangathemesia/manhwadesu/src/ManhwaDesu.kt @@ -3,13 +3,23 @@ package eu.kanade.tachiyomi.extension.id.manhwadesu import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit +import org.jsoup.nodes.Element -class ManhwaDesu : MangaThemesia("ManhwaDesu", "https://manhwadesu.org", "id", "/komik") { +class ManhwaDesu : MangaThemesia("ManhwaDesu", "https://manhwadesu.one", "id", "/komik") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() + + override fun Element.imgAttr(): String { + attributes() + .find { it.key.endsWith("original-src") } + ?.let { return absUrl(it.key) } + + return when { + hasAttr("data-lazy-src") -> attr("abs:data-lazy-src") + hasAttr("data-src") -> attr("abs:data-src") + else -> attr("abs:src") + } + } } diff --git a/multisrc/overrides/mangathemesia/manhwafreak/src/ManhwaFreak.kt b/multisrc/overrides/mangathemesia/manhwafreak/src/ManhwaFreak.kt index 0ad450efca..8dab850bad 100644 --- a/multisrc/overrides/mangathemesia/manhwafreak/src/ManhwaFreak.kt +++ b/multisrc/overrides/mangathemesia/manhwafreak/src/ManhwaFreak.kt @@ -7,8 +7,9 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import okhttp3.Request import org.jsoup.nodes.Element +import java.util.Calendar -class ManhwaFreak : MangaThemesia("Manhwa Freak", "https://manhwafreak.com", "en") { +class ManhwaFreak : MangaThemesia("Manhwa Freak", "https://manhwa-freak.com", "en") { // they called the theme "mangareaderfix" @@ -51,9 +52,34 @@ class ManhwaFreak : MangaThemesia("Manhwa Freak", "https://manhwafreak.com", "en override fun chapterFromElement(element: Element) = SChapter.create().apply { val urlElements = element.select("a") setUrlWithoutDomain(urlElements.attr("href")) - name = element.select(".chapter-info p:nth-child(1)")!!.text().ifBlank { urlElements.first()!!.text() } - date_upload = element.selectFirst(".chapter-info p:nth-child(2)")?.text().parseChapterDate() + val chapterElements = element.select(".chapter-info") + name = chapterElements.select("p:nth-child(1)").text().ifBlank { urlElements.first()!!.text() } + date_upload = getChapterDate(chapterElements.first()) } override fun getFilterList() = FilterList() + + private fun getChapterDate(element: Element?): Long { + element ?: return 0 + val chapterDate = element.select("p:nth-child(2)").text() + + return when { + element.select("p.new").isNotEmpty() -> getToday() + chapterDate.contains(Regex("day(s)* ago$")) -> { + val number = Regex("""(\d+)""").find(chapterDate)?.value?.toIntOrNull() ?: return 0 + Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + } + + else -> chapterDate.parseChapterDate() + } + } + + private fun getToday(): Long { + return Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } } diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7284178864 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..cb1acc5361 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..142f451b07 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..032e7759b8 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..676f90eb69 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/manhwafreakfr/res/web_hi_res_512.png new file mode 100644 index 0000000000..4d0f594988 Binary files /dev/null and b/multisrc/overrides/mangathemesia/manhwafreakfr/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/manhwafreakfr/src/ManhwaFreakFR.kt b/multisrc/overrides/mangathemesia/manhwafreakfr/src/ManhwaFreakFR.kt new file mode 100644 index 0000000000..1662a06417 --- /dev/null +++ b/multisrc/overrides/mangathemesia/manhwafreakfr/src/ManhwaFreakFR.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.extension.fr.manhwafreakfr + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.Request +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +class ManhwaFreakFR : MangaThemesia("ManhwaFreak", "https://manhwafreak.fr", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)) { + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manga/?type=comic", headers) + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/?order=views&type=comic", headers) + + override fun searchMangaSelector() = ".listupd .lastest-serie" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + GET("$baseUrl/page/$page/?s=$query") + + override fun chapterListSelector() = ".chapter-li a:not(:has(svg))" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + val urlElements = element.select("a") + setUrlWithoutDomain(urlElements.attr("href")) + name = element.select(".chapter-info p:nth-child(1)").text().ifBlank { urlElements.first()!!.text() } + date_upload = element.selectFirst(".chapter-info p:nth-child(2)")?.text().parseChapterDate() + } + + override fun getFilterList() = FilterList() +} diff --git a/multisrc/overrides/mangathemesia/manhwaindo/src/ManhwaIndo.kt b/multisrc/overrides/mangathemesia/manhwaindo/src/ManhwaIndo.kt index 365a2d8b56..e70fba3bd1 100644 --- a/multisrc/overrides/mangathemesia/manhwaindo/src/ManhwaIndo.kt +++ b/multisrc/overrides/mangathemesia/manhwaindo/src/ManhwaIndo.kt @@ -1,8 +1,6 @@ package eu.kanade.tachiyomi.extension.id.manhwaindo import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import okhttp3.Headers -import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale @@ -11,15 +9,9 @@ class ManhwaIndo : MangaThemesia( "https://manhwaindo.id", "id", "/series", - SimpleDateFormat("MMMM dd, yyyy", Locale("id")), + SimpleDateFormat("MMMM dd, yyyy", Locale.US), ) { - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", baseUrl) - - override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply { - thumbnail_url = document.select(seriesThumbnailSelector).attr("abs:src") - } + override val seriesTitleSelector = ".ts-breadcrumb li:last-child span" override val hasProjectPage = true } diff --git a/multisrc/overrides/mangathemesia/manhwalandmom/src/ManhwaLandMom.kt b/multisrc/overrides/mangathemesia/manhwalandmom/src/ManhwaLandMom.kt index fcab463287..a56211ea26 100644 --- a/multisrc/overrides/mangathemesia/manhwalandmom/src/ManhwaLandMom.kt +++ b/multisrc/overrides/mangathemesia/manhwalandmom/src/ManhwaLandMom.kt @@ -3,13 +3,17 @@ package eu.kanade.tachiyomi.extension.id.manhwalandmom import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit +import java.text.SimpleDateFormat +import java.util.Locale -class ManhwaLandMom : MangaThemesia("ManhwaLand.mom", "https://manhwaland.us", "id") { +class ManhwaLandMom : MangaThemesia( + "ManhwaLand.mom", + "https://manhwaland.lat", + "id", + dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("id")), +) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() } diff --git a/multisrc/overrides/mangathemesia/manhwalist/src/ManhwaList.kt b/multisrc/overrides/mangathemesia/manhwalist/src/ManhwaList.kt index ba3a8b0888..2534e7fb79 100644 --- a/multisrc/overrides/mangathemesia/manhwalist/src/ManhwaList.kt +++ b/multisrc/overrides/mangathemesia/manhwalist/src/ManhwaList.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.manhwalist import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit -class ManhwaList : MangaThemesia("ManhwaList", "https://manhwalist.xyz", "id") { +class ManhwaList : MangaThemesia("ManhwaList", "https://manhwalist.com", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fb92456c71..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0ef1a91946..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3f377b9602..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 4ac4f7d55f..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index bcd75b74fb..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/martialmanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/martialmanga/res/web_hi_res_512.png deleted file mode 100644 index 39b2986fc6..0000000000 Binary files a/multisrc/overrides/mangathemesia/martialmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-hdpi/ic_launcher.png index 2be925c7d0..4349665cb3 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-hdpi/ic_launcher.png and b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-mdpi/ic_launcher.png index 2da0321045..6fc32d5266 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-mdpi/ic_launcher.png and b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xhdpi/ic_launcher.png index 8bc21baa3c..6e38f07f76 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xhdpi/ic_launcher.png and b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxhdpi/ic_launcher.png index 9c02b6d375..1cce10d9e7 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxhdpi/ic_launcher.png and b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxxhdpi/ic_launcher.png index 42b6479b1a..68f9b78612 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxxhdpi/ic_launcher.png and b/multisrc/overrides/mangathemesia/masterkomik/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/masterkomik/res/web_hi_res_512.png index bb9dd6dca4..00aaa64cf0 100644 Binary files a/multisrc/overrides/mangathemesia/masterkomik/res/web_hi_res_512.png and b/multisrc/overrides/mangathemesia/masterkomik/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/masterkomik/src/MasterKomik.kt b/multisrc/overrides/mangathemesia/masterkomik/src/MasterKomik.kt deleted file mode 100644 index e9082ffb68..0000000000 --- a/multisrc/overrides/mangathemesia/masterkomik/src/MasterKomik.kt +++ /dev/null @@ -1,19 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.masterkomik - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit - -class MasterKomik : MangaThemesia("MasterKomik", "https://masterkomik.com", "id") { - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .rateLimit(4) - .build() - - override val projectPageString = "/project-list" - - override val hasProjectPage = true -} diff --git a/multisrc/overrides/mangathemesia/masterkomik/src/TenshiId.kt b/multisrc/overrides/mangathemesia/masterkomik/src/TenshiId.kt new file mode 100644 index 0000000000..2dcc003d83 --- /dev/null +++ b/multisrc/overrides/mangathemesia/masterkomik/src/TenshiId.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.extension.id.masterkomik + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.text.SimpleDateFormat +import java.util.Locale + +class TenshiId : MangaThemesia( + "Tenshi.id", + "https://tenshi.id", + "id", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id", "ID")), + mangaUrlDirectory = "/komik", +) { + + // MasterKomik changed to Tenshi.id + override val id: Long = 3146720114171452298 + + override val seriesArtistSelector: String = ".imptdt-artist-sub-2 i .js-button-custom" + override val seriesAuthorSelector: String = ".imptdt-author-sub-2 i .js-button-custom" + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(4) + .build() + + override val projectPageString = "/project-list" + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/miauscan/src/MiauScanFactory.kt b/multisrc/overrides/mangathemesia/miauscan/src/MiauScanFactory.kt index 1e3d18ae1e..df73599f16 100644 --- a/multisrc/overrides/mangathemesia/miauscan/src/MiauScanFactory.kt +++ b/multisrc/overrides/mangathemesia/miauscan/src/MiauScanFactory.kt @@ -13,18 +13,23 @@ import java.util.Locale class MiauScanFactory : SourceFactory { override fun createSources() = listOf( - MiauScan("es", Filter.TriState.STATE_EXCLUDE), - MiauScan("pt-BR", Filter.TriState.STATE_INCLUDE), + MiauScan("es"), + MiauScan("pt-BR"), ) } -open class MiauScan(lang: String, private val portugueseMode: Int) : MangaThemesia( +open class MiauScan(lang: String) : MangaThemesia( name = "Miau Scan", - baseUrl = "https://miauscan.com", + baseUrl = "https://miaucomics.org", lang = lang, dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), ) { + private val portugueseMode = + if (lang == "pt-BR") Filter.TriState.STATE_INCLUDE else Filter.TriState.STATE_EXCLUDE + + override val seriesGenreSelector = ".mgen a:not(:contains(Português))" + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val genreFilterIndex = filters.indexOfFirst { it is GenreListFilter } val genreFilter = filters.getOrNull(genreFilterIndex) as? GenreListFilter @@ -32,7 +37,7 @@ open class MiauScan(lang: String, private val portugueseMode: Int) : MangaThemes val overloadedGenreFilter = GenreListFilter( genres = genreFilter.state + listOf( - Genre("", PORTUGUESE_GENRE, portugueseMode), + Genre("", PORTUGUESE_GENRE_ID, portugueseMode), ), ) @@ -60,11 +65,11 @@ open class MiauScan(lang: String, private val portugueseMode: Int) : MangaThemes } override fun getGenreList(): List { - return super.getGenreList().filter { it.value != PORTUGUESE_GENRE } + return super.getGenreList().filter { it.value != PORTUGUESE_GENRE_ID } } companion object { - const val PORTUGUESE_GENRE = "307" + const val PORTUGUESE_GENRE_ID = "307" val PORTUGUESE_SUFFIX = "^\\(\\s*Portugu[êe]s\\s*\\)\\s*".toRegex(RegexOption.IGNORE_CASE) } diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..ee21522289 Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ed7202a8a2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e62f45d259 Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0a3625e14a Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..98a81093b4 Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/miraiscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..2abfda60b6 Binary files /dev/null and b/multisrc/overrides/mangathemesia/miraiscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/miraiscans/src/MiraiScans.kt b/multisrc/overrides/mangathemesia/miraiscans/src/MiraiScans.kt new file mode 100644 index 0000000000..ef693deee4 --- /dev/null +++ b/multisrc/overrides/mangathemesia/miraiscans/src/MiraiScans.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.id.miraiscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class MiraiScans : MangaThemesia("Mirai Scans", "https://miraiscans.com", "id", mangaUrlDirectory = "/komik") { + + override val hasProjectPage = true + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(3) + .build() +} diff --git a/multisrc/overrides/mangathemesia/mirrordesu/src/MirrorDesu.kt b/multisrc/overrides/mangathemesia/mirrordesu/src/MirrorDesu.kt index c1c007c77f..3703c33a28 100644 --- a/multisrc/overrides/mangathemesia/mirrordesu/src/MirrorDesu.kt +++ b/multisrc/overrides/mangathemesia/mirrordesu/src/MirrorDesu.kt @@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.extension.id.mirrordesu import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class MirrorDesu : MangaThemesia("MirrorDesu", "https://mirrordesu.me", "id", "/komik") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ad6b2180c1..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 577303ecf9..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3ae3810454..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index cd12ab3527..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e771402fe1..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/modescanlator/res/web_hi_res_512.png deleted file mode 100644 index cb158b95d8..0000000000 Binary files a/multisrc/overrides/mangathemesia/modescanlator/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/modescanlator/src/ModeScanlator.kt b/multisrc/overrides/mangathemesia/modescanlator/src/ModeScanlator.kt deleted file mode 100644 index fc73005593..0000000000 --- a/multisrc/overrides/mangathemesia/modescanlator/src/ModeScanlator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.modescanlator - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class ModeScanlator : MangaThemesia( - "Mode Scanlator", - "https://modescanlator.com", - "pt-BR", - mangaUrlDirectory = "/projetos", - dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - // Site changed from Madara to WpMangaReader. - override val versionId: Int = 2 - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override val altNamePrefix = "Nome alternativo: " -} diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a1c5c14ff1..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c5b5fc64aa..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7ed506564d..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fcd39eaa9d..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1746bbbabc..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/mundomangakun/res/web_hi_res_512.png deleted file mode 100644 index 5b9d6b398f..0000000000 Binary files a/multisrc/overrides/mangathemesia/mundomangakun/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/mundomangakun/src/MundoMangaKun.kt b/multisrc/overrides/mangathemesia/mundomangakun/src/MundoMangaKun.kt deleted file mode 100644 index 34f8033561..0000000000 --- a/multisrc/overrides/mangathemesia/mundomangakun/src/MundoMangaKun.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mundomangakun - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import okhttp3.OkHttpClient -import java.text.SimpleDateFormat -import java.util.Locale - -class MundoMangaKun : MangaThemesia( - "Mundo Mangá-Kun", - "https://mundomangakun.com.br", - "pt-BR", - dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), -) { - - // Changed their theme from a custom one to MangaThemesia. - // The URLs are incompatible between the versions. - override val versionId = 2 - - override val client: OkHttpClient = super.client.newBuilder() - .build() -} diff --git a/multisrc/overrides/mangathemesia/nekomik/src/Nekomik.kt b/multisrc/overrides/mangathemesia/nekomik/src/Nekomik.kt index 513409e49c..7603a10fff 100644 --- a/multisrc/overrides/mangathemesia/nekomik/src/Nekomik.kt +++ b/multisrc/overrides/mangathemesia/nekomik/src/Nekomik.kt @@ -1,17 +1,49 @@ package eu.kanade.tachiyomi.extension.id.nekomik +import app.cash.quickjs.QuickJs import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit +import org.jsoup.nodes.Document -class Nekomik : MangaThemesia("Nekomik", "https://nekomik.com", "id") { +class Nekomik : MangaThemesia("Nekomik", "https://nekomik.me", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() override val hasProjectPage = true + + override fun pageListParse(document: Document): List { + val obfuscatedJs = document.selectFirst("script:containsData(fromCharCode)")?.data() + ?: return super.pageListParse(document) + + val data = QuickJs.create().use { context -> + context.evaluate( + """ + ts_reader = { run: function(...args) { whatever = args[0] } }; + $obfuscatedJs; + JSON.stringify(whatever); + """.trimIndent(), + ) as String + } + + val tsReader = json.decodeFromString(data) + val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() + return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) } + } + + @Serializable + data class TSReader( + val sources: List, + ) + + @Serializable + data class ReaderImageSource( + val source: String, + val images: List, + ) } diff --git a/multisrc/overrides/mangathemesia/nightscans/src/NightScans.kt b/multisrc/overrides/mangathemesia/nightscans/src/NightScans.kt index 025c679e9c..a5715576c6 100644 --- a/multisrc/overrides/mangathemesia/nightscans/src/NightScans.kt +++ b/multisrc/overrides/mangathemesia/nightscans/src/NightScans.kt @@ -1,9 +1,13 @@ package eu.kanade.tachiyomi.extension.en.nightscans import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia -import okhttp3.Headers +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit -class NightScans : MangaThemesia("NIGHT SCANS", "https://nightscans.org", "en", "/series") { - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) +class NightScans : MangaThemesia("NIGHT SCANS", "https://nightscans.net", "en", "/series") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 4, TimeUnit.SECONDS) + .build() } diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..5df1a56f23 Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..269632709b Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..f3cf4e6547 Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c5d5fdacef Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..891e887d99 Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/nonbiri/res/web_hi_res_512.png new file mode 100644 index 0000000000..c2cf95009c Binary files /dev/null and b/multisrc/overrides/mangathemesia/nonbiri/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/nonbiri/src/Nonbiri.kt b/multisrc/overrides/mangathemesia/nonbiri/src/Nonbiri.kt new file mode 100644 index 0000000000..a0834935b1 --- /dev/null +++ b/multisrc/overrides/mangathemesia/nonbiri/src/Nonbiri.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.id.nonbiri + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class Nonbiri : MangaThemesia( + "Nonbiri", + "https://nonbiri.space", + "id", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")), +) { + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/zeistmanga/noromax/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/noromax/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/noromax/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/noromax/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/noromax/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/noromax/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/noromax/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/noromax/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/noromax/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/noromax/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/noromax/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/noromax/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/noromax/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/noromax/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/noromax/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/zeistmanga/noromax/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/noromax/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/zeistmanga/noromax/res/web_hi_res_512.png rename to multisrc/overrides/mangathemesia/noromax/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangathemesia/noromax/src/Noromax.kt b/multisrc/overrides/mangathemesia/noromax/src/Noromax.kt new file mode 100644 index 0000000000..144bf7e0a6 --- /dev/null +++ b/multisrc/overrides/mangathemesia/noromax/src/Noromax.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.id.noromax + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia + +class Noromax : MangaThemesia("Noromax", "https://noromax.my.id", "id", "/Komik") { + + // Site changed from ZeistManga to MangaThemesia + override val versionId = 2 + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/opscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/opscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..5956934249 Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/opscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/opscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4a68cdd7ca Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/opscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..4b60784a3b Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1c2902361d Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..229940bde0 Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/opscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/opscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..c4e354264a Binary files /dev/null and b/multisrc/overrides/mangathemesia/opscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/origamiorpheans/src/OrigamiOrpheans.kt b/multisrc/overrides/mangathemesia/origamiorpheans/src/OrigamiOrpheans.kt index f778cc0b47..b0b9d79777 100644 --- a/multisrc/overrides/mangathemesia/origamiorpheans/src/OrigamiOrpheans.kt +++ b/multisrc/overrides/mangathemesia/origamiorpheans/src/OrigamiOrpheans.kt @@ -15,7 +15,7 @@ class OrigamiOrpheans : MangaThemesia( // Scanlator migrated from Madara to WpMangaReader. override val versionId = 2 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .build() override val altNamePrefix = "Nomes alternativos: " diff --git a/multisrc/overrides/mangathemesia/otsugami/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0e37f00620 Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b2e2990387 Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..39777045a2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..036ac99298 Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..45369b4cc3 Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/otsugami/res/web_hi_res_512.png new file mode 100644 index 0000000000..0456dd1ddf Binary files /dev/null and b/multisrc/overrides/mangathemesia/otsugami/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/otsugami/src/Otsugami.kt b/multisrc/overrides/mangathemesia/otsugami/src/Otsugami.kt new file mode 100644 index 0000000000..260a99a0b4 --- /dev/null +++ b/multisrc/overrides/mangathemesia/otsugami/src/Otsugami.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.id.otsugami + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient + +class Otsugami : MangaThemesia("Otsugami", "https://otsugami.id", "id") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(3) + .build() + + override val hasProjectPage = true +} diff --git a/multisrc/overrides/mangathemesia/ozulscans/src/OzulScans.kt b/multisrc/overrides/mangathemesia/ozulscans/src/OzulScans.kt index 126a4adf0e..e1f9fc60bc 100644 --- a/multisrc/overrides/mangathemesia/ozulscans/src/OzulScans.kt +++ b/multisrc/overrides/mangathemesia/ozulscans/src/OzulScans.kt @@ -6,7 +6,7 @@ import java.util.Locale class OzulScans : MangaThemesia( "Ozul Scans", - "https://ozulscans.com", + "https://kingofmanga.com", "ar", dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")), ) diff --git a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/patatescans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 06a09308c8..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/patatescans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3f242c2736..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3a8b33e8aa..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 50325d4b40..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9564f6f2d3..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/patatescans/res/web_hi_res_512.png deleted file mode 100644 index 370bff2517..0000000000 Binary files a/multisrc/overrides/mangathemesia/patatescans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mangathemesia/patatescans/src/Patatescans.kt b/multisrc/overrides/mangathemesia/patatescans/src/Patatescans.kt deleted file mode 100644 index 8f96ffc4ca..0000000000 --- a/multisrc/overrides/mangathemesia/patatescans/src/Patatescans.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.patatescans - -import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia - -class Patatescans : MangaThemesia("Patatescans", "https://patatescans.com", "fr") diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..22b5f44c46 Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d196e814c5 Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..468199344e Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9a780ff02f Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..86716aa030 Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/potatomanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..5c438036eb Binary files /dev/null and b/multisrc/overrides/mangathemesia/potatomanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/potatomanga/src/PotatoManga.kt b/multisrc/overrides/mangathemesia/potatomanga/src/PotatoManga.kt new file mode 100644 index 0000000000..44eea894cf --- /dev/null +++ b/multisrc/overrides/mangathemesia/potatomanga/src/PotatoManga.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.extension.ar.potatomanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class PotatoManga : MangaThemesia( + "PotatoManga", + "https://potatomanga.xyz", + "ar", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), +) { + override val seriesArtistSelector = + ".infotable tr:contains(الرسام) td:last-child, ${super.seriesArtistSelector}" + override val seriesAuthorSelector = + ".infotable tr:contains(المؤلف) td:last-child, ${super.seriesAuthorSelector}" + override val seriesStatusSelector = + ".infotable tr:contains(الحالة) td:last-child, ${super.seriesStatusSelector}" + override val seriesTypeSelector = + ".infotable tr:contains(النوع) td:last-child, ${super.seriesTypeSelector}" + override val seriesAltNameSelector = + ".infotable tr:contains(الأسماء الثانوية) td:last-child, ${super.seriesAltNameSelector}" +} diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..5b49b5d64f Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c395fe1825 Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..59ac35c3f2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cd4556e7f6 Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c05a1490a7 Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/quantumscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..e394e11aa9 Binary files /dev/null and b/multisrc/overrides/mangathemesia/quantumscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/quantumscans/src/QuantumScans.kt b/multisrc/overrides/mangathemesia/quantumscans/src/QuantumScans.kt new file mode 100644 index 0000000000..e5e8a29df3 --- /dev/null +++ b/multisrc/overrides/mangathemesia/quantumscans/src/QuantumScans.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.en.quantumscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +class QuantumScans : MangaThemesia("Quantum Scans", "https://readers-point.space", "en", "/series") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(12, 3, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/mangathemesia/queenscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..847a3610d1 Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3c4da6d4a6 Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..9f080d5d85 Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..aab37056de Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ec2c697900 Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/queenscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..a85e063b40 Binary files /dev/null and b/multisrc/overrides/mangathemesia/queenscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/queenscans/src/QueenScans.kt b/multisrc/overrides/mangathemesia/queenscans/src/QueenScans.kt new file mode 100644 index 0000000000..f7144572c0 --- /dev/null +++ b/multisrc/overrides/mangathemesia/queenscans/src/QueenScans.kt @@ -0,0 +1,28 @@ +package eu.kanade.tachiyomi.extension.en.queenscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Request + +class QueenScans : MangaThemesia("Fairy Manga", "https://fairymanga.com", "en") { + override val client = super.client.newBuilder() + .rateLimit(2) + .build() + + override val id = 4680104728999154642 + + override fun mangaDetailsRequest(manga: SManga): Request { + if (manga.url.startsWith("/comics")) { + manga.url.replaceFirst("/comics", mangaUrlDirectory) + } + return super.mangaDetailsRequest(manga) + } + + override fun chapterListRequest(manga: SManga): Request { + if (manga.url.startsWith("/comics")) { + manga.url.replaceFirst("/comics", mangaUrlDirectory) + } + return super.chapterListRequest(manga) + } +} diff --git a/multisrc/overrides/mangathemesia/raiscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b27af8c69e Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9e731fef8b Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e27743a830 Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7a2725075a Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ebd972589 Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/raiscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..ca179b6ad2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/raiscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/raiscans/src/Raiscans.kt b/multisrc/overrides/mangathemesia/raiscans/src/Raiscans.kt new file mode 100644 index 0000000000..90311c1c28 --- /dev/null +++ b/multisrc/overrides/mangathemesia/raiscans/src/Raiscans.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.en.raiscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +class Raiscans : MangaThemesia("Raiscans", "https://www.raiscans.com", "en", "/Series") { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(20, 5, TimeUnit.SECONDS) + .build() +} diff --git a/multisrc/overrides/mangathemesia/rawkuma/src/Rawkuma.kt b/multisrc/overrides/mangathemesia/rawkuma/src/Rawkuma.kt index bce788723e..3bc87fcbea 100644 --- a/multisrc/overrides/mangathemesia/rawkuma/src/Rawkuma.kt +++ b/multisrc/overrides/mangathemesia/rawkuma/src/Rawkuma.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.ja.rawkuma import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class Rawkuma : MangaThemesia("Rawkuma", "https://rawkuma.com/", "ja") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() } diff --git a/multisrc/overrides/mangathemesia/readkomik/src/ReadKomik.kt b/multisrc/overrides/mangathemesia/readkomik/src/ReadKomik.kt index 98a881a7f1..d1b8c75319 100644 --- a/multisrc/overrides/mangathemesia/readkomik/src/ReadKomik.kt +++ b/multisrc/overrides/mangathemesia/readkomik/src/ReadKomik.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.en.readkomik import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class ReadKomik : MangaThemesia("Readkomik", "https://readkomik.com", "en") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/ryukonesia/src/Ryukonesia.kt b/multisrc/overrides/mangathemesia/ryukonesia/src/Ryukonesia.kt index 4e19d66e58..f6a1540aae 100644 --- a/multisrc/overrides/mangathemesia/ryukonesia/src/Ryukonesia.kt +++ b/multisrc/overrides/mangathemesia/ryukonesia/src/Ryukonesia.kt @@ -3,13 +3,10 @@ package eu.kanade.tachiyomi.extension.id.ryukonesia import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit class Ryukonesia : MangaThemesia("Ryukonesia", "https://ryukonesia.net", "id") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() } diff --git a/multisrc/overrides/mangathemesia/sekaikomik/src/Sekaikomik.kt b/multisrc/overrides/mangathemesia/sekaikomik/src/Sekaikomik.kt index e440f2f934..5b584d1b51 100644 --- a/multisrc/overrides/mangathemesia/sekaikomik/src/Sekaikomik.kt +++ b/multisrc/overrides/mangathemesia/sekaikomik/src/Sekaikomik.kt @@ -1,7 +1,26 @@ package eu.kanade.tachiyomi.extension.id.sekaikomik import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale -class Sekaikomik : MangaThemesia("Sekaikomik", "https://www.sekaikomik.pro", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id"))) +class Sekaikomik : MangaThemesia( + "Sekaikomik", + "https://sekaikomik.bio", + "id", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")), +) { + + private val junkDescriptionPattern = """ Link Download : .*""".toRegex() + + override fun mangaDetailsParse(document: Document): SManga { + val manga = super.mangaDetailsParse(document) + + // Remove junk from description + manga.description = manga.description?.replace(junkDescriptionPattern, "") + + return manga + } +} diff --git a/multisrc/overrides/mangathemesia/sektedoujin/src/SekteDoujin.kt b/multisrc/overrides/mangathemesia/sektedoujin/src/SekteDoujin.kt index 92df0e544e..dcac72f8a6 100644 --- a/multisrc/overrides/mangathemesia/sektedoujin/src/SekteDoujin.kt +++ b/multisrc/overrides/mangathemesia/sektedoujin/src/SekteDoujin.kt @@ -5,13 +5,10 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class SekteDoujin : MangaThemesia("Sekte Doujin", "https://sektedoujin.lol", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.forLanguageTag("id"))) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() } diff --git a/multisrc/overrides/mangathemesia/sheamanga/src/SheaManga.kt b/multisrc/overrides/mangathemesia/sheamanga/src/SheaManga.kt index fb19b54e82..70b8b4676a 100644 --- a/multisrc/overrides/mangathemesia/sheamanga/src/SheaManga.kt +++ b/multisrc/overrides/mangathemesia/sheamanga/src/SheaManga.kt @@ -6,7 +6,6 @@ import okhttp3.Dns import okhttp3.OkHttpClient import java.text.SimpleDateFormat import java.util.Locale -import java.util.concurrent.TimeUnit class SheaManga : MangaThemesia( "Shea Manga", @@ -15,9 +14,7 @@ class SheaManga : MangaThemesia( dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.forLanguageTag("id")), ) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .dns(Dns.SYSTEM) .build() diff --git a/multisrc/overrides/mangathemesia/silencescan/src/SilenceScan.kt b/multisrc/overrides/mangathemesia/silencescan/src/SilenceScan.kt index c11b6d211d..61bd13ccb7 100644 --- a/multisrc/overrides/mangathemesia/silencescan/src/SilenceScan.kt +++ b/multisrc/overrides/mangathemesia/silencescan/src/SilenceScan.kt @@ -16,9 +16,8 @@ class SilenceScan : MangaThemesia( override val versionId: Int = 2 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) .build() override val altNamePrefix = "Nome alternativo: " diff --git a/multisrc/overrides/mangathemesia/skymangas/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b19685d7ac Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..15f2b75bb0 Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..00fdf99430 Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..75b4295bda Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8f3d258119 Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/skymangas/res/web_hi_res_512.png new file mode 100644 index 0000000000..4fd9557d83 Binary files /dev/null and b/multisrc/overrides/mangathemesia/skymangas/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/skymangas/src/SkyMangas.kt b/multisrc/overrides/mangathemesia/skymangas/src/SkyMangas.kt new file mode 100644 index 0000000000..5ef46470d5 --- /dev/null +++ b/multisrc/overrides/mangathemesia/skymangas/src/SkyMangas.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.extension.es.skymangas + +import android.util.Base64 +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import org.jsoup.nodes.Document +import java.lang.IllegalArgumentException +import java.text.SimpleDateFormat +import java.util.Locale + +class SkyMangas : MangaThemesia( + "SkyMangas", + "https://skymangas.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + override fun pageListParse(document: Document): List { + val script = document.selectFirst("div.readercontent > div.wrapper > script") + ?: return super.pageListParse(document) + + val scriptSrc = script.attr("src") + + if (scriptSrc.startsWith("data:text/javascript;base64,")) { + val encodedData = scriptSrc.substringAfter("data:text/javascript;base64,") + val decodedData = Base64.decode(encodedData, Base64.DEFAULT).toString(Charsets.UTF_8) + + val imageListJson = JSON_IMAGE_LIST_REGEX.find(decodedData)?.destructured?.toList()?.get(0).orEmpty() + val imageList = try { + json.parseToJsonElement(imageListJson).jsonArray + } catch (_: IllegalArgumentException) { + emptyList() + } + + return imageList.mapIndexed { i, jsonEl -> + Page(i, document.location(), jsonEl.jsonPrimitive.content) + } + } + + return super.pageListParse(document) + } +} diff --git a/multisrc/overrides/mangathemesia/soulscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..a58edc097b Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..688a42f617 Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..f52b8fbdca Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3f0e50f679 Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ca52688a05 Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/soulscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..2eded446a5 Binary files /dev/null and b/multisrc/overrides/mangathemesia/soulscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/soulscans/src/SoulScans.kt b/multisrc/overrides/mangathemesia/soulscans/src/SoulScans.kt new file mode 100644 index 0000000000..0e30054aac --- /dev/null +++ b/multisrc/overrides/mangathemesia/soulscans/src/SoulScans.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.extension.id.soulscans + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Document +import java.util.Locale + +class SoulScans : MangaThemesia("Soul Scans", "https://soulscans.my.id", "id") { + + override val hasProjectPage = true + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + document.selectFirst(seriesDetailsSelector)?.let { seriesDetails -> + title = seriesDetails.selectFirst(seriesTitleSelector)?.text().orEmpty() + artist = seriesDetails.selectFirst(seriesArtistSelector)?.ownText().removeEmptyPlaceholder() + author = seriesDetails.selectFirst(seriesAuthorSelector)?.ownText().removeEmptyPlaceholder() + description = seriesDetails.select(seriesDescriptionSelector).joinToString("\n") { it.text() }.trim() + // Add alternative name to manga description + val altName = seriesDetails.selectFirst(seriesAltNameSelector)?.ownText().takeIf { it.isNullOrBlank().not() } + altName?.let { + description = "$description\n\n$altNamePrefix$altName".trim() + } + val genres = seriesDetails.select(seriesGenreSelector).map { it.text() }.toMutableList() + // Add series type (manga/manhwa/manhua/other) to genre + seriesDetails.selectFirst(seriesTypeSelector)?.ownText().takeIf { it.isNullOrBlank().not() }?.let { genres.add(it) } + genre = genres.map { genre -> + genre.lowercase(Locale.forLanguageTag(lang)).replaceFirstChar { char -> + if (char.isLowerCase()) { + char.titlecase(Locale.forLanguageTag(lang)) + } else { + char.toString() + } + } + } + .joinToString { it.trim() } + + status = seriesDetails.selectFirst(seriesStatusSelector)?.text().parseStatus() + seriesDetails.select(seriesThumbnailSelector).firstOrNull()?.let { thumbnail_url = it.imgAttr() } + } + } +} diff --git a/multisrc/overrides/mangathemesia/ssshentais/src/SssHentais.kt b/multisrc/overrides/mangathemesia/ssshentais/src/SssHentais.kt new file mode 100644 index 0000000000..24fb3476bc --- /dev/null +++ b/multisrc/overrides/mangathemesia/ssshentais/src/SssHentais.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.extension.pt.ssshentais + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.OkHttpClient +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class SssHentais : MangaThemesia( + "SSS Hentais", + "https://hentais.sssscanlator.com", + "pt-BR", + dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .set("Accept", "image/avif,image/webp,*/*") + .set("Accept-Language", "pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3") + .set("Sec-Fetch-Dest", "image") + .set("Sec-Fetch-Mode", "no-cors") + .set("Sec-Fetch-Site", "same-origin") + .build() + + return GET(page.imageUrl!!, newHeaders) + } +} diff --git a/multisrc/overrides/mangathemesia/sssscanlator/src/SSSScanlator.kt b/multisrc/overrides/mangathemesia/sssscanlator/src/SSSScanlator.kt new file mode 100644 index 0000000000..ff42ec4672 --- /dev/null +++ b/multisrc/overrides/mangathemesia/sssscanlator/src/SSSScanlator.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.extension.pt.sssscanlator + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.OkHttpClient +import okhttp3.Request +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class SSSScanlator : MangaThemesia( + "SSSScanlator", + "https://sssscanlator.com", + "pt-BR", + dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .set("Accept", "image/avif,image/webp,*/*") + .set("Accept-Language", "pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3") + .set("Sec-Fetch-Dest", "image") + .set("Sec-Fetch-Mode", "no-cors") + .set("Sec-Fetch-Site", "same-origin") + .build() + + return GET(page.imageUrl!!, newHeaders) + } +} diff --git a/multisrc/overrides/mangathemesia/starlightscan/src/StarlightScan.kt b/multisrc/overrides/mangathemesia/starlightscan/src/StarlightScan.kt new file mode 100644 index 0000000000..99a51e42cd --- /dev/null +++ b/multisrc/overrides/mangathemesia/starlightscan/src/StarlightScan.kt @@ -0,0 +1,103 @@ +package eu.kanade.tachiyomi.extension.pt.starlightscan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class StarlightScan : MangaThemesia( + "Starlight Scan", + "https://starligthscan.com", + "pt-BR", + mangaUrlDirectory = "/mangas", + dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")), +) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + override val sendViewCount = false + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesParse(response: Response): MangasPage { + val mangaList = response.asJsoup() + .select(latestUpdatesSelector()) + .map(::latestUpdatesFromElement) + + return MangasPage(mangaList, hasNextPage = false) + } + + override fun latestUpdatesSelector() = "div.mostRecentMangaCard__listContainer article.mostRecentMangaCard" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("a.mostRecentMangaCard__title")!!.text() + thumbnail_url = element.selectFirst("img.mostRecentMangaCard__cover")!!.imgAttr() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegment(if (query.isEmpty()) mangaUrlDirectory.substring(1) else "buscar") + .addQueryParameter("search", query) + .addQueryParameter("page-current", page.toString()) + .build() + + return GET(url, headers) + } + + override fun searchMangaSelector() = "div.bulkMangaList article.bulkMangaCard" + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("a.bulkMangaCard__title")!!.text() + thumbnail_url = element.selectFirst("img.bulkMangaCard__cover")!!.imgAttr() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaNextPageSelector() = "footer.base__horizontalList a:contains(Próxima):not([disabled])" + + override val seriesDetailsSelector = "section.mangaDetails" + override val seriesTitleSelector = "h1.mangaDetails__title" + override val seriesAuthorSelector = "span.mangaDetails__author" + override val seriesDescriptionSelector = "span.mangaDetails__description" + override val seriesGenreSelector = "li.mangaTags__item" + override val seriesStatusSelector = "span.base__horizontalList[title^=Status]" + override val seriesThumbnailSelector = "img.mangaDetails__cover" + + override fun String?.parseStatus(): Int = when (this) { + "Publicação Finalizada" -> SManga.COMPLETED + else -> SManga.ONGOING + } + + override fun chapterListParse(response: Response): List { + return response.asJsoup() + .select(chapterListSelector()) + .map(::chapterFromElement) + .filter { it.name.isNotEmpty() } + } + + override fun chapterListSelector() = "div.mangaDetails__episodesContainer div.mangaDetails__episode" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.selectFirst("a.mangaDetails__episodeTitle")!!.text() + date_upload = element.selectFirst("span.mangaDetails__episodeReleaseDate")?.text().parseChapterDate() + setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) + } + + override val pageSelector = "div.scanImagesContainer img.scanImage" + + override fun getFilterList(): FilterList = FilterList() +} diff --git a/multisrc/overrides/mangathemesia/summertoon/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..31f2f9c56d Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..15ba32e164 Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..69a9d3fb77 Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..85d2a85b37 Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..83a684bf77 Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/summertoon/res/web_hi_res_512.png new file mode 100644 index 0000000000..1d22bf5d17 Binary files /dev/null and b/multisrc/overrides/mangathemesia/summertoon/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/summertoon/src/SummerToon.kt b/multisrc/overrides/mangathemesia/summertoon/src/SummerToon.kt new file mode 100644 index 0000000000..65a68fed5d --- /dev/null +++ b/multisrc/overrides/mangathemesia/summertoon/src/SummerToon.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.extension.tr.summertoon + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.text.SimpleDateFormat +import java.util.Locale + +class SummerToon : MangaThemesia( + "SummerToon", + "https://summertoon.com", + "tr", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("tr")), +) { + override val client = super.client.newBuilder() + .rateLimit(1, 1) + .build() + + override val seriesStatusSelector = ".tsinfo .imptdt:contains(Durum) i" + override val seriesAuthorSelector = ".fmed b:contains(Yazar)+span" +} diff --git a/multisrc/overrides/mangathemesia/sushiscan/src/SushiScan.kt b/multisrc/overrides/mangathemesia/sushiscan/src/SushiScan.kt index 6a09eed4f1..4de98fd429 100644 --- a/multisrc/overrides/mangathemesia/sushiscan/src/SushiScan.kt +++ b/multisrc/overrides/mangathemesia/sushiscan/src/SushiScan.kt @@ -1,15 +1,27 @@ package eu.kanade.tachiyomi.extension.fr.sushiscan import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import okhttp3.Headers +import okhttp3.OkHttpClient import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit + +class SushiScan : MangaThemesia("Sushi-Scan", "https://sushiscan.net", "fr", mangaUrlDirectory = "/catalogue", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.FRENCH)) { + + override val client: OkHttpClient = super.client.newBuilder() + .rateLimit(2, 1, TimeUnit.SECONDS) + .build() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .set("Referer", "$baseUrl$mangaUrlDirectory") -class SushiScan : MangaThemesia("Sushi-Scan", "https://sushiscan.net", "fr", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.FRENCH)) { override val altNamePrefix = "Nom alternatif : " override val seriesAuthorSelector = ".imptdt:contains(Auteur) i, .fmed b:contains(Auteur)+span" override val seriesStatusSelector = ".imptdt:contains(Statut) i" @@ -25,28 +37,23 @@ class SushiScan : MangaThemesia("Sushi-Scan", "https://sushiscan.net", "fr", dat status = document.select(seriesStatusSelector).text().parseStatus() } - // Overriding to fix http -> https when needed override fun pageListParse(document: Document): List { - val htmlPages = document.select(pageSelector) - .filterNot { it.imgAttr().isEmpty() } - .mapIndexed { i, img -> Page(i, "", if (img.imgAttr().contains("https")) img.imgAttr() else img.imgAttr().replace("http", "https")) } - - countViews(document) - - // Some sites also loads pages via javascript - if (htmlPages.isNotEmpty()) { return htmlPages } + val scriptContent = document.selectFirst("script:containsData(ts_reader)")?.data() + ?: return super.pageListParse(document) + val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");") + val tsReader = json.decodeFromString(jsonString) + val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() + return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl.replace("http://", "https://")) } + } - val docString = document.toString() - val imageListJson = JSON_IMAGE_LIST_REGEX.find(docString)?.destructured?.toList()?.get(0).orEmpty() - val imageList = try { - json.parseToJsonElement(imageListJson).jsonArray - } catch (_: IllegalArgumentException) { - emptyList() - } - val scriptPages = imageList.mapIndexed { i, jsonEl -> - Page(i, "", jsonEl.jsonPrimitive.content) - } + @Serializable + data class TSReader( + val sources: List, + ) - return scriptPages - } + @Serializable + data class ReaderImageSource( + val source: String, + val images: List, + ) } diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..aabef19589 Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..462e84620f Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..42fe15f564 Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5ae9300b38 Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..656df0261a Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/sushiscanfr/res/web_hi_res_512.png new file mode 100644 index 0000000000..373e99d426 Binary files /dev/null and b/multisrc/overrides/mangathemesia/sushiscanfr/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/sushiscanfr/src/SushiScanFR.kt b/multisrc/overrides/mangathemesia/sushiscanfr/src/SushiScanFR.kt new file mode 100644 index 0000000000..c34efc31d9 --- /dev/null +++ b/multisrc/overrides/mangathemesia/sushiscanfr/src/SushiScanFR.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.fr.sushiscanfr + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale + +class SushiScanFR : MangaThemesia("Sushiscan.fr", "https://sushiscan.fr", "fr", dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale.FRENCH)) { + override val altNamePrefix = "Nom alternatif : " + override val seriesAuthorSelector = ".imptdt:contains(Auteur) i, .fmed b:contains(Auteur)+span" + override val seriesStatusSelector = ".imptdt:contains(Statut) i" + override fun String?.parseStatus(): Int = when { + this == null -> SManga.UNKNOWN + this.contains("En Cours", ignoreCase = true) -> SManga.ONGOING + this.contains("Terminé", ignoreCase = true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun mangaDetailsParse(document: Document): SManga = + super.mangaDetailsParse(document).apply { + status = document.select(seriesStatusSelector).text().parseStatus() + } +} diff --git a/multisrc/overrides/madara/tecnoscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/tecnoscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/tecnoscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/tecnoscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/tecnoscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/tecnoscan/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/tecnoscan/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/tecnoscan/res/web_hi_res_512.png rename to multisrc/overrides/mangathemesia/tecnoscan/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangathemesia/tecnoscan/src/TecnoScan.kt b/multisrc/overrides/mangathemesia/tecnoscan/src/TecnoScan.kt new file mode 100644 index 0000000000..2b3285c8d4 --- /dev/null +++ b/multisrc/overrides/mangathemesia/tecnoscan/src/TecnoScan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.es.tecnoscan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class TecnoScan : MangaThemesia( + "Tecno Scan", + "https://tecnoscann.com", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")), +) { + // Site moved from Madara to MangaThemesia + override val versionId = 3 + + override val seriesStatusSelector = ".tsinfo .imptdt:contains(Estado) i" +} diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..9f0bcaaf7f Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a1a5c015e2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..9aad01888f Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..94d5f1c64e Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0281691862 Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/tenkaiscan/res/web_hi_res_512.png new file mode 100644 index 0000000000..63da273720 Binary files /dev/null and b/multisrc/overrides/mangathemesia/tenkaiscan/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/tenkaiscan/src/TenkaiScan.kt b/multisrc/overrides/mangathemesia/tenkaiscan/src/TenkaiScan.kt new file mode 100644 index 0000000000..8c4703212d --- /dev/null +++ b/multisrc/overrides/mangathemesia/tenkaiscan/src/TenkaiScan.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.extension.es.tenkaiscan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import java.text.SimpleDateFormat +import java.util.Locale + +class TenkaiScan : MangaThemesia( + "TenkaiScan", + "https://tenkaiscan.net", + "es", + dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("en")), +) diff --git a/multisrc/overrides/mangathemesia/tsundokutraducoes/src/TsundokuTraducoes.kt b/multisrc/overrides/mangathemesia/tsundokutraducoes/src/TsundokuTraducoes.kt index 66e4a8bdaa..3211e6c8aa 100644 --- a/multisrc/overrides/mangathemesia/tsundokutraducoes/src/TsundokuTraducoes.kt +++ b/multisrc/overrides/mangathemesia/tsundokutraducoes/src/TsundokuTraducoes.kt @@ -14,7 +14,7 @@ class TsundokuTraducoes : MangaThemesia( dateFormat = SimpleDateFormat("MMMMM d, yyyy", Locale("pt", "BR")), ) { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(1, 2, TimeUnit.SECONDS) .build() diff --git a/multisrc/overrides/mangathemesia/uzaymanga/src/UzayManga.kt b/multisrc/overrides/mangathemesia/uzaymanga/src/UzayManga.kt index 6ce8dcf98f..a1c083e7ec 100644 --- a/multisrc/overrides/mangathemesia/uzaymanga/src/UzayManga.kt +++ b/multisrc/overrides/mangathemesia/uzaymanga/src/UzayManga.kt @@ -4,4 +4,9 @@ import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import java.text.SimpleDateFormat import java.util.Locale -class UzayManga : MangaThemesia("Uzay Manga", "https://uzaymanga.com", "tr", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr"))) +class UzayManga : MangaThemesia( + "Uzay Manga", + "https://uzaymanga.com", + "tr", + dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr")), +) diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..d29e1c721f Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..fad5354685 Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..948a8a97e1 Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2c525ee197 Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ceff41dd2 Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/vexmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..67ed7b365e Binary files /dev/null and b/multisrc/overrides/mangathemesia/vexmanga/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mangathemesia/vexmanga/src/VexManga.kt b/multisrc/overrides/mangathemesia/vexmanga/src/VexManga.kt new file mode 100644 index 0000000000..ef25a4e02b --- /dev/null +++ b/multisrc/overrides/mangathemesia/vexmanga/src/VexManga.kt @@ -0,0 +1,74 @@ +package eu.kanade.tachiyomi.extension.ar.vexmanga + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.lang.IllegalArgumentException +import java.util.Calendar + +class VexManga : MangaThemesia( + "فيكس مانجا", + "https://vexmanga.com", + "ar", +) { + override fun searchMangaSelector() = ".listupd .latest-series, ${super.searchMangaSelector()}" + override val sendViewCount = false + override fun chapterListSelector() = ".ulChapterList > a, ${super.chapterListSelector()}" + + override val seriesArtistSelector = + ".tsinfo .imptdt:contains(الرسام) i, ${super.seriesArtistSelector}" + override val seriesAuthorSelector = + ".tsinfo .imptdt:contains(المؤلف) i, ${super.seriesAuthorSelector}" + override val seriesStatusSelector = + ".tsinfo .imptdt:contains(الحالة) i, ${super.seriesStatusSelector}" + override val seriesTypeSelector = + ".tsinfo .imptdt:contains(النوع) i, ${super.seriesTypeSelector}" + + override fun String?.parseStatus() = when { + this == null -> SManga.UNKNOWN + this.contains("مستمر", ignoreCase = true) -> SManga.ONGOING + this.contains("مكتمل", ignoreCase = true) -> SManga.COMPLETED + this.contains("متوقف", ignoreCase = true) -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.select(".chapternum").text() + date_upload = element.select(".chapterdate").text().parseRelativeDate() + } + + private fun String.parseRelativeDate(): Long { + val number = Regex("""(\d+)""").find(this)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + this.contains("أيام", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + this.contains("ساعة", true) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + this.contains("دقائق", true) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + this.contains("أسبوعين", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + this.contains("أشهر", true) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + else -> 0 + } + } + + override fun pageListParse(document: Document): List { + val docString = document.toString() + val imageListJson = JSON_IMAGE_LIST_REGEX.find(docString)?.destructured?.toList()?.get(0).orEmpty() + val imageList = try { + json.parseToJsonElement(imageListJson).jsonArray + } catch (_: IllegalArgumentException) { + emptyList() + } + val scriptPages = imageList.mapIndexed { i, jsonEl -> + Page(i, document.location(), jsonEl.jsonPrimitive.content) + } + + return scriptPages + } +} diff --git a/multisrc/overrides/mangathemesia/walpurgisscan/src/WalpurgisScan.kt b/multisrc/overrides/mangathemesia/walpurgisscan/src/WalpurgisScan.kt index 6ecfc4ade6..f5ed2e7afb 100644 --- a/multisrc/overrides/mangathemesia/walpurgisscan/src/WalpurgisScan.kt +++ b/multisrc/overrides/mangathemesia/walpurgisscan/src/WalpurgisScan.kt @@ -4,6 +4,6 @@ import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import java.text.SimpleDateFormat import java.util.Locale -class WalpurgisScan : MangaThemesia("Walpurgi Scan", "https://www.walpurgiscan.com", "it", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("it"))) { +class WalpurgisScan : MangaThemesia("Walpurgi Scan", "https://www.walpurgiscan.it", "it", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("it"))) { override val id = 6566957355096372149 } diff --git a/multisrc/overrides/mangathemesia/westmanga/src/WestManga.kt b/multisrc/overrides/mangathemesia/westmanga/src/WestManga.kt index 2d460ba156..b8e09bfdb3 100644 --- a/multisrc/overrides/mangathemesia/westmanga/src/WestManga.kt +++ b/multisrc/overrides/mangathemesia/westmanga/src/WestManga.kt @@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.extension.id.westmanga import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import okhttp3.OkHttpClient -import java.util.concurrent.TimeUnit -class WestManga : MangaThemesia("West Manga", "https://westmanga.info", "id") { +class WestManga : MangaThemesia("West Manga", "https://westmanga.org", "id") { // Formerly "West Manga (WP Manga Stream)" override val id = 8883916630998758688 - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .rateLimit(4) .build() diff --git a/multisrc/overrides/mangathemesia/worldromancetranslation/src/WorldRomanceTranslation.kt b/multisrc/overrides/mangathemesia/worldromancetranslation/src/WorldRomanceTranslation.kt index 47629eda5d..3482be3fc1 100644 --- a/multisrc/overrides/mangathemesia/worldromancetranslation/src/WorldRomanceTranslation.kt +++ b/multisrc/overrides/mangathemesia/worldromancetranslation/src/WorldRomanceTranslation.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.id.worldromancetranslation import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Headers import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale @@ -17,10 +16,6 @@ class WorldRomanceTranslation : MangaThemesia( override val hasProjectPage = true - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - override fun mangaDetailsParse(document: Document): SManga { val thumbnail = document.select(seriesThumbnailSelector) diff --git a/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt b/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt index 00c8ca8118..e764499da9 100644 --- a/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt +++ b/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt @@ -8,13 +8,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import java.util.concurrent.TimeUnit class xCaliBRScans : MangaThemesia("xCaliBR Scans", "https://xcalibrscans.com", "en") { - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + override val client: OkHttpClient = super.client.newBuilder() .addInterceptor(AntiScrapInterceptor()) .rateLimit(2) .build() @@ -43,7 +40,7 @@ class xCaliBRScans : MangaThemesia("xCaliBR Scans", "https://xcalibrscans.com", } } - return imgUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + return imgUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) } } private fun parseAntiScrapScramble(element: Element, destination: MutableList) { diff --git a/multisrc/overrides/mmrcms/zahard/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/zahard/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/zahard/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/mmrcms/zahard/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/zahard/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/zahard/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/mmrcms/zahard/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/zahard/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/zahard/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/mmrcms/zahard/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/zahard/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/zahard/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mmrcms/zahard/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangathemesia/zahard/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/mangathemesia/zahard/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/mmrcms/zahard/res/web_hi_res_512.png b/multisrc/overrides/mangathemesia/zahard/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/mmrcms/zahard/res/web_hi_res_512.png rename to multisrc/overrides/mangathemesia/zahard/res/web_hi_res_512.png diff --git a/multisrc/overrides/mangathemesia/zahard/src/Zahard.kt b/multisrc/overrides/mangathemesia/zahard/src/Zahard.kt new file mode 100644 index 0000000000..641e9093c7 --- /dev/null +++ b/multisrc/overrides/mangathemesia/zahard/src/Zahard.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.extension.en.zahard + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request + +class Zahard : MangaThemesia( + "Zahard", + "https://zahard.xyz", + "en", + mangaUrlDirectory = "/library", +) { + override val versionId = 2 + + override val supportsLatest = false + + override val pageSelector = "div#chapter_imgs img" + + override fun searchMangaNextPageSelector() = "a[rel=next]" + + override fun chapterListSelector() = "#chapterlist > ul > a" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegment(mangaUrlDirectory.substring(1)) + .addQueryParameter("search", query) + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } + + override fun getFilterList() = FilterList() +} diff --git a/multisrc/overrides/mangaworld/default/AndroidManifest.xml b/multisrc/overrides/mangaworld/default/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/multisrc/overrides/mangaworld/default/AndroidManifest.xml +++ b/multisrc/overrides/mangaworld/default/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/multisrc/overrides/mccms/haoman6/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2445529494..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 177f76bc9e..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index abf8ee911b..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8462b7bdbf..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 8ba646a3aa..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/res/web_hi_res_512.png b/multisrc/overrides/mccms/haoman6/res/web_hi_res_512.png deleted file mode 100644 index 7f1d38eb17..0000000000 Binary files a/multisrc/overrides/mccms/haoman6/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6/src/Haoman6.kt b/multisrc/overrides/mccms/haoman6/src/Haoman6.kt deleted file mode 100644 index 0f67d99ff5..0000000000 --- a/multisrc/overrides/mccms/haoman6/src/Haoman6.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.haoman6 - -import eu.kanade.tachiyomi.multisrc.mccms.MCCMSWeb -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga - -class Haoman6 : MCCMSWeb("好漫6", "https://www.haoman6.com") { - override fun SManga.cleanup() = apply { - description = description?.substringBefore(title) - title = title.removeSuffix("(最新在线)").removeSuffix("-") - } - - override fun pageListRequest(chapter: SChapter) = - GET(baseUrl + chapter.url, headers) - - override val lazyLoadImageAttr = "mob-ec" -} diff --git a/multisrc/overrides/mccms/haoman6glens/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6glens/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 19c085465c..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6glens/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 85b1119b87..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6glens/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e8d38996ee..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 990f59368c..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 75b64e69f6..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/res/web_hi_res_512.png b/multisrc/overrides/mccms/haoman6glens/res/web_hi_res_512.png deleted file mode 100644 index 2c881f6d80..0000000000 Binary files a/multisrc/overrides/mccms/haoman6glens/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mccms/haoman6glens/src/Haoman6glens.kt b/multisrc/overrides/mccms/haoman6glens/src/Haoman6glens.kt deleted file mode 100644 index 7b0ff5e587..0000000000 --- a/multisrc/overrides/mccms/haoman6glens/src/Haoman6glens.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.haoman6glens - -import eu.kanade.tachiyomi.multisrc.mccms.MCCMSWeb -import eu.kanade.tachiyomi.source.model.SManga - -class Haoman6glens : MCCMSWeb("好漫6 (g-lens)", "https://www.g-lens.com") { - override fun SManga.cleanup() = apply { - title = title.removeSuffix("_").removeSuffix("-").removeSuffix("漫画") - } - - override val lazyLoadImageAttr = "pc-ec" -} diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bffcc3df0e..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f2f352ef2f..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 82d3d4eac1..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1aae40c6dd..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 375ca9c05f..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/fallenangelsscans/res/web_hi_res_512.png b/multisrc/overrides/mmrcms/fallenangelsscans/res/web_hi_res_512.png deleted file mode 100644 index 8d2327466c..0000000000 Binary files a/multisrc/overrides/mmrcms/fallenangelsscans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mmrcms/frscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 17656f64fd..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mmrcms/frscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c463b18fe0..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/frscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3de26b4c6b..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/frscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5f682d92d2..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/frscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 45062e7025..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/frscan/res/web_hi_res_512.png b/multisrc/overrides/mmrcms/frscan/res/web_hi_res_512.png deleted file mode 100644 index 48ca32cd1d..0000000000 Binary files a/multisrc/overrides/mmrcms/frscan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mmrcms/leomanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ed095c35db..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mmrcms/leomanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f1405f306b..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/leomanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 359b7a9765..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7a9d75ece1..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 717a837631..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/leomanga/res/web_hi_res_512.png b/multisrc/overrides/mmrcms/leomanga/res/web_hi_res_512.png deleted file mode 100644 index 5140c569e2..0000000000 Binary files a/multisrc/overrides/mmrcms/leomanga/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/mangafr/src/MangaFR.kt b/multisrc/overrides/mmrcms/mangafr/src/MangaFR.kt new file mode 100644 index 0000000000..83328562d1 --- /dev/null +++ b/multisrc/overrides/mmrcms/mangafr/src/MangaFR.kt @@ -0,0 +1,55 @@ +package eu.kanade.tachiyomi.extension.fr.mangafr + +import eu.kanade.tachiyomi.multisrc.mmrcms.MMRCMS +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Response +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +class MangaFR : MMRCMS("Manga-FR", "https://manga-fr.cc", "fr") { + override fun mangaDetailsParse(response: Response): SManga { + return super.mangaDetailsParse(response).apply { + title = title.replace("Chapitres ", "") + } + } + + override fun nullableChapterFromElement(element: Element): SChapter? { + val chapter = SChapter.create() + + val titleWrapper = element.select("[class^=chapter-title-rtl]").first()!! + val chapterElement = titleWrapper.getElementsByTag("a")!! + val url = chapterElement.attr("href") + + chapter.url = getUrlWithoutBaseUrl(url) + + // Construct chapter names + // Before -> Scan VF: + // Now -> Chapitre : OR Chapitre + val chapterText = chapterElement.text() + val numberRegex = Regex("""[1-9]\d*(\.\d+)*""") + val chapterNumber = numberRegex.find(chapterText)?.value.orEmpty() + val chapterTitle = titleWrapper.getElementsByTag("em")!!.text() + if (chapterTitle.toIntOrNull() != null) { + chapter.name = "Chapitre $chapterNumber" + } else { + chapter.name = "Chapitre $chapterNumber : $chapterTitle" + } + + // Parse date + val dateText = element.getElementsByClass("date-chapter-title-rtl").text().trim() + + chapter.date_upload = runCatching { + dateFormat.parse(dateText)?.time + }.getOrNull() ?: 0L + + return chapter + } + + companion object { + val dateFormat by lazy { + SimpleDateFormat("d MMM. yyyy", Locale.US) + } + } +} diff --git a/multisrc/overrides/mmrcms/mangascan/src/MangaScan.kt b/multisrc/overrides/mmrcms/mangascan/src/MangaScan.kt new file mode 100644 index 0000000000..41a67e3209 --- /dev/null +++ b/multisrc/overrides/mmrcms/mangascan/src/MangaScan.kt @@ -0,0 +1,17 @@ +package eu.kanade.tachiyomi.extension.fr.mangascan + +import eu.kanade.tachiyomi.multisrc.mmrcms.MMRCMS +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Page +import okhttp3.Request + +class MangaScan : MMRCMS("Manga-Scan", "https://manga-scan.me", "fr") { + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", baseUrl) + .set("Accept", "image/avif,image/webp,*/*") + .build() + + return GET(page.imageUrl!!, newHeaders) + } +} diff --git a/multisrc/overrides/mmrcms/scanone/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mmrcms/scanone/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 01d62d703e..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/scanone/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mmrcms/scanone/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 938684b6ce..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/scanone/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/scanone/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 80ba0f8102..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/scanone/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/scanone/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5f6803bdeb..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/scanone/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mmrcms/scanone/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ea931f7edb..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mmrcms/scanone/res/web_hi_res_512.png b/multisrc/overrides/mmrcms/scanone/res/web_hi_res_512.png deleted file mode 100644 index a0f97bf081..0000000000 Binary files a/multisrc/overrides/mmrcms/scanone/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/monochrome/default/AndroidManifest.xml b/multisrc/overrides/monochrome/default/AndroidManifest.xml index c1dc6e6eaa..37d1fedbdc 100644 --- a/multisrc/overrides/monochrome/default/AndroidManifest.xml +++ b/multisrc/overrides/monochrome/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - - + diff --git a/multisrc/overrides/multichan/henchan/src/HenChan.kt b/multisrc/overrides/multichan/henchan/src/HenChan.kt index 2c3fc67960..8c5b6693b0 100644 --- a/multisrc/overrides/multichan/henchan/src/HenChan.kt +++ b/multisrc/overrides/multichan/henchan/src/HenChan.kt @@ -28,9 +28,9 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableSource { +class HenChan : MultiChan("HenChan", "https://xxxxx.hentaichan.live", "ru"), ConfigurableSource { - override val id: Long = 5504588601186153612 + override val id = 5504588601186153612 private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) @@ -40,6 +40,8 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS override val baseUrl = domain + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga/newest?offset=${20 * (page - 1)}") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = if (query.isNotEmpty()) { "$baseUrl/?do=search&subaction=search&story=$query&search_start=$page" @@ -55,6 +57,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS genres += (if (f.isExcluded()) "-" else "") + f.id + '+' } } + else -> return@forEach } } @@ -65,6 +68,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS is OrderBy -> { order = filter.toUriPartWithGenres() } + else -> return@forEach } } @@ -75,6 +79,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS is OrderBy -> { order = filter.toUriPartWithoutGenres() } + else -> return@forEach } } @@ -304,10 +309,12 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("rpg"), Genre("scat"), Genre("shemale"), + Genre("shimaidon"), Genre("shooter"), Genre("simulation"), Genre("skinsuit"), Genre("tomboy"), + Genre("tomgirl"), Genre("x-ray"), Genre("алкоголь"), Genre("анал"), @@ -346,7 +353,6 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("гг_парень"), Genre("гипноз"), Genre("глубокий_минет"), - Genre("горничные"), Genre("горячий_источник"), Genre("грудастая_лоли"), Genre("групповой_секс"), @@ -386,6 +392,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("мать"), Genre("мейдочки"), Genre("мерзкий_дядька"), + Genre("минет"), Genre("много_девушек"), Genre("молоко"), Genre("монашки"), @@ -400,8 +407,10 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("новелла"), Genre("обмен_партнерами"), Genre("обмен_телами"), + Genre("обычный_секс"), Genre("огромная_грудь"), Genre("огромный_член"), + Genre("оплодотворение"), Genre("остановка_времени"), Genre("парень_пассив"), Genre("переодевание"), @@ -411,6 +420,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("подглядывание"), Genre("подчинение"), Genre("похищение"), + Genre("презерватив"), Genre("принуждение"), Genre("прозрачная_одежда"), Genre("проникновение_в_матку"), @@ -432,12 +442,14 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("толстушки"), Genre("трап"), Genre("тётя"), + Genre("умеренная_жестокость"), Genre("учитель_и_ученик"), Genre("ушастые"), Genre("фантазии"), Genre("фантастика"), Genre("фемдом"), Genre("фестиваль"), + Genre("фетиш"), Genre("фистинг"), Genre("фурри"), Genre("футанари"), @@ -452,6 +464,7 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS Genre("школьники"), Genre("школьницы"), Genre("школьный_купальник"), + Genre("щекотка"), Genre("эксгибиционизм"), Genre("эльфы"), Genre("эччи"), @@ -470,8 +483,13 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS dialogTitle = DOMAIN_TITLE setOnPreferenceChangeListener { _, newValue -> try { - val res = preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() - Toast.makeText(screen.context, "Для смены домена необходимо перезапустить приложение с полной остановкой.", Toast.LENGTH_LONG).show() + val res = + preferences.edit().putString(DOMAIN_TITLE, newValue as String).commit() + Toast.makeText( + screen.context, + "Для смены домена необходимо перезапустить приложение с полной остановкой.", + Toast.LENGTH_LONG, + ).show() res } catch (e: Exception) { e.printStackTrace() @@ -483,6 +501,6 @@ class HenChan : MultiChan("HenChan", "http://y.hchan.live", "ru"), ConfigurableS companion object { private const val DOMAIN_TITLE = "Домен" - private const val DOMAIN_DEFAULT = "http://y.hchan.live" + private const val DOMAIN_DEFAULT = "https://xxxxx.hentaichan.live" } } diff --git a/multisrc/overrides/mymangacms/default/AndroidManifest.xml b/multisrc/overrides/mymangacms/default/AndroidManifest.xml index 8a95129799..29f5f8a850 100644 --- a/multisrc/overrides/mymangacms/default/AndroidManifest.xml +++ b/multisrc/overrides/mymangacms/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - \ No newline at end of file + diff --git a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mymangacms/lkdtt/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c42b4063a4..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mymangacms/lkdtt/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b2150bb896..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dc243cd2aa..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 580330e4f4..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d00d37442..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/res/web_hi_res_512.png b/multisrc/overrides/mymangacms/lkdtt/res/web_hi_res_512.png deleted file mode 100644 index 57120ab13f..0000000000 Binary files a/multisrc/overrides/mymangacms/lkdtt/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/mymangacms/lkdtt/src/LKDTT.kt b/multisrc/overrides/mymangacms/lkdtt/src/LKDTT.kt deleted file mode 100644 index e1a87aca3f..0000000000 --- a/multisrc/overrides/mymangacms/lkdtt/src/LKDTT.kt +++ /dev/null @@ -1,121 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.lkdtt - -import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -class LKDTT : MyMangaCMS("LKDTT", "https://lkdttee.com", "vi") { - override val dateFormatter = SimpleDateFormat("dd/MM/yy", Locale.US).apply { - timeZone = TimeZone.getTimeZone(super.timeZone) - } - - override fun dateUpdatedParser(date: String): Long = - runCatching { super.dateUpdatedParser(date.split(" - ")[1]) }.getOrNull() ?: 0L - - override fun getGenreList() = listOf( - Genre("Học đường", 1), - Genre("Hài hước", 2), - Genre("Cổ Đại", 3), - Genre("Hiện đại", 4), - Genre("Kinh dị", 5), - Genre("Tổng tài", 6), - Genre("Xuyên không", 7), - Genre("Manhua", 8), - Genre("Manhwa", 9), - Genre("Mystery", 10), - Genre("One shot", 11), - Genre("Smut", 12), - Genre("Webtoon", 13), - Genre("Yaoi", 14), - Genre("Yuri", 15), - Genre("Trinh Thám", 16), - Genre("Tình Cảm", 17), - Genre("Drama", 18), - Genre("Comedy", 19), - Genre("Fantasy", 20), - Genre("Novel", 21), - Genre("Action", 22), - Genre("Manga", 23), - Genre("Đam Mỹ", 24), - Genre("Trọng Sinh", 25), - Genre("Ngôn Tình", 26), - Genre("Phiêu Lưu", 27), - Genre("Boy Love", 28), - Genre("giới giải trí", 29), - Genre("đô thị", 30), - Genre("Romance", 31), - Genre("Đô Thị", 32), - Genre("Shoujo", 33), - Genre("Historical", 34), - Genre("Slice of life", 35), - Genre("Mature", 36), - Genre("GL", 37), - Genre("Adult", 38), - Genre("Huyền huyễn", 39), - Genre("Baby", 40), - Genre("Tragedy", 41), - Genre("Truyện Màu", 42), - Genre("School Life", 43), - Genre("Josei", 44), - Genre("Oneshot", 45), - Genre("Gender Bender", 46), - Genre("Nữ cường", 47), - Genre("Harem", 48), - Genre("Reverse Harem", 49), - Genre("Isekai", 50), - Genre("Adventure", 51), - Genre("Chuyển Sinh", 52), - Genre("Đại Nữ Chủ", 53), - Genre("Shounen", 54), - Genre("Sports", 55), - Genre("Sủng Ngọt", 56), - Genre("Truyện 18+", 57), - Genre("Trung Cổ", 58), - Genre("Ma Thuật", 59), - Genre("Webtoons", 60), - Genre("Xuyên", 61), - Genre("Ngôn", 62), - Genre("Tiểu Bạch Thỏ", 63), - Genre("Sủng", 65), - Genre("Trùng Sinh", 66), - Genre("Ma Cà Rồng", 67), - Genre("Tái Sinh", 68), - Genre("Quân Nhân", 69), - Genre("Showbiz", 70), - Genre("Comic", 71), - Genre("Phép Thuật", 72), - Genre("Psychological", 73), - Genre("Supernatural", 74), - Genre("Lãng Mạn", 75), - Genre("Gender", 76), - Genre("Bender", 77), - Genre("Vườn Trường", 78), - Genre("Magic", 79), - Genre("Nhân Thú", 80), - Genre("Soft Yaoi", 81), - Genre("Hôn Nhân Hợp Đồng", 82), - Genre("Cưới Trước Yêu Sau", 83), - Genre("Bi Kịch", 84), - Genre("Horror", 85), - Genre("Reincarnation", 86), - Genre("Hồi Sinh", 87), - Genre("Hoàng Gia", 88), - Genre("Giả Tưởng", 89), - Genre("Xuyên Sách", 90), - Genre("Hài", 91), - Genre("Ngọt", 92), - Genre("Nam Cường", 93), - Genre("Chủ Nam", 94), - Genre("Minh Tinh", 95), - Genre("Cổ Trang", 96), - Genre("Xuyên Game", 97), - Genre("Villainess", 98), - Genre("Cung Đấu", 99), - Genre("Hành Động", 100), - Genre("Truyện Tranh", 101), - Genre("Adaptation", 102), - Genre("Magi", 103), - Genre("Âu Cổ", 104), - ) -} diff --git a/multisrc/overrides/nepnep/mangasee/AndroidManifest.xml b/multisrc/overrides/nepnep/mangasee/AndroidManifest.xml index f2bfc55a6a..4dd2c5b71f 100644 --- a/multisrc/overrides/nepnep/mangasee/AndroidManifest.xml +++ b/multisrc/overrides/nepnep/mangasee/AndroidManifest.xml @@ -1,7 +1,6 @@ - - + + - + - + - diff --git a/multisrc/overrides/readallcomics/readallcomicscom/src/ReadAllComicsCom.kt b/multisrc/overrides/readallcomics/readallcomicscom/src/ReadAllComicsCom.kt index 9819226c4b..967ab5a892 100644 --- a/multisrc/overrides/readallcomics/readallcomicscom/src/ReadAllComicsCom.kt +++ b/multisrc/overrides/readallcomics/readallcomicscom/src/ReadAllComicsCom.kt @@ -17,6 +17,8 @@ class ReadAllComicsCom : ReadAllComics("ReadAllComics", "https://readallcomics.c } } + override fun pageListSelector() = "body img:not(body div[id=\"logo\"] img)" + companion object { private val titleRegex = Regex("""^([a-zA-Z_.\s\-–:]*)""") } diff --git a/multisrc/overrides/readerfront/default/AndroidManifest.xml b/multisrc/overrides/readerfront/default/AndroidManifest.xml index 1fce661c89..b53f52f6e4 100644 --- a/multisrc/overrides/readerfront/default/AndroidManifest.xml +++ b/multisrc/overrides/readerfront/default/AndroidManifest.xml @@ -1,6 +1,5 @@ - + ().getSharedPreferences("source_$id", 0x0000) + } + + private var domain: String? = if (preferences.getBoolean(redirect_PREF, true)) "https://senkognito.com" else "https://senkuro.com" + override val baseUrl: String = domain.toString() + override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + val domainRedirect = androidx.preference.CheckBoxPreference(screen.context).apply { + key = redirect_PREF + title = "Домен Senkognito" + summary = "Отключите если домен Senkognito недоступен в браузере/WebView." + setDefaultValue(true) + setOnPreferenceChangeListener { _, newValue -> + val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой." + Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show() + true + } + } + screen.addPreference(domainRedirect) + } + + companion object { + private const val redirect_PREF = "domainRedirect" + } +} diff --git a/multisrc/overrides/senkuro/senkuro/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/senkuro/senkuro/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..c7b04fec43 Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/senkuro/senkuro/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/senkuro/senkuro/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..1452cbd4cc Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/senkuro/senkuro/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/senkuro/senkuro/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..37820c9a83 Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/senkuro/senkuro/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/senkuro/senkuro/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..944881d337 Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/senkuro/senkuro/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/senkuro/senkuro/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b4dac12d20 Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/senkuro/senkuro/res/web_hi_res_512.png b/multisrc/overrides/senkuro/senkuro/res/web_hi_res_512.png new file mode 100644 index 0000000000..359430854a Binary files /dev/null and b/multisrc/overrides/senkuro/senkuro/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/senkuro/senkuro/src/Senkuro.kt b/multisrc/overrides/senkuro/senkuro/src/Senkuro.kt new file mode 100644 index 0000000000..31a1645b5c --- /dev/null +++ b/multisrc/overrides/senkuro/senkuro/src/Senkuro.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.extension.ru.senkuro + +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.multisrc.senkuro.Senkuro + +class Senkuro : Senkuro("Senkuro", "https://senkuro.com", "ru") { + override fun setupPreferenceScreen(screen: PreferenceScreen) {} +} diff --git a/multisrc/overrides/sinmh/qinqin/src/Qinqin.kt b/multisrc/overrides/sinmh/qinqin/src/Qinqin.kt index 93044f834a..7003282f1d 100644 --- a/multisrc/overrides/sinmh/qinqin/src/Qinqin.kt +++ b/multisrc/overrides/sinmh/qinqin/src/Qinqin.kt @@ -9,7 +9,7 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -class Qinqin : SinMH("亲亲漫画", "https://www.acgqd.com") { +class Qinqin : SinMH("亲亲漫画", "https://www.acgud.com") { override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/post/?page=$page", headers) @@ -17,7 +17,7 @@ class Qinqin : SinMH("亲亲漫画", "https://www.acgqd.com") { override fun Elements.sectionsDescending() = this - // https://www.acgqd.com/js/jmzz20191018.js + // https://www.acgud.com/js/jmzz20191018.js override fun parsePageImages(chapterImages: String): List { val key = SecretKeySpec("cxNB23W8xzKJV26O".toByteArray(), "AES") val iv = IvParameterSpec("opb4x7z21vg1f3gI".toByteArray()) diff --git a/multisrc/overrides/webtoons/webtoons/AndroidManifest.xml b/multisrc/overrides/webtoons/webtoons/AndroidManifest.xml index 14082a252f..1ce564382b 100644 --- a/multisrc/overrides/webtoons/webtoons/AndroidManifest.xml +++ b/multisrc/overrides/webtoons/webtoons/AndroidManifest.xml @@ -1,6 +1,5 @@ - + SManga.ONGOING + contains("TERMINÉ") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } +} + class WebtoonsZH : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "zh-Hant", "zh-hant", "zh_TW", SimpleDateFormat("yyyy/MM/dd", Locale.TRADITIONAL_CHINESE)) { // Due to lang code getting more specific override val id: Long = 2959982438613576472 diff --git a/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt b/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt index 77f480844a..0ae869e4ec 100644 --- a/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt +++ b/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt @@ -57,10 +57,10 @@ open class WebtoonsSrc( var pages = document.select("div#_imageList > img").mapIndexed { i, element -> Page(i, "", element.attr("data-url")) } if (showAuthorsNotesPref()) { - val note = document.select("div.comment_area div.info_area p").text() + val note = document.select("div.creator_note p.author_text").text() if (note.isNotEmpty()) { - val creator = document.select("div.creator_note span.author a").text().trim() + val creator = document.select("div.creator_note a.author_name span").text().trim() pages = pages + Page( pages.size, diff --git a/multisrc/overrides/wpcomics/nettruyen/src/NetTruyen.kt b/multisrc/overrides/wpcomics/nettruyen/src/NetTruyen.kt index d762c8f05e..8858284814 100644 --- a/multisrc/overrides/wpcomics/nettruyen/src/NetTruyen.kt +++ b/multisrc/overrides/wpcomics/nettruyen/src/NetTruyen.kt @@ -9,7 +9,7 @@ import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale -class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenmax.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { +class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenus.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", baseUrl).build()) override fun getFilterList(): FilterList { diff --git a/multisrc/overrides/wpcomics/nhattruyen/src/NhatTruyen.kt b/multisrc/overrides/wpcomics/nhattruyen/src/NhatTruyen.kt index 87ef107c31..210eb0c087 100644 --- a/multisrc/overrides/wpcomics/nhattruyen/src/NhatTruyen.kt +++ b/multisrc/overrides/wpcomics/nhattruyen/src/NhatTruyen.kt @@ -9,7 +9,7 @@ import okhttp3.Request import java.text.SimpleDateFormat import java.util.Locale -class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenmin.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { +class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenplus.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) { override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/the-loai?keyword=$query&page=$page", headers) override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headersBuilder().add("Referer", baseUrl).build()) diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..dd4c016e36 Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d45ea5affe Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c29b6ef6ee Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ea4ba5a1a Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..53c2248d96 Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/animexnovel/res/web_hi_res_512.png new file mode 100644 index 0000000000..1d9e5f43bb Binary files /dev/null and b/multisrc/overrides/zeistmanga/animexnovel/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/animexnovel/src/AnimeXNovel.kt b/multisrc/overrides/zeistmanga/animexnovel/src/AnimeXNovel.kt new file mode 100644 index 0000000000..2968737b28 --- /dev/null +++ b/multisrc/overrides/zeistmanga/animexnovel/src/AnimeXNovel.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.extension.pt.animexnovel + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response + +class AnimeXNovel : ZeistManga("AnimeXNovel", "https://www.animexnovel.com", "pt-BR") { + + override val mangaCategory: String = "Manga" + + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select("div.PopularPosts div.grid > figure").map { element -> + SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption > a")!!.text() + setUrlWithoutDomain(element.selectFirst("figcaption > a")!!.attr("href")) + } + }.filter { it.title.contains("[Mangá]") } + + return MangasPage(mangas, false) + } + + override val mangaDetailsSelectorDescription = "div.bc-fff.s1 > h3:contains(Sinopse) ~ div[style=text-align: justify;]" + + private val chapterListSelector = "div:has(> .list-judul:contains(Lista de Capítulos)) div#latest ul > li, div.tab:has(> label:contains(Lista de Capítulos)) div.tab-content ul > li" + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + val chapters = document.select(chapterListSelector) + return chapters.map { + SChapter.create().apply { + name = it.select("a").text() + setUrlWithoutDomain(it.select("a").attr("href")) + } + } + } +} diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2c043a5d9b..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 90af3d5d49..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a3d613f210..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f2a1acae9e..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2d1bb27088..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/datgarscanlation/res/web_hi_res_512.png deleted file mode 100644 index 7795824b82..0000000000 Binary files a/multisrc/overrides/zeistmanga/datgarscanlation/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/zeistmanga/datgarscanlation/src/DatGarScanlation.kt b/multisrc/overrides/zeistmanga/datgarscanlation/src/DatGarScanlation.kt deleted file mode 100644 index 1f9e3d9602..0000000000 --- a/multisrc/overrides/zeistmanga/datgarscanlation/src/DatGarScanlation.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.datgarscanlation - -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language -import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga -import org.jsoup.nodes.Document - -class DatGarScanlation : ZeistManga("DatGarScanlation", "https://datgarscanlation.blogspot.com", "es") { - - override val hasFilters = true - - private val altChapterFeedRegex = """label\s*=\s*'([^']+)'""".toRegex() - private val altScriptSelector = "#latest > script" - - override fun getApiUrl(doc: Document): String { - var chapterRegex = chapterFeedRegex - var script = doc.selectFirst(scriptSelector) - - if (script == null) { - script = doc.selectFirst(altScriptSelector)!! - chapterRegex = altChapterFeedRegex - } - - val feed = chapterRegex - .find(script.html()) - ?.groupValues?.get(1) - ?: throw Exception("Failed to find chapter feed") - - val url = apiUrl(feed) - .addQueryParameter("start-index", "2") // Only get chapters - .addQueryParameter("max-results", "999999") // Get all chapters - .build() - - return url.toString() - } - - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) -} diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..edab19abfa Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a5bc08cc45 Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..958bd9913d Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4598836c4e Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bbc622fb67 Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/elevenscanlator/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/elevenscanlator/res/web_hi_res_512.png new file mode 100644 index 0000000000..a5fb63c5d6 Binary files /dev/null and b/multisrc/overrides/zeistmanga/elevenscanlator/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..514dd275d3 Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..a646eb2e38 Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e0e8390a80 Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ca69b02953 Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dfb208bc9f Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/guildatierdraw/res/web_hi_res_512.png new file mode 100644 index 0000000000..db4eb55a5a Binary files /dev/null and b/multisrc/overrides/zeistmanga/guildatierdraw/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/guildatierdraw/src/GuildaTierDraw.kt b/multisrc/overrides/zeistmanga/guildatierdraw/src/GuildaTierDraw.kt new file mode 100644 index 0000000000..3b5f7bee62 --- /dev/null +++ b/multisrc/overrides/zeistmanga/guildatierdraw/src/GuildaTierDraw.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.pt.guildatierdraw + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga + +class GuildaTierDraw : ZeistManga("Guilda Tier Draw", "https://www.guildatierdraw.com", "pt-BR") { + override val mangaDetailsSelectorDescription = "#Sinopse" +} diff --git a/multisrc/overrides/zeistmanga/hijala/src/Hijala.kt b/multisrc/overrides/zeistmanga/hijala/src/Hijala.kt index a2920a9e8c..8edb8d68ab 100644 --- a/multisrc/overrides/zeistmanga/hijala/src/Hijala.kt +++ b/multisrc/overrides/zeistmanga/hijala/src/Hijala.kt @@ -1,16 +1,20 @@ package eu.kanade.tachiyomi.extension.ar.hijala import eu.kanade.tachiyomi.multisrc.zeistmanga.Genre -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.MangasPage +import okhttp3.Request +import okhttp3.Response class Hijala : ZeistManga("Hijala", "https://hijala.blogspot.com", "ar") { override val hasFilters = true + override val hasLanguageFilter = false - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) + override val supportsLatest = false + + override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) + override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response) override fun getGenreList(): List = listOf( Genre("أكشن", "Action"), diff --git a/multisrc/overrides/zeistmanga/klmanhua/src/KLManhua.kt b/multisrc/overrides/zeistmanga/klmanhua/src/KLManhua.kt index 8b34b50b1c..dc150c0228 100644 --- a/multisrc/overrides/zeistmanga/klmanhua/src/KLManhua.kt +++ b/multisrc/overrides/zeistmanga/klmanhua/src/KLManhua.kt @@ -1,13 +1,9 @@ package eu.kanade.tachiyomi.extension.id.klmanhua -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga class KLManhua : ZeistManga("KLManhua", "https://klmanhua.blogspot.com", "id") { override val hasFilters = true - - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) + override val hasLanguageFilter = false } diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..04e0b06c1a Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..8fe0d409c7 Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1f43d3b13c Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e9e807cbb6 Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..297bf1665a Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/komikrealm/res/web_hi_res_512.png new file mode 100644 index 0000000000..ab0a2dfc4a Binary files /dev/null and b/multisrc/overrides/zeistmanga/komikrealm/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/komikrealm/src/KomikRealm.kt b/multisrc/overrides/zeistmanga/komikrealm/src/KomikRealm.kt new file mode 100644 index 0000000000..bef6676cf6 --- /dev/null +++ b/multisrc/overrides/zeistmanga/komikrealm/src/KomikRealm.kt @@ -0,0 +1,146 @@ +package eu.kanade.tachiyomi.extension.id.komikrealm + +import eu.kanade.tachiyomi.multisrc.zeistmanga.Genre +import eu.kanade.tachiyomi.multisrc.zeistmanga.Status +import eu.kanade.tachiyomi.multisrc.zeistmanga.Type +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistMangaDto +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistMangaIntl +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import uy.kohesive.injekt.injectLazy + +class KomikRealm : ZeistManga( + "KomikRealm", + "https://www.komikrealm.my.id", + "id", +) { + private val json: Json by injectLazy() + + override val hasFilters = true + + override val hasLanguageFilter = false + + override val chapterCategory = "" + + override fun popularMangaRequest(page: Int): Request { + val url = apiUrl("Project") + .addQueryParameter("orderby", "updated") + .addQueryParameter("max-results", "12") + .build() + + return GET(url, headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + val jsonString = response.body.string() + val result = json.decodeFromString(jsonString) + + val mangas = result.feed?.entry.orEmpty() + .filter { it.category.orEmpty().any { category -> category.term == "Series" } } + .filter { !it.category.orEmpty().any { category -> category.term == "Anime" } } + .map { it.toSManga(baseUrl) } + + return MangasPage(mangas, false) + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val profileManga = document.select(".bigcontent") + val infoManga = profileManga.select("ul.infonime li") + + return SManga.create().apply { + thumbnail_url = profileManga.select("img").first()!!.attr("data-src") + description = profileManga.select(".sinoposis").text() + genre = profileManga.select("div.info-genre > a[rel=tag]") + .joinToString { it.text() } + + infoManga.forEach { + val title = it.select("b").text() + val desc = it.select("span").text() + + when (title) { + "Status" -> status = parseStatus(desc) + "Author" -> author = desc + "Artist" -> artist = desc + } + } + } + } + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + val url = getChapterFeedUrl(document) + + val req = GET(url, headers) + val res = client.newCall(req).execute() + + val jsonString = res.body.string() + val result = json.decodeFromString(jsonString) + + return result.feed?.entry + ?.filter { + !it.category.orEmpty().any { category -> + category.term == "Series" + } + } + ?.map { it.toSChapter(baseUrl) } + ?: throw Exception("Failed to parse from chapter API") + } + + private val imagePageRegex = """(http|https)://[^"]+""".toRegex() + + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + val script = document.select(".post-body > script").html() + val matches = imagePageRegex.findAll(script) + return matches.mapIndexed { i, match -> + Page(i, "", match.value) + }.toList() + } + + private val labelChapterRegex = """var label_chapter = "([^"]+)";""".toRegex() + + override fun getChapterFeedUrl(doc: Document): String { + val script = doc.select(".post-body > script") + val feed = labelChapterRegex.find(script.html()) + ?.groupValues?.get(1) + ?: throw Exception("Failed to find chapter feed") + + return apiUrl(chapterCategory) + .addPathSegments(feed) + .addQueryParameter("max-results", "999999") + .build().toString() + } + + private val intl by lazy { ZeistMangaIntl(lang) } + + override fun getStatusList(): List = listOf( + Status(intl.all, ""), + Status(intl.statusOngoing, "Ongoing"), + Status(intl.statusCompleted, "Completed"), + ) + + override fun getTypeList(): List = listOf( + Type(intl.all, ""), + Type(intl.typeManga, "Manga"), + Type(intl.typeManhua, "Manhua"), + Type(intl.typeManhwa, "Manhwa"), + ) + + override fun getGenreList(): List = listOf( + Genre("Drama", "Drama"), + Genre("Mature", "Mature"), + Genre("Supernatural", "Supernatural"), + ) +} diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..6d2753b61b Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4e550b8b13 Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..3e8b896255 Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0a0278ad1c Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a1ad11d0ab Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/lonertranslations/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/lonertranslations/res/web_hi_res_512.png new file mode 100644 index 0000000000..2e27d7a46c Binary files /dev/null and b/multisrc/overrides/zeistmanga/lonertranslations/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/mangaailand/src/MangaAiLand.kt b/multisrc/overrides/zeistmanga/mangaailand/src/MangaAiLand.kt index 406781b854..3f87781c2c 100644 --- a/multisrc/overrides/zeistmanga/mangaailand/src/MangaAiLand.kt +++ b/multisrc/overrides/zeistmanga/mangaailand/src/MangaAiLand.kt @@ -1,16 +1,14 @@ package eu.kanade.tachiyomi.extension.ar.mangaailand import eu.kanade.tachiyomi.multisrc.zeistmanga.Genre -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga class MangaAiLand : ZeistManga("Manga Ai Land", "https://manga-ai-land.blogspot.com", "ar") { override val hasFilters = true + override val hasLanguageFilter = false - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) + override val chapterCategory = "فصل" override fun getGenreList(): List = listOf( Genre("تراجيدي", "تراجيدي"), diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..abd5e9e5ad Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f186ffa911 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..7fc5145fc2 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..13299100a8 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9abc6abaaf Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/mangasoul/res/web_hi_res_512.png new file mode 100644 index 0000000000..4a40c9f31c Binary files /dev/null and b/multisrc/overrides/zeistmanga/mangasoul/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/mangasoul/src/MangaSoul.kt b/multisrc/overrides/zeistmanga/mangasoul/src/MangaSoul.kt new file mode 100644 index 0000000000..42ebc9ba09 --- /dev/null +++ b/multisrc/overrides/zeistmanga/mangasoul/src/MangaSoul.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.ar.mangasoul + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga + +class MangaSoul : ZeistManga("Manga Soul", "https://www.manga-soul.com", "ar") { + override val mangaDetailsSelectorInfo = ".y6x11p, .y6x11p > b > span" +} diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..338bae3c67 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..280f88901f Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..25495ea8e7 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e696c9415e Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0fc7946ba8 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/mikoroku/res/web_hi_res_512.png new file mode 100644 index 0000000000..27760ce9fa Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikoroku/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/mikoroku/src/MikoRoku.kt b/multisrc/overrides/zeistmanga/mikoroku/src/MikoRoku.kt new file mode 100644 index 0000000000..28d8944e75 --- /dev/null +++ b/multisrc/overrides/zeistmanga/mikoroku/src/MikoRoku.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.extension.id.mikoroku + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response + +class MikoRoku : ZeistManga("MikoRoku", "https://www.mikoroku.web.id", "id") { + + override val popularMangaSelector = "div.PopularPosts article" + override val popularMangaSelectorTitle = ".post-title a" + override val popularMangaSelectorUrl = ".post-title a" + + override val pageListSelector = "article#reader div.separator a" + + override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { + val document = response.asJsoup() + with(document.selectFirst("div.section#main div.widget header")!!) { + thumbnail_url = selectFirst("img")!!.attr("abs:src") + genre = select("aside dl:has(dt:contains(Genre)) dd a") + .joinToString { it.text() } + status = parseStatus(selectFirst("span[data-status]")!!.text()) + } + description = document.select("div.section#main div.widget div.grid #synopsis").text() + } +} diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..86a18ccac5 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..38485b195d Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..6e271f3a48 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..465983f1d6 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ec52f1f0b2 Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/web_hi_res_512.png new file mode 100644 index 0000000000..1c1e9c500f Binary files /dev/null and b/multisrc/overrides/zeistmanga/mikrokosmosfansub/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/mikrokosmosfansub/src/MikrokosmosFansub.kt b/multisrc/overrides/zeistmanga/mikrokosmosfansub/src/MikrokosmosFansub.kt new file mode 100644 index 0000000000..ee773195eb --- /dev/null +++ b/multisrc/overrides/zeistmanga/mikrokosmosfansub/src/MikrokosmosFansub.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.extension.tr.mikrokosmosfansub + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.Jsoup + +class MikrokosmosFansub : ZeistManga("Mikrokosmos Fansub", "https://mikrokosmosfb.blogspot.com", "tr") { + + override val pageListSelector = "div.check-box > script" + + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + val script = document.select(pageListSelector) + val content = script.html().substringAfter("const content = `").substringBefore("`;") + val images = Jsoup.parse(content).select("a") + return images.select("img[src]").mapIndexed { i, img -> + Page(i, "", img.attr("abs:src")) + } + } +} diff --git a/multisrc/overrides/zeistmanga/muslosnosekai/src/MuslosNoSekai.kt b/multisrc/overrides/zeistmanga/muslosnosekai/src/MuslosNoSekai.kt deleted file mode 100644 index fbee9f9d99..0000000000 --- a/multisrc/overrides/zeistmanga/muslosnosekai/src/MuslosNoSekai.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.muslosnosekai - -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language -import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga - -class MuslosNoSekai : ZeistManga("Muslos No Sekai", "https://muslosnosekai.blogspot.com", "es") { - - override val hasFilters = true - - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) -} diff --git a/multisrc/overrides/zeistmanga/noromax/src/Noromax.kt b/multisrc/overrides/zeistmanga/noromax/src/Noromax.kt deleted file mode 100644 index 3a535ea84e..0000000000 --- a/multisrc/overrides/zeistmanga/noromax/src/Noromax.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.noromax - -import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga - -class Noromax : ZeistManga("Noromax", "https://www.noromax.xyz", "id") { - - override val hasFilters = true -} diff --git a/multisrc/overrides/zeistmanga/shiyurasub/src/ShiyuraSub.kt b/multisrc/overrides/zeistmanga/shiyurasub/src/ShiyuraSub.kt index 38870cc9e8..4e310cad44 100644 --- a/multisrc/overrides/zeistmanga/shiyurasub/src/ShiyuraSub.kt +++ b/multisrc/overrides/zeistmanga/shiyurasub/src/ShiyuraSub.kt @@ -1,13 +1,32 @@ package eu.kanade.tachiyomi.extension.id.shiyurasub -import eu.kanade.tachiyomi.multisrc.zeistmanga.Language import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response class ShiyuraSub : ZeistManga("ShiyuraSub", "https://shiyurasub.blogspot.com", "id") { override val hasFilters = true + override val hasLanguageFilter = false - override fun getLanguageList(): List = listOf( - Language(intl.all, ""), - ) + override val supportsLatest = false + + override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) + override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response) + + override val pageListSelector = "main.content article.container" + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val profileManga = document.selectFirst("main.content.post")!! + return SManga.create().apply { + thumbnail_url = profileManga.selectFirst("div.grid img")!!.attr("abs:src") + description = profileManga.select("#synopsis").text() + genre = profileManga.select("div.my-5 > a[rel=tag]") + .joinToString { it.text() } + } + } } diff --git a/multisrc/overrides/zeistmanga/sobatmanku/SobatManKu.kt b/multisrc/overrides/zeistmanga/sobatmanku/SobatManKu.kt new file mode 100644 index 0000000000..bd08c5c1d0 --- /dev/null +++ b/multisrc/overrides/zeistmanga/sobatmanku/SobatManKu.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.id.sobatmanku + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.SChapter +import okhttp3.Response + +class SobatManKu : ZeistManga("SobatManKu", "https://www.sobatmanku19.site", "id") { + + override val hasFilters = true + + override fun chapterListParse(response: Response): List { + return super.chapterListParse(response).onEach { + // fix some chapter name + it.name = it.name.run { + substring(indexOf("Chapter")) + } + } + } +} diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..68a17f51ba Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..6c936355de Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..c7699dd2ac Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c8faabf661 Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4b9834a9ca Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/sobatmanku/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/sobatmanku/res/web_hi_res_512.png new file mode 100644 index 0000000000..b56a5b3158 Binary files /dev/null and b/multisrc/overrides/zeistmanga/sobatmanku/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/tooncubus/src/Tooncubus.kt b/multisrc/overrides/zeistmanga/tooncubus/src/Tooncubus.kt index fa681425b5..660343fd62 100644 --- a/multisrc/overrides/zeistmanga/tooncubus/src/Tooncubus.kt +++ b/multisrc/overrides/zeistmanga/tooncubus/src/Tooncubus.kt @@ -3,10 +3,8 @@ package eu.kanade.tachiyomi.extension.id.tooncubus import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Response -import org.jsoup.nodes.Document class Tooncubus : ZeistManga("Tooncubus", "https://www.tooncubus.top", "id") { @@ -15,7 +13,7 @@ class Tooncubus : ZeistManga("Tooncubus", "https://www.tooncubus.top", "id") { override fun chapterListParse(response: Response): List { return response.asJsoup().selectFirst("ul.series-chapterlist")!!.select("div.flexch-infoz").map { element -> SChapter.create().apply { - name = element.select("span")!!.text() + name = element.select("span").text() url = element.select("a").attr("href") // The website uses another domain for reading } } @@ -24,13 +22,4 @@ class Tooncubus : ZeistManga("Tooncubus", "https://www.tooncubus.top", "id") { override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) override fun getChapterUrl(chapter: SChapter) = chapter.url - - override fun mangaDetailsParse(document: Document): SManga { - val profileManga = document.selectFirst(".grid.gtc-235fr")!! - return SManga.create().apply { - thumbnail_url = profileManga.selectFirst("img")!!.attr("src") - genre = profileManga.select("div.mt-15 > a[rel=tag]") - .joinToString { it.text() } - } - } } diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0a8138ab72 Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..fc3d89063d Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1218856b03 Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..71a80449f6 Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1ced8827cd Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/tyrantscans/res/web_hi_res_512.png new file mode 100644 index 0000000000..9fb6f27cd3 Binary files /dev/null and b/multisrc/overrides/zeistmanga/tyrantscans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zeistmanga/tyrantscans/src/TyrantScans.kt b/multisrc/overrides/zeistmanga/tyrantscans/src/TyrantScans.kt new file mode 100644 index 0000000000..20f91998a9 --- /dev/null +++ b/multisrc/overrides/zeistmanga/tyrantscans/src/TyrantScans.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.pt.tyrantscans + +import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga +import eu.kanade.tachiyomi.source.model.MangasPage +import okhttp3.Request +import okhttp3.Response + +class TyrantScans : ZeistManga("Tyrant Scans", "https://www.tyrantscans.com", "pt-BR") { + + override val supportsLatest = false + + override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) + + override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response) +} diff --git a/multisrc/overrides/zeistmanga/yokai/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/yokai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f85f4182c5 Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/yokai/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/yokai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..aad38c9745 Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/yokai/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..ec169d5e6b Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7c49f55f5c Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..ed18be3646 Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/zeistmanga/yokai/res/web_hi_res_512.png b/multisrc/overrides/zeistmanga/yokai/res/web_hi_res_512.png new file mode 100644 index 0000000000..0214dbe9b5 Binary files /dev/null and b/multisrc/overrides/zeistmanga/yokai/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/zmanga/alceascan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/zmanga/alceascan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 215d6ed6ba..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zmanga/alceascan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/zmanga/alceascan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 15a3c9940a..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zmanga/alceascan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/zmanga/alceascan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index db8b32de46..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zmanga/alceascan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/zmanga/alceascan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 81aabc0ab6..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zmanga/alceascan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/zmanga/alceascan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9c5c3babf7..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/zmanga/alceascan/res/web_hi_res_512.png b/multisrc/overrides/zmanga/alceascan/res/web_hi_res_512.png deleted file mode 100644 index 9452401808..0000000000 Binary files a/multisrc/overrides/zmanga/alceascan/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/src/main/AndroidManifest.xml b/multisrc/src/main/AndroidManifest.xml deleted file mode 100644 index 17ee0c14a4..0000000000 --- a/multisrc/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt index a5da4d2511..daf21dd051 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt @@ -147,7 +147,7 @@ open class A3Manga( }.getOrNull() ?: 0 } - override fun pageListParse(document: Document): List { + protected fun decodeImgList(document: Document): String { val htmlContentScript = document.selectFirst("script:containsData(htmlContent)")?.html() ?.substringAfter("var htmlContent=\"") ?.substringBefore("\";") @@ -175,8 +175,41 @@ open class A3Manga( val imgListHtml = cipher.doFinal(ciphertext).toString(Charsets.UTF_8) - return Jsoup.parseBodyFragment(imgListHtml).select("img").mapIndexed { idx, it -> - Page(idx, imageUrl = it.attr("abs:src")) + return imgListHtml + } + + override fun pageListParse(document: Document): List { + val imgListHtml = decodeImgList(document) + + return Jsoup.parseBodyFragment(imgListHtml).select("img").mapIndexed { idx, element -> + val encryptedUrl = element.attributes().find { it.key.startsWith("data") }?.value + val effectiveUrl = encryptedUrl?.decodeUrl() ?: element.attr("abs:src") + Page(idx, imageUrl = effectiveUrl) + } + } + + private fun String.decodeUrl(): String? { + // We expect the URL to start with `https://`, where the last 3 characters are encoded. + // The length of the encoded character is not known, but it is the same across all. + // Essentially we are looking for the two encoded slashes, which tells us the length. + val patternIdx = patternsLengthCheck.indexOfFirst { pattern -> + val matchResult = pattern.find(this) + val g1 = matchResult?.groupValues?.get(1) + val g2 = matchResult?.groupValues?.get(2) + g1 == g2 && g1 != null + } + if (patternIdx == -1) { + return null + } + + // With a known length we can predict all the encoded characters. + // This is a slightly more expensive pattern, hence the separation. + val matchResult = patternsSubstitution[patternIdx].find(this) + return matchResult?.destructured?.let { (colon, slash, period) -> + this + .replace(colon, ":") + .replace(slash, "/") + .replace(period, ".") } } @@ -203,5 +236,12 @@ open class A3Manga( val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US).apply { timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") } + + private val patternsLengthCheck: List = (20 downTo 1).map { i -> + """^https.{$i}(.{$i})(.{$i})""".toRegex() + } + private val patternsSubstitution: List = (20 downTo 1).map { i -> + """^https(.{$i})(.{$i}).*(.{$i})(?:webp|jpeg|tiff|.{3})$""".toRegex() + } } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3MangaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3MangaGenerator.kt index 9a0500766d..5e7ac5ed44 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3MangaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/a3manga/A3MangaGenerator.kt @@ -9,13 +9,13 @@ class A3MangaGenerator : ThemeSourceGenerator { override val themeClass = "A3Manga" - override val baseVersionCode: Int = 1 + override val baseVersionCode: Int = 2 override val sources = listOf( - SingleLang("A3 Manga", "https://www.a3mnga.com", "vi"), - SingleLang("Team Lanh Lung", "https://teamlanhlung.com", "vi", sourceName = "Team Lạnh Lùng"), + SingleLang("A3 Manga", "https://www.a3manga.info", "vi"), + SingleLang("Team Lanh Lung", "https://teamlanhlung.me", "vi", sourceName = "Team Lạnh Lùng", overrideVersionCode = 1), SingleLang("Ngon Phong", "https://www.ngonphong.com", "vi", sourceName = "Ngôn Phong", overrideVersionCode = 1), - SingleLang("O Cu Meo", "https://www.ocumeo.com", "vi", sourceName = "Ổ Cú Mèo"), + SingleLang("O Cu Meo", "https://www.ocumoe.com", "vi", sourceName = "Ổ Cú Mèo", overrideVersionCode = 1), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bakamanga/BakaMangaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bakamanga/BakaMangaGenerator.kt index ff5325d52b..b271dd90e3 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bakamanga/BakaMangaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bakamanga/BakaMangaGenerator.kt @@ -11,9 +11,6 @@ class BakaMangaGenerator : ThemeSourceGenerator { override val baseVersionCode = 1 override val sources = listOf( - SingleLang("ManhuaManga.net", "https://manhuamanga.net", "en", className = "ManhuaMangaNet", overrideVersionCode = 2), - SingleLang("ManhwaManga.net", "https://manhwamanga.net", "en", isNsfw = true, className = "ManhwaMangaNet", overrideVersionCode = 7), - SingleLang("MWManhwa", "https://mwmanhwa.net", "all", isNsfw = true), SingleLang("Manhwa XXL", "https://manhwaxxl.com", "en", isNsfw = true), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt index a072d55c4b..ccf803d6ed 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt @@ -204,7 +204,7 @@ abstract class Bilibili( title = comic.title author = comic.authorName.joinToString() - genre = comic.genres(intl.pricePaid, EMOJI_LOCKED).joinToString() + genre = comic.styles.joinToString() status = when { comic.isFinish == 1 -> SManga.COMPLETED comic.isOnHiatus -> SManga.ON_HIATUS @@ -215,11 +215,10 @@ abstract class Bilibili( append("${intl.hasPaidChaptersWarning(comic.paidChaptersCount)}\n\n") } - append("${comic.classicLines}\n\n") - append("${intl.informationTitle}:") - append("\n• ${intl.totalChapterCount}: ${intl.localize(comic.episodeList.size)}") + append(comic.classicLines) if (comic.updateWeekdays.isNotEmpty() && status == SManga.ONGOING) { + append("\n\n${intl.informationTitle}:") append("\n• ${intl.getUpdateDays(comic.updateWeekdays)}") } } @@ -237,13 +236,21 @@ abstract class Bilibili( return emptyList() } - return result.data!!.episodeList - .filter { episode -> episode.payMode == 0 && episode.payGold == 0 } - .map { ep -> chapterFromObject(ep, result.data.id) } + return result.data!!.episodeList.map { ep -> chapterFromObject(ep, result.data.id) } } - protected open fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply { - name = episode.shortTitle.plus(if (episode.title.isNotBlank()) " - ${episode.title}" else "") + protected open fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int, isUnlocked: Boolean = false): SChapter = SChapter.create().apply { + name = buildString { + if (episode.isPaid && !isUnlocked) { + append("$EMOJI_LOCKED ") + } + + append(episode.shortTitle) + + if (episode.title.isNotBlank()) { + append(" - ${episode.title}") + } + } date_upload = episode.publicationTime.toDate() url = "/mc$comicId/${episode.id}" } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt index 3d5e2c70ce..a9db40cb28 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt @@ -34,10 +34,7 @@ data class BilibiliComicDto( get() = paidChaptersCount > 0 val paidChaptersCount: Int - get() = episodeList.filter { episode -> episode.payMode == 1 && episode.payGold > 0 }.size - - fun genres(paidLabel: String, emoji: String): List = - (if (hasPaidChapters) listOf("$emoji $paidLabel") else emptyList()) + styles + get() = episodeList.filter { it.isPaid }.size } @Serializable @@ -50,7 +47,9 @@ data class BilibiliEpisodeDto( @SerialName("pub_time") val publicationTime: String, @SerialName("short_title") val shortTitle: String, val title: String, -) +) { + val isPaid = payMode == 1 && payGold > 0 +} @Serializable data class BilibiliReader( diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt index 45f91190cb..7fd830e6e0 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt @@ -10,7 +10,7 @@ class BilibiliGenerator : ThemeSourceGenerator { override val themeClass = "Bilibili" - override val baseVersionCode: Int = 8 + override val baseVersionCode: Int = 9 override val sources = listOf( MultiLang( diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt index 375c51aa27..b656f56bff 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt @@ -54,9 +54,8 @@ class BilibiliIntl(private val lang: String) { "actualice la lista de capítulos para leerlos." else -> "${Bilibili.EMOJI_WARNING} WARNING: This series has ${chapterCount.localized} paid " + - "chapters that were filtered out from the chapter list. If you have already " + - "unlocked and have any in your account, sign in through WebView and refresh " + - "the chapter list to read them." + "chapters. If you have any unlocked in your account then sign in through WebView " + + "to be able to read them." } val imageQualityPrefTitle: String = when (lang) { @@ -85,6 +84,7 @@ class BilibiliIntl(private val lang: String) { else -> "Interest" } + @Suppress("UNUSED") // In BilibiliManga val sortPopular: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "人气推荐" INDONESIAN -> "Populer" @@ -101,11 +101,13 @@ class BilibiliIntl(private val lang: String) { else -> "Updated" } + @Suppress("UNUSED") // In BilibiliManga val sortAdded: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "上架时间" else -> "Added" } + @Suppress("UNUSED") // In BilibiliManga val sortFollowers: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "追漫人数" else -> "Followers count" @@ -135,6 +137,7 @@ class BilibiliIntl(private val lang: String) { else -> "Completed" } + @Suppress("UNUSED") // In BilibiliManga val priceAll: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "全部" INDONESIAN -> "Semua" @@ -142,6 +145,7 @@ class BilibiliIntl(private val lang: String) { else -> "All" } + @Suppress("UNUSED") // In BilibiliManga val priceFree: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "免费" INDONESIAN -> "Bebas" @@ -149,6 +153,7 @@ class BilibiliIntl(private val lang: String) { else -> "Free" } + @Suppress("UNUSED") // In BilibiliManga val pricePaid: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "付费" INDONESIAN -> "Dibayar" @@ -156,17 +161,20 @@ class BilibiliIntl(private val lang: String) { else -> "Paid" } + @Suppress("UNUSED") // In BilibiliManga val priceWaitForFree: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "等就免费" else -> "Wait for free" } + @Suppress("UNUSED") // In BilibiliComics val failedToRefreshToken: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "无法刷新令牌。请打开 WebView 修正错误。" SPANISH -> "Error al actualizar el token. Abra el WebView para solucionar este error." else -> "Failed to refresh the token. Open the WebView to fix this error." } + @Suppress("UNUSED") // In BilibiliComics val failedToGetCredential: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "无法获取阅读章节所需的凭证。" SPANISH -> "Erro al obtener la credencial para leer el capítulo." @@ -179,12 +187,6 @@ class BilibiliIntl(private val lang: String) { else -> "Information" } - val totalChapterCount: String = when (lang) { - CHINESE, SIMPLIFIED_CHINESE -> "章节总数" - SPANISH -> "Número total de capítulos" - else -> "Total chapter count" - } - private val updatesDaily: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "每日更新" SPANISH -> "Actualizaciones diarias" @@ -208,17 +210,17 @@ class BilibiliIntl(private val lang: String) { return updatesEvery(days) } - fun localize(value: Int) = value.localized - private val Int.localized: String get() = numberFormat.format(this) companion object { const val CHINESE = "zh" - const val ENGLISH = "en" const val INDONESIAN = "id" const val SIMPLIFIED_CHINESE = "zh-Hans" const val SPANISH = "es" const val FRENCH = "fr" + + @Suppress("UNUSED") // In BilibiliComics + const val ENGLISH = "en" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt deleted file mode 100644 index 316f420e06..0000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt +++ /dev/null @@ -1,187 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.comicake - -import android.os.Build -import eu.kanade.tachiyomi.AppInfo -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -abstract class ComiCake( - override val name: String, - final override val baseUrl: String, - override val lang: String, - readerEndpoint: String = COMICAKE_DEFAULT_READER_ENDPOINT, - apiEndpoint: String = COMICAKE_DEFAULT_API_ENDPOINT, -) : HttpSource() { - - override val versionId = 1 - override val supportsLatest = true - private val readerBase = baseUrl + readerEndpoint - private var apiBase = baseUrl + apiEndpoint - - private val userAgent = "Mozilla/5.0 (" + - "Android ${Build.VERSION.RELEASE}; Mobile) " + - "Tachiyomi/${AppInfo.getVersionName()}" - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", userAgent) - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBase/comics.json?ordering=-created_at&page=$page") // Not actually popular, just latest added to system - } - - override fun popularMangaParse(response: Response): MangasPage { - val res = response.body.string() - return getMangasPageFromComicsResponse(res) - } - - private fun getMangasPageFromComicsResponse(json: String, nested: Boolean = false): MangasPage { - val response = JSONObject(json) - val results = response.getJSONArray("results") - val mangas = ArrayList() - val ids = mutableListOf() - - for (i in 0 until results.length()) { - val obj = results.getJSONObject(i) - if (nested) { - val nestedComic = obj.getJSONObject("comic") - val id = nestedComic.getInt("id") - if (ids.contains(id)) { - continue - } - ids.add(id) - val manga = SManga.create() - manga.url = id.toString() - manga.title = nestedComic.getString("name") - mangas.add(manga) - } else { - val id = obj.getInt("id") - if (ids.contains(id)) { - continue - } - ids.add(id) - mangas.add(parseComicJson(obj)) - } - } - - return MangasPage(mangas, !(response.getString("next").isNullOrEmpty() || response.getString("next") == "null")) - } - - // Shenanigans to allow "open in webview" to show a webpage instead of JSON - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(apiMangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$apiBase/comics/${manga.url}.json") - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(manga.description!!.substringAfterLast("\n")) - } - - override fun mangaDetailsParse(response: Response): SManga { - val comicJson = JSONObject(response.body.string()) - return parseComicJson(comicJson, true) - } - - private fun parseComicJson(obj: JSONObject, human: Boolean = false) = SManga.create().apply { - url = if (human) { - "$readerBase/series/${obj.getString("slug")}/" - } else { - obj.getInt("id").toString() // Yeah, I know... Feel free to improve on this - } - title = obj.getString("name") - thumbnail_url = obj.getString("cover") - author = parseListNames(obj.getJSONArray("author")) - artist = parseListNames(obj.getJSONArray("artist")) - description = obj.getString("description") + - "\n\n${readerBase}series/${obj.getString("slug")}/" // webpage for "open in webview" - genre = parseListNames(obj.getJSONArray("tags")) - status = SManga.UNKNOWN - } - - private fun parseListNames(arr: JSONArray): String { - val hold = ArrayList(arr.length()) - for (i in 0 until arr.length()) - hold.add(arr.getJSONObject(i).getString("name")) - return hold.sorted().joinToString(", ") - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // TODO filters - return GET("$apiBase/comics.json?page=$page&search=$query") - } - - override fun searchMangaParse(response: Response): MangasPage { - val res = response.body.string() - return getMangasPageFromComicsResponse(res) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBase/chapters.json?page=$page&expand=comic") - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val res = response.body.string() - return getMangasPageFromComicsResponse(res, true) - } - - private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply { - name = obj.getString("title") // title will always have content, vs. name that's an optional field - chapter_number = (obj.getInt("chapter") + (obj.getInt("subchapter") / 10.0)).toFloat() - date_upload = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", Locale.getDefault()).parse(obj.getString("published_at"))?.time ?: 0L - // TODO scanlator field by adding team to expandable in CC (low priority given the use case of CC) - url = obj.getString("manifest") - } - - override fun chapterListRequest(manga: SManga): Request { - return GET("$apiBase/chapters.json?comic=${manga.url}&ordering=-volume%2C-chapter%2C-subchapter&n=1000", headers) // There's no pagination in Tachiyomi for chapters so we get 1k chapters - } - - override fun chapterListParse(response: Response): List { - val chapterJson = JSONObject(response.body.string()) - val results = chapterJson.getJSONArray("results") - val ret = ArrayList() - for (i in 0 until results.length()) { - ret.add(parseChapterJson(results.getJSONObject(i))) - } - return ret - } - - override fun pageListParse(response: Response): List { - val webPub = JSONObject(response.body.string()) - val readingOrder = webPub.getJSONArray("readingOrder") - val ret = ArrayList() - for (i in 0 until readingOrder.length()) { - val pageUrl = readingOrder.getJSONObject(i).getString("href") - ret.add(Page(i, "", pageUrl)) - } - return ret - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") - - companion object { - private const val COMICAKE_DEFAULT_API_ENDPOINT = "/api" // Highly unlikely to change - private const val COMICAKE_DEFAULT_READER_ENDPOINT = "/r/" // Can change based on CC config - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt deleted file mode 100644 index c1abc51482..0000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.comicake - -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class ComiCakeGenerator : ThemeSourceGenerator { - - override val themePkg = "comicake" - - override val themeClass = "ComiCake" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("WhimSubs", "https://whimsubs.xyz", "en"), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - ComiCakeGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt new file mode 100644 index 0000000000..bae45d35dd --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt @@ -0,0 +1,403 @@ +package eu.kanade.tachiyomi.multisrc.fansubscat + +import eu.kanade.tachiyomi.AppInfo +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.float +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.injectLazy + +abstract class FansubsCat( + override val name: String, + override val baseUrl: String, + override val lang: String, + val isHentaiSite: Boolean, +) : HttpSource() { + + private val apiBaseUrl = "https://api.fansubs.cat" + + override val supportsLatest = true + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}") + + override val client: OkHttpClient = network.client + + private val json: Json by injectLazy() + + private fun parseMangaFromJson(response: Response): MangasPage { + val jsonObject = json.decodeFromString(response.body.string()) + + val mangas = jsonObject["result"]!!.jsonArray.map { json -> + SManga.create().apply { + url = json.jsonObject["slug"]!!.jsonPrimitive.content + title = json.jsonObject["name"]!!.jsonPrimitive.content + thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content + author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull + description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull + status = json.jsonObject["status"]!!.jsonPrimitive.content.toStatus() + genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull + } + } + + return MangasPage(mangas, mangas.size >= 20) + } + + private fun parseChapterListFromJson(response: Response): List { + val jsonObject = json.decodeFromString(response.body.string()) + + return jsonObject["result"]!!.jsonArray.map { json -> + SChapter.create().apply { + url = json.jsonObject["id"]!!.jsonPrimitive.content + name = json.jsonObject["title"]!!.jsonPrimitive.content + chapter_number = json.jsonObject["number"]!!.jsonPrimitive.float + scanlator = json.jsonObject["fansub"]!!.jsonPrimitive.content + date_upload = json.jsonObject["created"]!!.jsonPrimitive.long + } + } + } + + private fun parsePageListFromJson(response: Response): List { + val jsonObject = json.decodeFromString(response.body.string()) + + return jsonObject["result"]!!.jsonArray.mapIndexed { i, it -> + Page( + i, + it.jsonObject["url"]!!.jsonPrimitive.content, + it.jsonObject["url"]!!.jsonPrimitive.content, + ) + } + } + + // Popular + + override fun popularMangaRequest(page: Int): Request { + return GET("$apiBaseUrl/manga/popular/$page?hentai=$isHentaiSite", headers) + } + + override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Latest + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$apiBaseUrl/manga/recent/$page?hentai=$isHentaiSite", headers) + } + + override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Search + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val mangaTypeFilter = filterList.find { it is MangaTypeFilter } as MangaTypeFilter + val stateFilter = filterList.find { it is StateFilter } as StateFilter + val demographyFilter = filterList.find { it is DemographyFilter } as DemographyFilter + val genreFilter = filterList.find { it is GenreTagFilter } as GenreTagFilter + val themeFilter = filterList.find { it is ThemeTagFilter } as ThemeTagFilter + val builder = "$apiBaseUrl/manga/search/$page?hentai=$isHentaiSite".toHttpUrl().newBuilder() + mangaTypeFilter.addQueryParameter(builder) + stateFilter.addQueryParameter(builder) + demographyFilter.addQueryParameter(builder) + genreFilter.addQueryParameter(builder) + themeFilter.addQueryParameter(builder) + if (query.isNotBlank()) { + builder.addQueryParameter("query", query) + } + return GET(builder.toString(), headers) + } + + override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Details + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET( + "$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl/${manga.url}" + } + + override fun mangaDetailsParse(response: Response): SManga { + val jsonObject = json.decodeFromString(response.body.string()) + val resultObject = jsonObject.jsonObject["result"]!!.jsonObject + + return SManga.create().apply { + url = resultObject["slug"]!!.jsonPrimitive.content + title = resultObject["name"]!!.jsonPrimitive.content + thumbnail_url = resultObject["thumbnail_url"]!!.jsonPrimitive.content + author = resultObject["author"]!!.jsonPrimitive.contentOrNull + description = resultObject["synopsis"]!!.jsonPrimitive.contentOrNull + status = resultObject["status"]!!.jsonPrimitive.content.toStatus() + genre = resultObject["genres"]!!.jsonPrimitive.contentOrNull + } + } + + private fun String?.toStatus() = when { + this == null -> SManga.UNKNOWN + this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING + this.contains("finished", ignoreCase = true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + // Chapters + + override fun chapterListRequest(manga: SManga): Request { + return GET( + "$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun chapterListParse(response: Response): List = + parseChapterListFromJson(response) + + // Pages + + override fun pageListRequest(chapter: SChapter): Request { + return GET( + "$apiBaseUrl/manga/pages/${chapter.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun getChapterUrl(chapter: SChapter): String { + return "$baseUrl/${chapter.url.replace("/", "?f=")}" + } + + override fun pageListParse(response: Response): List = parsePageListFromJson(response) + + override fun imageUrlParse(response: Response): String = + throw UnsupportedOperationException("Not used") + + // Filter + override fun getFilterList() = FilterList( + listOfNotNull( + MangaTypeFilter("Tipus", getMangaTypeList()), + StateFilter("Estat", getStateList()), + if (!isHentaiSite) { + DemographyFilter("Demografies", getDemographyList()) + } else { + null + }, + GenreTagFilter("Gèneres (inclou/exclou)", getGenreList()), + ThemeTagFilter("Temàtiques (inclou/exclou)", getThemeList()), + ), + ) + + private fun getMangaTypeList() = listOf( + MangaType("oneshot", "One-shots"), + MangaType("serialized", "Serialitzats"), + ) + + private fun getStateList() = listOf( + State(1, "Completat"), + State(2, "En procés"), + State(3, "Parcialment completat"), + State(4, "Abandonat"), + State(5, "Cancel·lat"), + ) + + private fun getDemographyList() = listOf( + Demography(35, "Infantil"), + Demography(27, "Josei"), + Demography(12, "Seinen"), + Demography(16, "Shōjo"), + Demography(1, "Shōnen"), + Demography(-1, "No definida"), + ) + + private fun getGenreList() = listOfNotNull( + Tag(4, "Acció"), + Tag(7, "Amor"), + Tag(38, "Amor entre noies"), + Tag(23, "Amor entre nois"), + Tag(31, "Avantguardisme"), + Tag(6, "Aventura"), + Tag(10, "Ciència-ficció"), + Tag(2, "Comèdia"), + Tag(47, "De prestigi"), + Tag(3, "Drama"), + Tag(19, "Ecchi"), + Tag(46, "Erotisme"), + Tag(20, "Esports"), + Tag(5, "Fantasia"), + Tag(48, "Gastronomia"), + if (isHentaiSite) { + Tag(34, "Hentai") + } else { + null + }, + Tag(11, "Misteri"), + Tag(8, "Sobrenatural"), + Tag(17, "Suspens"), + Tag(21, "Terror"), + Tag(42, "Vida quotidiana"), + ) + + private fun getThemeList() = listOf( + Tag(71, "Animals de companyia"), + Tag(50, "Antropomorfisme"), + Tag(70, "Arts escèniques"), + Tag(18, "Arts marcials"), + Tag(81, "Arts visuals"), + Tag(64, "Canvi de gènere màgic"), + Tag(56, "Comèdia de gags"), + Tag(68, "Crim organitzat"), + Tag(69, "Cultura otaku"), + Tag(30, "Curses"), + Tag(54, "Delinqüència"), + Tag(43, "Detectivesc"), + Tag(55, "Educatiu"), + Tag(9, "Escolar"), + Tag(39, "Espai"), + Tag(77, "Esports d’equip"), + Tag(53, "Esports de combat"), + Tag(25, "Harem"), + Tag(73, "Harem invers"), + Tag(15, "Històric"), + Tag(59, "Idols femenines"), + Tag(60, "Idols masculins"), + Tag(75, "Indústria de l’entreteniment"), + Tag(61, "Isekai"), + Tag(58, "Joc d’alt risc"), + Tag(33, "Joc d’estratègia"), + Tag(82, "Laboral"), + Tag(29, "Mecha"), + Tag(66, "Medicina"), + Tag(67, "Memòries"), + Tag(22, "Militar"), + Tag(32, "Mitologia"), + Tag(26, "Música"), + Tag(65, "Noies màgiques"), + Tag(36, "Paròdia"), + Tag(49, "Personatges adults"), + Tag(51, "Personatges bufons"), + Tag(63, "Polígon amorós"), + Tag(13, "Psicològic"), + Tag(52, "Puericultura"), + Tag(72, "Reencarnació"), + Tag(62, "Relaxant"), + Tag(74, "Rerefons romàntic"), + Tag(37, "Samurais"), + Tag(57, "Sang i fetge"), + Tag(40, "Superpoders"), + Tag(76, "Supervivència"), + Tag(80, "Tirana"), + Tag(45, "Transformisme"), + Tag(41, "Vampirs"), + Tag(78, "Viatges en el temps"), + Tag(79, "Videojocs"), + ) + + private interface UrlQueryFilter { + fun addQueryParameter(url: HttpUrl.Builder) + } + + internal class MangaType(val id: String, name: String) : Filter.CheckBox(name) + internal class State(val id: Int, name: String) : Filter.CheckBox(name) + internal class Tag(val id: Int, name: String) : Filter.TriState(name) + internal class Demography(val id: Int, name: String) : Filter.CheckBox(name) + + private class MangaTypeFilter(collection: String, mangaTypes: List) : + Filter.Group(collection, mangaTypes), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + var oneShotSelected = false + var serializedSelected = false + state.forEach { mangaType -> + if (mangaType.id.equals("oneshot") && mangaType.state) { + oneShotSelected = true + } else if (mangaType.id.equals("serialized") && mangaType.state) { + serializedSelected = true + } + } + if (oneShotSelected && !serializedSelected) { + url.addQueryParameter("type", "oneshot") + } else if (!oneShotSelected && serializedSelected) { + url.addQueryParameter("type", "serialized") + } else { + url.addQueryParameter("type", "all") + } + } + } + + private class StateFilter(collection: String, states: List) : + Filter.Group(collection, states), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { state -> + if (state.state) { + url.addQueryParameter("status[]", state.id.toString()) + } + } + } + } + + private class DemographyFilter(collection: String, demographies: List) : + Filter.Group(collection, demographies), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { demography -> + if (demography.state) { + url.addQueryParameter("demographies[]", demography.id.toString()) + } + } + } + } + + private class GenreTagFilter(collection: String, tags: List) : + Filter.Group(collection, tags), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { tag -> + if (tag.isIncluded()) { + url.addQueryParameter("genres_include[]", tag.id.toString()) + } else if (tag.isExcluded()) { + url.addQueryParameter("genres_exclude[]", tag.id.toString()) + } + } + } + } + + private class ThemeTagFilter(collection: String, tags: List) : + Filter.Group(collection, tags), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { tag -> + if (tag.isIncluded()) { + url.addQueryParameter("themes_include[]", tag.id.toString()) + } else if (tag.isExcluded()) { + url.addQueryParameter("themes_exclude[]", tag.id.toString()) + } + } + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt new file mode 100644 index 0000000000..9271d2c6e5 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.multisrc.fansubscat + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class FansubsCatGenerator : ThemeSourceGenerator { + + override val themePkg = "fansubscat" + + override val themeClass = "FansubsCat" + + override val baseVersionCode = 4 + + override val sources = listOf( + SingleLang( + name = "Fansubs.cat", + baseUrl = "https://manga.fansubs.cat", + lang = "ca", + className = "FansubsCatMain", + isNsfw = false, + pkgName = "fansubscat", + ), + SingleLang( + name = "Fansubs.cat - Hentai", + baseUrl = "https://hentai.fansubs.cat/manga", + lang = "ca", + className = "FansubsCatHentai", + isNsfw = true, + ), + ) + + companion object { + @JvmStatic + fun main(args: Array) = FansubsCatGenerator().createAll() + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt new file mode 100644 index 0000000000..51b56371c3 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScans.kt @@ -0,0 +1,314 @@ +package eu.kanade.tachiyomi.multisrc.flixscans + +import android.util.Log +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.Call +import okhttp3.Callback +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.injectLazy + +abstract class FlixScans( + override val name: String, + override val baseUrl: String, + override val lang: String, + protected val apiUrl: String = baseUrl.replace("://", "://api.").plus("/api/v1"), + protected val cdnUrl: String = baseUrl.replace("://", "://api.").plus("/storage/"), +) : HttpSource() { + + override val supportsLatest = true + + protected open val json: Json by injectLazy() + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(2) + .build() + + // only returns 15 chapters each request, so using higher rate limit + private val chapterClient = network.cloudflareClient.newBuilder() + .rateLimitHost(apiUrl.toHttpUrl(), 1, 2) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", baseUrl) + + override fun fetchPopularManga(page: Int): Observable { + runCatching { fetchGenre() } + + return super.fetchPopularManga(page) + } + + override fun popularMangaRequest(page: Int): Request { + return GET("$apiUrl/webtoon/homepage/home", headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + val result = response.parseAs() + + val entries = (result.hot + result.topAll + result.topMonth + result.topWeek) + .distinctBy { it.id } + .map { it.toSManga(cdnUrl) } + + return MangasPage(entries, false) + } + + override fun fetchLatestUpdates(page: Int): Observable { + runCatching { fetchGenre() } + + return super.fetchLatestUpdates(page) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$apiUrl/search/advance?page=$page&serie_type=webtoon", headers) + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val result = response.parseAs>() + val currentPage = response.request.url.queryParameter("page") + ?.toIntOrNull() ?: 1 + + val entries = result.data.map { it.toSManga(cdnUrl) } + val hasNextPage = result.meta.lastPage > currentPage + + return MangasPage(entries, hasNextPage) + } + + private var fetchGenreList: List = emptyList() + private var fetchGenreCallOngoing = false + private var fetchGenreFailed = false + private var fetchGenreAttempt = 0 + + private fun fetchGenre() { + if (fetchGenreAttempt < 3 && (fetchGenreList.isEmpty() || fetchGenreFailed) && !fetchGenreCallOngoing) { + fetchGenreCallOngoing = true + + // fetch genre asynchronously as it sometimes hangs + client.newCall(fetchGenreRequest()).enqueue(fetchGenreCallback) + } + } + + private val fetchGenreCallback = object : Callback { + override fun onFailure(call: Call, e: okio.IOException) { + fetchGenreAttempt++ + fetchGenreFailed = true + fetchGenreCallOngoing = false + + e.message?.let { Log.e("$name Filters", it) } + } + + override fun onResponse(call: Call, response: Response) { + fetchGenreCallOngoing = false + fetchGenreAttempt++ + + if (!response.isSuccessful) { + fetchGenreFailed = true + response.close() + + return + } + + val parsed = runCatching { + response.use(::fetchGenreParse) + } + + fetchGenreFailed = parsed.isFailure + fetchGenreList = parsed.getOrElse { + Log.e("$name Filters", it.stackTraceToString()) + emptyList() + } + } + } + + private fun fetchGenreRequest(): Request { + return GET("$apiUrl/search/genres", headers) + } + + private fun fetchGenreParse(response: Response): List { + return response.parseAs>() + } + + override fun getFilterList(): FilterList { + val filters: MutableList> = mutableListOf( + Filter.Header("Ignored when using Text Search"), + MainGenreFilter(), + TypeFilter(), + StatusFilter(), + ) + + filters += if (fetchGenreList.isNotEmpty()) { + listOf( + GenreFilter("Genre", fetchGenreList), + ) + } else { + listOf( + Filter.Separator(), + Filter.Header("Press 'reset' to attempt to show Genres"), + ) + } + + return FilterList(filters) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + runCatching { fetchGenre() } + + return super.fetchSearchManga(page, query, filters) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + val requestBody = SearchInput(query.trim()) + .let(json::encodeToString) + .toRequestBody(JSON_MEDIA_TYPE) + + val newHeaders = headersBuilder() + .add("Content-Length", requestBody.contentLength().toString()) + .add("Content-Type", requestBody.contentType().toString()) + .build() + + return POST("$apiUrl/search/serie?page=$page", newHeaders, requestBody) + } + + val advSearchUrl = apiUrl.toHttpUrl().newBuilder().apply { + addPathSegments("search/advance") + addQueryParameter("page", page.toString()) + addQueryParameter("serie_type", "webtoon") + + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + filter.checked.let { + if (it.isNotEmpty()) { + addQueryParameter("genres", it.joinToString(",")) + } + } + } + is MainGenreFilter -> { + if (filter.state > 0) { + addQueryParameter("main_genres", filter.selected) + } + } + is TypeFilter -> { + if (filter.state > 0) { + addQueryParameter("type", filter.selected) + } + } + is StatusFilter -> { + if (filter.state > 0) { + addQueryParameter("status", filter.selected) + } + } + else -> {} + } + } + }.build() + + return GET(advSearchUrl, headers) + } + + override fun searchMangaParse(response: Response) = latestUpdatesParse(response) + + override fun mangaDetailsRequest(manga: SManga): Request { + val id = manga.url.split("-")[1] + + return GET("$apiUrl/webtoon/series/$id", headers) + } + + override fun getMangaUrl(manga: SManga) = baseUrl + manga.url + + override fun mangaDetailsParse(response: Response): SManga { + val result = response.parseAs() + + return result.serie.toSManga(cdnUrl) + } + + override fun fetchChapterList(manga: SManga): Observable> { + return chapterClient.newCall(chapterListRequest(manga)) + .asObservableSuccess() + .map(::chapterListParse) + } + + override fun chapterListRequest(manga: SManga): Request { + val id = manga.url.split("-")[1] + + return paginatedChapterListRequest(id) + } + + private fun paginatedChapterListRequest(seriesID: String, page: Int = 1): Request { + return GET("$apiUrl/webtoon/chapters/$seriesID-asc?page=$page", headers) + } + + override fun chapterListParse(response: Response): List { + val result = response.parseAs>() + + val id = response.request.url.toString() + .substringAfterLast("/") + .substringBefore("-") + + val chapters = result.data.toMutableList() + + var page = 1 + + while (page < result.meta.lastPage) { + page++ + + val newResponse = chapterClient.newCall(paginatedChapterListRequest(id, page)).execute() + + if (!newResponse.isSuccessful) { + newResponse.close() + continue + } + + val newResult = newResponse.parseAs>() + + chapters.addAll(newResult.data) + } + + return chapters.map(Chapter::toSChapter).reversed() + } + + override fun pageListRequest(chapter: SChapter): Request { + val id = chapter.url + .substringAfterLast("/") + .substringBefore("-") + + return GET("$apiUrl/webtoon/chapters/chapter/$id", headers) + } + + override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url + + override fun pageListParse(response: Response): List { + val result = response.parseAs() + + return result.chapter.chapterData.webtoon.mapIndexed { i, img -> + Page(i, "", cdnUrl + img) + } + } + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not Used") + + protected inline fun Response.parseAs(): T = + use { body.string() }.let(json::decodeFromString) + + companion object { + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt new file mode 100644 index 0000000000..637841a38c --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansDto.kt @@ -0,0 +1,146 @@ +package eu.kanade.tachiyomi.multisrc.flixscans + +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.jsoup.Jsoup +import java.text.SimpleDateFormat +import java.util.Locale + +@Serializable +data class ApiResponse( + val data: List, + val meta: PageInfo, +) + +@Serializable +data class PageInfo( + @SerialName("last_page") val lastPage: Int, +) + +@Serializable +data class HomeDto( + val hot: List, + val topWeek: List, + val topMonth: List, + val topAll: List, +) + +@Serializable +data class BrowseSeries( + val id: Int, + val title: String, + val slug: String, + val prefix: Int, + val thumbnail: String?, +) { + fun toSManga(cdnUrl: String) = SManga.create().apply { + title = this@BrowseSeries.title + url = "/series/$prefix-$id-$slug" + thumbnail_url = thumbnail?.let { cdnUrl + it } + } +} + +@Serializable +data class SearchInput( + val title: String, +) + +@Serializable +data class GenreHolder( + val name: String, + val id: Int, +) + +@Serializable +data class SeriesResponse( + val serie: Series, +) + +@Serializable +data class Series( + val id: Int, + val title: String, + val slug: String, + val prefix: Int, + val thumbnail: String?, + val story: String?, + val serieType: String?, + val mainGenres: String?, + val otherNames: List? = emptyList(), + val status: String?, + val type: String?, + val authors: List? = emptyList(), + val artists: List? = emptyList(), + val genres: List? = emptyList(), +) { + fun toSManga(cdnUrl: String) = SManga.create().apply { + title = this@Series.title + url = "/series/$prefix-$id-$slug" + thumbnail_url = cdnUrl + thumbnail + author = authors?.joinToString { it.name.trim() } + artist = artists?.joinToString { it.name.trim() } + genre = (otherGenres + genres?.map { it.name.trim() }.orEmpty()) + .distinct().joinToString { it.trim() } + description = story?.let { Jsoup.parse(it).text() } + if (otherNames?.isNotEmpty() == true) { + if (description.isNullOrEmpty()) { + description = "Alternative Names:\n" + } else { + description += "\n\nAlternative Names:\n" + } + description += otherNames.joinToString("\n") { "• ${it.trim()}" } + } + status = when (this@Series.status?.trim()) { + "ongoing" -> SManga.ONGOING + "completed" -> SManga.COMPLETED + "onhold" -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + } + + private val otherGenres = listOfNotNull(serieType, mainGenres, type) + .map { word -> + word.trim().replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase(Locale.getDefault()) + } else { + it.toString() + } + } + } +} + +@Serializable +data class Chapter( + val id: Int, + val name: String, + val slug: String, + val createdAt: String? = null, +) { + fun toSChapter() = SChapter.create().apply { + url = "/read/webtoon/$id-$slug" + name = this@Chapter.name + date_upload = runCatching { dateFormat.parse(createdAt!!)!!.time }.getOrDefault(0L) + } + + companion object { + val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.ENGLISH) + } +} + +@Serializable +data class PageListResponse( + val chapter: ChapterPages, +) + +@Serializable +data class ChapterPages( + val chapterData: ChapterPageData, +) + +@Serializable +data class ChapterPageData( + val webtoon: List, +) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenerator.kt new file mode 100644 index 0000000000..0d49ac9623 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenerator.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.multisrc.flixscans + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class FlixScansGenerator : ThemeSourceGenerator { + + override val themePkg = "flixscans" + + override val themeClass = "FlixScans" + + override val baseVersionCode: Int = 2 + + override val sources = listOf( + SingleLang("Flix Scans", "https://flixscans.net", "en", className = "FlixScansNet", pkgName = "flixscans"), + SingleLang("جالاكسي مانجا", "https://flixscans.com", "ar", className = "GalaxyManga", overrideVersionCode = 26), + SingleLang("مانجا نون", "https://manjanoon.com", "ar", className = "MangaNoon"), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + FlixScansGenerator().createAll() + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt new file mode 100644 index 0000000000..aebbe7813d --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/flixscans/FlixScansGenre.kt @@ -0,0 +1,62 @@ +package eu.kanade.tachiyomi.multisrc.flixscans + +import eu.kanade.tachiyomi.source.model.Filter + +abstract class SelectFilter( + name: String, + private val options: List, +) : Filter.Select( + name, + options.toTypedArray(), +) { + val selected get() = options[state] +} + +class CheckBoxFilter( + name: String, + val id: String, +) : Filter.CheckBox(name) + +class GenreFilter( + name: String, + private val genres: List, +) : Filter.Group( + name, + genres.map { CheckBoxFilter(it.name.trim(), it.id.toString()) }, +) { + val checked get() = state.filter { it.state }.map { it.id } +} + +class MainGenreFilter : SelectFilter( + "Main Genre", + listOf( + "", + "fantasy", + "romance", + "action", + "drama", + ), +) + +class TypeFilter : SelectFilter( + "Type", + listOf( + "", + "manhwa", + "manhua", + "manga", + "comic", + ), +) + +class StatusFilter : SelectFilter( + "Status", + listOf( + "", + "ongoing", + "completed", + "droped", + "onhold", + "soon", + ), +) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt index 3873d626fa..dfb96dc114 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt @@ -19,7 +19,10 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Elements import java.nio.charset.Charset +import java.text.SimpleDateFormat import java.util.Calendar +import java.util.Locale + /** * For sites based on the Flat-Manga CMS */ @@ -27,6 +30,7 @@ abstract class FMReader( override val name: String, override val baseUrl: String, override val lang: String, + private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH), ) : ParsedHttpSource() { override val supportsLatest = true @@ -237,7 +241,7 @@ abstract class FMReader( name = element.attr(chapterNameAttrSelector).substringAfter("$mangaTitle ") } } - date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseChapterDate(it.text()) else 0 } + date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseRelativeDate(it.text()) else 0 } } } @@ -247,7 +251,7 @@ abstract class FMReader( // gets the unit of time (day, week hour) from "1 day ago" open val dateWordIndex = 1 - private fun parseChapterDate(date: String): Long { + private fun parseRelativeDate(date: String): Long { val value = date.split(' ')[dateValueIndex].toInt() val dateWord = date.split(' ')[dateWordIndex].let { if (it.contains("(")) { @@ -294,6 +298,10 @@ abstract class FMReader( } } } + open fun parseAbsoluteDate(dateStr: String): Long { + return runCatching { dateFormat.parse(dateStr)?.time } + .getOrNull() ?: 0L + } open val pageListImageSelector = "img.chapter-img" diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt index 3f9996d177..46d824c505 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt @@ -13,16 +13,16 @@ class FMReaderGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 8 override val sources = listOf( - MultiLang("Manhwa18.net", "https://manhwa18.net", listOf("en", "ko"), className = "Manhwa18NetFactory", isNsfw = true), + MultiLang("Manhwa18.net", "https://manhwa18.net", listOf("en", "ko"), className = "Manhwa18NetFactory", isNsfw = true, overrideVersionCode = 1), SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"), - SingleLang("KissLove", "https://klmanga.com", "ja", overrideVersionCode = 2), + SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4), SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 1), SingleLang("ManhuaRock", "https://manhuarock.net", "vi", overrideVersionCode = 1), SingleLang("Manhwa18", "https://manhwa18.com", "en", isNsfw = true, overrideVersionCode = 2), SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3), - SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", overrideVersionCode = 4), + SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5), SingleLang("Manga1000", "https://manga1000.top", "ja"), - SingleLang("WeLoveMangaOne", "https://welovemanga.one", "ja"), + SingleLang("WeLoveMangaOne", "https://welovemanga.one", "ja", isNsfw = true, overrideVersionCode = 1), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLe.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLe.kt index 3c8787a696..3f3b61d608 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLe.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLe.kt @@ -150,14 +150,14 @@ abstract class GroupLe( else -> rawAgeValue } val manga = SManga.create() - manga.title = document.select("h1.names .name").text() + manga.title = document.select(".names > .name").text() manga.author = infoElement.select("span.elem_author").first()?.text() ?: infoElement.select( "span.elem_screenwriter", ).first()?.text() manga.artist = infoElement.select("span.elem_illustrator").first()?.text() manga.genre = ( - "$category, $rawAgeStop, " + infoElement.select("span.elem_genre") - .text() + ", " + infoElement.select("span.elem_tag").text() + "$category, $rawAgeStop, " + infoElement.select("p:contains(Жанры:) a, p:contains(Теги:) a") + .joinToString { it.text() } ).split(", ") .filter { it.isNotEmpty() }.joinToString { it.trim().lowercase() } val altName = if (infoElement.select(".another-names").isNotEmpty()) { @@ -179,6 +179,7 @@ abstract class GroupLe( "продолжается" -> SManga.ONGOING "начат" -> SManga.ONGOING "переведено" -> SManga.COMPLETED + "завершён" -> SManga.COMPLETED "приостановлен" -> SManga.ON_HIATUS else -> SManga.UNKNOWN } @@ -201,7 +202,7 @@ abstract class GroupLe( private fun chapterListParse(response: Response, manga: SManga): List { val document = response.asJsoup() - if ((document.select(".expandable.hide-dn").isNotEmpty() || document.select("img.logo").first()?.attr("title")?.contains("Allhentai") == true) && document.select(".user-avatar").isNullOrEmpty()) { + if ((document.select(".expandable.hide-dn").isNotEmpty() && document.select(".user-avatar").isNullOrEmpty() && document.toString().contains("current_user_country_code = 'RU'")) || (document.select("img.logo").first()?.attr("title")?.contains("Allhentai") == true && document.select(".user-avatar").isNullOrEmpty())) { throw Exception("Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E") } return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLeGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLeGenerator.kt index 8d775ba569..c740ecf920 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLeGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/grouple/GroupLeGenerator.kt @@ -9,12 +9,12 @@ class GroupLeGenerator : ThemeSourceGenerator { override val themeClass = "GroupLe" - override val baseVersionCode = 15 + override val baseVersionCode = 20 override val sources = listOf( SingleLang("ReadManga", "https://readmanga.live", "ru", overrideVersionCode = 46), - SingleLang("MintManga", "https://mintmanga.live", "ru", overrideVersionCode = 46), - SingleLang("AllHentai", "https://2023.allhen.online", "ru", isNsfw = true, overrideVersionCode = 22), + SingleLang("MintManga", "https://mintmanga.com", "ru", isNsfw = true, overrideVersionCode = 46), + SingleLang("AllHentai", "https://z.allhen.online", "ru", isNsfw = true, overrideVersionCode = 22), SingleLang("SelfManga", "https://selfmanga.live", "ru", overrideVersionCode = 22), SingleLang("RuMIX", "https://rumix.me", "ru", overrideVersionCode = 1), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt index e0c96d501a..9cf9660d3f 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt @@ -1,7 +1,12 @@ package eu.kanade.tachiyomi.multisrc.heancms +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -9,16 +14,21 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale @@ -27,12 +37,38 @@ abstract class HeanCms( override val baseUrl: String, override val lang: String, protected val apiUrl: String = baseUrl.replace("://", "://api."), -) : HttpSource() { +) : ConfigurableSource, HttpSource() { + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + SwitchPreferenceCompat(screen.context).apply { + key = SHOW_PAID_CHAPTERS_PREF + title = intl.prefShowPaidChapterTitle + summaryOn = intl.prefShowPaidChapterSummaryOn + summaryOff = intl.prefShowPaidChapterSummaryOff + setDefaultValue(SHOW_PAID_CHAPTERS_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit() + .putBoolean(SHOW_PAID_CHAPTERS_PREF, newValue as Boolean) + .commit() + } + }.also(screen::addPreference) + } override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient + protected open val slugStrategy = SlugStrategy.NONE + + protected open val useNewQueryEndpoint = false + + private var seriesSlugMap: Map? = null + /** * Custom Json instance to make usage of `encodeDefaults`, * which is not enabled on the injected instance of the app. @@ -45,24 +81,26 @@ abstract class HeanCms( protected val intl by lazy { HeanCmsIntl(lang) } - protected open val fetchAllTitles: Boolean = false - protected open val coverPath: String = "cover/" - protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US) + protected open val mangaSubDirectory: String = "series" - private var seriesSlugMap: Map? = null + protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US) override fun headersBuilder(): Headers.Builder = Headers.Builder() .add("Origin", baseUrl) .add("Referer", "$baseUrl/") override fun popularMangaRequest(page: Int): Request { + if (useNewQueryEndpoint) { + return newEndpointPopularMangaRequest(page) + } + val payloadObj = HeanCmsQuerySearchPayloadDto( page = page, order = "desc", orderBy = "total_views", - status = "Ongoing", + status = "All", type = "Comic", ) @@ -76,12 +114,32 @@ abstract class HeanCms( return POST("$apiUrl/series/querysearch", apiHeaders, payload) } + protected fun newEndpointPopularMangaRequest(page: Int): Request { + val url = "$apiUrl/query".toHttpUrl().newBuilder() + .addQueryParameter("query_string", "") + .addQueryParameter("series_status", "All") + .addQueryParameter("order", "desc") + .addQueryParameter("orderBy", "total_views") + .addQueryParameter("series_type", "Comic") + .addQueryParameter("page", page.toString()) + .addQueryParameter("perPage", "12") + .addQueryParameter("tags_ids", "[]") + + return GET(url.build(), headers) + } + override fun popularMangaParse(response: Response): MangasPage { val json = response.body.string() if (json.startsWith("{")) { val result = json.parseAs() - val mangaList = result.data.map { it.toSManga(apiUrl, coverPath) } + val mangaList = result.data.map { + if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } + } + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) + } fetchAllTitles() @@ -89,7 +147,13 @@ abstract class HeanCms( } val mangaList = json.parseAs>() - .map { it.toSManga(apiUrl, coverPath) } + .map { + if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } + } + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) + } fetchAllTitles() @@ -97,11 +161,15 @@ abstract class HeanCms( } override fun latestUpdatesRequest(page: Int): Request { + if (useNewQueryEndpoint) { + return newEndpointLatestUpdatesRequest(page) + } + val payloadObj = HeanCmsQuerySearchPayloadDto( page = page, order = "desc", orderBy = "latest", - status = "Ongoing", + status = "All", type = "Comic", ) @@ -115,6 +183,20 @@ abstract class HeanCms( return POST("$apiUrl/series/querysearch", apiHeaders, payload) } + protected fun newEndpointLatestUpdatesRequest(page: Int): Request { + val url = "$apiUrl/query".toHttpUrl().newBuilder() + .addQueryParameter("query_string", "") + .addQueryParameter("series_status", "All") + .addQueryParameter("order", "desc") + .addQueryParameter("orderBy", "latest") + .addQueryParameter("series_type", "Comic") + .addQueryParameter("page", page.toString()) + .addQueryParameter("perPage", "12") + .addQueryParameter("tags_ids", "[]") + + return GET(url.build(), headers) + } + override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { @@ -123,16 +205,38 @@ abstract class HeanCms( } val slug = query.substringAfter(SEARCH_PREFIX) - val manga = SManga.create().apply { url = "/series/$slug" } + val manga = SManga.create().apply { + url = if (slugStrategy != SlugStrategy.NONE) { + val mangaId = getIdBySlug(slug) + "/$mangaSubDirectory/${slug.toPermSlugIfNeeded()}#$mangaId" + } else { + "/$mangaSubDirectory/$slug" + } + } return fetchMangaDetails(manga).map { MangasPage(listOf(it), false) } } + private fun getIdBySlug(slug: String): Int { + val result = runCatching { + val response = client.newCall(GET("$apiUrl/series/$slug", headers)).execute() + val json = response.body.string() + + val seriesDetail = json.parseAs() + + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { it[seriesDetail.slug.toPermSlugIfNeeded()] = seriesDetail.slug } + + seriesDetail.id + } + return result.getOrNull() ?: throw Exception(intl.idNotFoundError + slug) + } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - /** - * Their query search endpoint doesn't return the thumbnails, so we need to do - * later an special parsing to get the thumbnails as well from the slug map. - */ + if (useNewQueryEndpoint) { + return newEndpointSearchMangaRequest(page, query, filters) + } + if (query.isNotBlank()) { val searchPayloadObj = HeanCmsSearchPayloadDto(query) val searchPayload = json.encodeToString(searchPayloadObj) @@ -170,6 +274,28 @@ abstract class HeanCms( return POST("$apiUrl/series/querysearch", apiHeaders, payload) } + protected fun newEndpointSearchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val sortByFilter = filters.firstInstanceOrNull() + val statusFilter = filters.firstInstanceOrNull() + + val tagIds = filters.firstInstanceOrNull()?.state.orEmpty() + .filter(Genre::state) + .map(Genre::id) + .joinToString(",", prefix = "[", postfix = "]") + + val url = "$apiUrl/query".toHttpUrl().newBuilder() + .addQueryParameter("query_string", query) + .addQueryParameter("series_status", statusFilter?.selected?.value ?: "All") + .addQueryParameter("order", if (sortByFilter?.state?.ascending == true) "asc" else "desc") + .addQueryParameter("orderBy", sortByFilter?.selected ?: "total_views") + .addQueryParameter("series_type", "Comic") + .addQueryParameter("page", page.toString()) + .addQueryParameter("perPage", "12") + .addQueryParameter("tags_ids", tagIds) + + return GET(url.build(), headers) + } + override fun searchMangaParse(response: Response): MangasPage { val json = response.body.string() @@ -179,14 +305,23 @@ abstract class HeanCms( val result = json.parseAs>() val mangaList = result .filter { it.type == "Comic" } - .map { it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty()) } + .map { + it.slug = it.slug.toPermSlugIfNeeded() + it.toSManga(apiUrl, coverPath, mangaSubDirectory, seriesSlugMap.orEmpty(), slugStrategy) + } return MangasPage(mangaList, false) } if (json.startsWith("{")) { val result = json.parseAs() - val mangaList = result.data.map { it.toSManga(apiUrl, coverPath) } + val mangaList = result.data.map { + if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } + } + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) + } fetchAllTitles() @@ -194,7 +329,13 @@ abstract class HeanCms( } val mangaList = json.parseAs>() - .map { it.toSManga(apiUrl, coverPath) } + .map { + if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug } + } + it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) + } fetchAllTitles() @@ -204,17 +345,33 @@ abstract class HeanCms( override fun getMangaUrl(manga: SManga): String { val seriesSlug = manga.url .substringAfterLast("/") - .replace(TIMESTAMP_REGEX, "") + .substringBefore("#") + .toPermSlugIfNeeded() - val currentSlug = seriesSlugMap?.get(seriesSlug)?.slug ?: seriesSlug + val currentSlug = if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap[seriesSlug] ?: seriesSlug + } else { + seriesSlug + } - return "$baseUrl/series/$currentSlug" + return "$baseUrl/$mangaSubDirectory/$currentSlug" } override fun mangaDetailsRequest(manga: SManga): Request { + if (slugStrategy != SlugStrategy.NONE && (manga.url.contains(TIMESTAMP_REGEX))) { + throw Exception(intl.urlChangedError(name)) + } + + if (slugStrategy == SlugStrategy.ID && !manga.url.contains("#")) { + throw Exception(intl.urlChangedError(name)) + } + val seriesSlug = manga.url .substringAfterLast("/") - .replace(TIMESTAMP_REGEX, "") + .substringBefore("#") + .toPermSlugIfNeeded() + + val seriesId = manga.url.substringAfterLast("#") fetchAllTitles() @@ -226,17 +383,30 @@ abstract class HeanCms( .add("Accept", ACCEPT_JSON) .build() - return GET("$apiUrl/series/$currentSlug#$currentStatus", apiHeaders) + return if (slugStrategy == SlugStrategy.ID) { + GET("$apiUrl/series/id/$seriesId", apiHeaders) + } else { + GET("$apiUrl/series/$currentSlug#$currentStatus", apiHeaders) + } } override fun mangaDetailsParse(response: Response): SManga { + val mangaStatus = response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN + val result = runCatching { response.parseAs() } - val seriesDetails = result.getOrNull()?.toSManga(apiUrl, coverPath) - ?: throw Exception(intl.urlChangedError(name)) + + val seriesResult = result.getOrNull() ?: throw Exception(intl.urlChangedError(name)) + + if (slugStrategy != SlugStrategy.NONE) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { it[seriesResult.slug.toPermSlugIfNeeded()] = seriesResult.slug } + } + + val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy) return seriesDetails.apply { status = status.takeUnless { it == SManga.UNKNOWN } - ?: response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN + ?: mangaStatus } } @@ -244,20 +414,57 @@ abstract class HeanCms( override fun chapterListParse(response: Response): List { val result = response.parseAs() - val seriesSlug = response.request.url.pathSegments.last() + + if (slugStrategy == SlugStrategy.ID) { + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { it[result.slug.toPermSlugIfNeeded()] = result.slug } + } + val currentTimestamp = System.currentTimeMillis() + val showPaidChapters = preferences.showPaidChapters + + if (useNewQueryEndpoint) { + return result.seasons.orEmpty() + .flatMap { it.chapters.orEmpty() } + .filter { it.price == 0 || showPaidChapters } + .map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) } + .filter { it.date_upload <= currentTimestamp } + } + return result.chapters.orEmpty() - .filterNot { it.price == 1 } - .map { it.toSChapter(seriesSlug, dateFormat) } + .filter { it.price == 0 || showPaidChapters } + .map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) } .filter { it.date_upload <= currentTimestamp } .reversed() } - override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url + override fun getChapterUrl(chapter: SChapter): String { + if (slugStrategy == SlugStrategy.NONE) return baseUrl + chapter.url + + val seriesSlug = chapter.url + .substringAfter("/$mangaSubDirectory/") + .substringBefore("/") + .toPermSlugIfNeeded() + + val currentSlug = preferences.slugMap[seriesSlug] ?: seriesSlug + val chapterUrl = chapter.url.replaceFirst(seriesSlug, currentSlug) + + return baseUrl + chapterUrl + } override fun pageListRequest(chapter: SChapter): Request { - val chapterId = chapter.url.substringAfterLast("#") + if (useNewQueryEndpoint) { + if (slugStrategy != SlugStrategy.NONE) { + val seriesPermSlug = chapter.url.substringAfter("/$mangaSubDirectory/").substringBefore("/") + val seriesSlug = preferences.slugMap[seriesPermSlug] ?: seriesPermSlug + val chapterUrl = chapter.url.replaceFirst(seriesPermSlug, seriesSlug) + return GET(baseUrl + chapterUrl, headers) + } + return GET(baseUrl + chapter.url, headers) + } + + val chapterId = chapter.url.substringAfterLast("#").substringBefore("-paid") val apiHeaders = headersBuilder() .add("Accept", ACCEPT_JSON) @@ -267,15 +474,38 @@ abstract class HeanCms( } override fun pageListParse(response: Response): List { - return response.parseAs().content?.images.orEmpty() - .filterNot { imageUrl -> - // Their image server returns HTTP 403 for hidden files that starts - // with a dot in the file name. To avoid download errors, these are removed. - imageUrl - .removeSuffix("/") - .substringAfterLast("/") - .startsWith(".") + if (useNewQueryEndpoint) { + val paidChapter = response.request.url.fragment?.contains("-paid") + + val document = response.asJsoup() + + val images = document.selectFirst("div.min-h-screen > div.container > p.items-center") + + if (images == null && paidChapter == true) { + throw IOException(intl.paidChapterError) } + + return images?.select("img").orEmpty().mapIndexed { i, img -> + val imageUrl = if (img.hasClass("lazy")) img.absUrl("data-src") else img.absUrl("src") + Page(i, "", imageUrl) + } + } + + val images = response.parseAs().content?.images.orEmpty() + val paidChapter = response.request.url.fragment?.contains("-paid") + + if (images.isEmpty() && paidChapter == true) { + throw IOException(intl.paidChapterError) + } + + return images.filterNot { imageUrl -> + // Their image server returns HTTP 403 for hidden files that starts + // with a dot in the file name. To avoid download errors, these are removed. + imageUrl + .removeSuffix("/") + .substringAfterLast("/") + .startsWith(".") + } .mapIndexed { i, url -> Page(i, imageUrl = if (url.startsWith("http")) url else "$apiUrl/$url") } @@ -293,23 +523,8 @@ abstract class HeanCms( return GET(page.imageUrl!!, imageHeaders) } - protected open fun getStatusList(): List = listOf( - Status(intl.statusOngoing, "Ongoing"), - Status(intl.statusOnHiatus, "Hiatus"), - Status(intl.statusDropped, "Dropped"), - ) - - protected open fun getSortProperties(): List = listOf( - SortProperty(intl.sortByTitle, "title"), - SortProperty(intl.sortByViews, "total_views"), - SortProperty(intl.sortByLatest, "latest"), - SortProperty(intl.sortByRecentlyAdded, "recently_added"), - ) - - protected open fun getGenreList(): List = emptyList() - protected open fun fetchAllTitles() { - if (!seriesSlugMap.isNullOrEmpty() || !fetchAllTitles) { + if (!seriesSlugMap.isNullOrEmpty() || slugStrategy != SlugStrategy.FETCH_ALL) { return } @@ -338,9 +553,20 @@ abstract class HeanCms( } seriesSlugMap = result.getOrNull() + preferences.slugMap = preferences.slugMap.toMutableMap() + .also { it.putAll(seriesSlugMap.orEmpty().mapValues { (_, v) -> v.slug }) } } protected open fun allTitlesRequest(page: Int): Request { + if (useNewQueryEndpoint) { + val url = "$apiUrl/query".toHttpUrl().newBuilder() + .addQueryParameter("series_type", "Comic") + .addQueryParameter("page", page.toString()) + .addQueryParameter("perPage", PER_PAGE_MANGA_TITLES.toString()) + + return GET(url.build(), headers) + } + val payloadObj = HeanCmsQuerySearchPayloadDto( page = page, order = "desc", @@ -373,6 +599,49 @@ abstract class HeanCms( ) } + /** + * Used to store the current slugs for sources that change it periodically and for the + * search that doesn't return the thumbnail URLs. + */ + data class HeanCmsTitle(val slug: String, val thumbnailFileName: String, val status: Int) + + /** + * Used to specify the strategy to use when fetching the slug for a manga. + * This is needed because some sources change the slug periodically. + * [NONE]: Use series_slug without changes. + * [ID]: Use series_id to fetch the slug from the API. + * IMPORTANT: [ID] is only available in the new query endpoint. + * [FETCH_ALL]: Convert the slug to a permanent slug by removing the timestamp. + * At extension start, all the slugs are fetched and stored in a map. + */ + enum class SlugStrategy { + NONE, ID, FETCH_ALL + } + + private fun String.toPermSlugIfNeeded(): String { + return if (slugStrategy != SlugStrategy.NONE) { + this.replace(TIMESTAMP_REGEX, "") + } else { + this + } + } + + protected open fun getStatusList(): List = listOf( + Status(intl.statusAll, "All"), + Status(intl.statusOngoing, "Ongoing"), + Status(intl.statusOnHiatus, "Hiatus"), + Status(intl.statusDropped, "Dropped"), + ) + + protected open fun getSortProperties(): List = listOf( + SortProperty(intl.sortByTitle, "title"), + SortProperty(intl.sortByViews, "total_views"), + SortProperty(intl.sortByLatest, "latest"), + SortProperty(intl.sortByCreatedAt, "created_at"), + ) + + protected open fun getGenreList(): List = emptyList() + override fun getFilterList(): FilterList { val genres = getGenreList() @@ -386,20 +655,29 @@ abstract class HeanCms( return FilterList(filters) } - private inline fun Response.parseAs(): T = use { + protected inline fun Response.parseAs(): T = use { it.body.string().parseAs() } - private inline fun String.parseAs(): T = json.decodeFromString(this) + protected inline fun String.parseAs(): T = json.decodeFromString(this) - private inline fun List<*>.firstInstanceOrNull(): R? = + protected inline fun List<*>.firstInstanceOrNull(): R? = filterIsInstance().firstOrNull() - /** - * Used to store the current slugs for sources that change it periodically and for the - * search that doesn't return the thumbnail URLs. - */ - data class HeanCmsTitle(val slug: String, val thumbnailFileName: String, val status: Int) + protected var SharedPreferences.slugMap: MutableMap + get() { + val jsonMap = getString(PREF_URL_MAP_SLUG, "{}")!! + val slugMap = runCatching { json.decodeFromString>(jsonMap) } + return slugMap.getOrNull()?.toMutableMap() ?: mutableMapOf() + } + set(newSlugMap) { + edit() + .putString(PREF_URL_MAP_SLUG, json.encodeToString(newSlugMap)) + .apply() + } + + private val SharedPreferences.showPaidChapters: Boolean + get() = getBoolean(SHOW_PAID_CHAPTERS_PREF, SHOW_PAID_CHAPTERS_DEFAULT) companion object { private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" @@ -407,8 +685,15 @@ abstract class HeanCms( private val JSON_MEDIA_TYPE = "application/json".toMediaType() - val TIMESTAMP_REGEX = "-\\d+$".toRegex() + val TIMESTAMP_REGEX = """-\d{13}$""".toRegex() + + private const val PER_PAGE_MANGA_TITLES = 10000 const val SEARCH_PREFIX = "slug:" + + private const val PREF_URL_MAP_SLUG = "pref_url_map" + + private const val SHOW_PAID_CHAPTERS_PREF = "pref_show_paid_chap" + private const val SHOW_PAID_CHAPTERS_DEFAULT = false } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt index 72086cf7c3..9a400cc6f7 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsDto.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.multisrc.heancms +import eu.kanade.tachiyomi.multisrc.heancms.HeanCms.SlugStrategy import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName @@ -26,7 +27,7 @@ data class HeanCmsQuerySearchMetaDto( @Serializable data class HeanCmsSearchDto( val description: String? = null, - @SerialName("series_slug") val slug: String, + @SerialName("series_slug") var slug: String, @SerialName("series_type") val type: String, val title: String, val thumbnail: String? = null, @@ -35,15 +36,16 @@ data class HeanCmsSearchDto( fun toSManga( apiUrl: String, coverPath: String, + mangaSubDirectory: String, slugMap: Map, + slugStrategy: SlugStrategy, ): SManga = SManga.create().apply { - val slugOnly = slug.replace(HeanCms.TIMESTAMP_REGEX, "") + val slugOnly = slug.toPermSlugIfNeeded(slugStrategy) val thumbnailFileName = slugMap[slugOnly]?.thumbnailFileName - title = this@HeanCmsSearchDto.title thumbnail_url = thumbnail?.toAbsoluteThumbnailUrl(apiUrl, coverPath) ?: thumbnailFileName?.toAbsoluteThumbnailUrl(apiUrl, coverPath) - url = "/series/$slugOnly" + url = "/$mangaSubDirectory/$slugOnly" } } @@ -60,10 +62,17 @@ data class HeanCmsSeriesDto( val title: String, val tags: List? = emptyList(), val chapters: List? = emptyList(), + val seasons: List? = emptyList(), ) { - fun toSManga(apiUrl: String, coverPath: String): SManga = SManga.create().apply { + fun toSManga( + apiUrl: String, + coverPath: String, + mangaSubDirectory: String, + slugStrategy: SlugStrategy, + ): SManga = SManga.create().apply { val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment) + val slugOnly = slug.toPermSlugIfNeeded(slugStrategy) title = this@HeanCmsSeriesDto.title author = this@HeanCmsSeriesDto.author?.trim() @@ -77,10 +86,20 @@ data class HeanCmsSeriesDto( thumbnail_url = thumbnail.ifEmpty { null } ?.toAbsoluteThumbnailUrl(apiUrl, coverPath) status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN - url = "/series/${slug.replace(HeanCms.TIMESTAMP_REGEX, "")}" + url = if (slugStrategy != SlugStrategy.NONE) { + "/$mangaSubDirectory/$slugOnly#$id" + } else { + "/$mangaSubDirectory/$slug" + } } } +@Serializable +data class HeanCmsSeasonsDto( + val index: Int, + val chapters: List? = emptyList(), +) + @Serializable data class HeanCmsTagDto(val name: String) @@ -93,12 +112,25 @@ data class HeanCmsChapterDto( @SerialName("created_at") val createdAt: String, val price: Int? = null, ) { - - fun toSChapter(seriesSlug: String, dateFormat: SimpleDateFormat): SChapter = SChapter.create().apply { + fun toSChapter( + seriesSlug: String, + mangaSubDirectory: String, + dateFormat: SimpleDateFormat, + slugStrategy: SlugStrategy, + ): SChapter = SChapter.create().apply { + val seriesSlugOnly = seriesSlug.toPermSlugIfNeeded(slugStrategy) name = this@HeanCmsChapterDto.name.trim() + + if (price != 0) { + name += " \uD83D\uDD12" + } + date_upload = runCatching { dateFormat.parse(createdAt)?.time } .getOrNull() ?: 0L - url = "/series/$seriesSlug/$slug#$id" + + val paidStatus = if (price != 0 && price != null) "-paid" else "" + + url = "/$mangaSubDirectory/$seriesSlugOnly/$slug#$id$paidStatus" } } @@ -129,6 +161,14 @@ private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): St return if (startsWith("https://")) this else "$apiUrl/$coverPath$this" } +private fun String.toPermSlugIfNeeded(slugStrategy: SlugStrategy): String { + return if (slugStrategy != SlugStrategy.NONE) { + this.replace(HeanCms.TIMESTAMP_REGEX, "") + } else { + this + } +} + fun String.toStatus(): Int = when (this) { "Ongoing" -> SManga.ONGOING "Hiatus" -> SManga.ON_HIATUS diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt index 840a468aa8..7c1ea7c815 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsGenerator.kt @@ -9,13 +9,14 @@ class HeanCmsGenerator : ThemeSourceGenerator { override val themeClass = "HeanCms" - override val baseVersionCode: Int = 13 + override val baseVersionCode: Int = 20 override val sources = listOf( - SingleLang("Glorious Scan", "https://gloriousscan.com", "pt-BR", overrideVersionCode = 17), - SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true, overrideVersionCode = 17), - SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 35), - SingleLang("YugenMangas", "https://yugenmangas.net", "es", isNsfw = true, overrideVersionCode = 4), + SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true, overrideVersionCode = 18), + SingleLang("Perf Scan", "https://perf-scan.fr", "fr"), + SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 36), + SingleLang("Temple Scan", "https://templescan.net", "en", isNsfw = true, overrideVersionCode = 16), + SingleLang("YugenMangas", "https://yugenmangas.net", "es", isNsfw = true, overrideVersionCode = 9), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsIntl.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsIntl.kt index 433a13b0fb..520eec3819 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsIntl.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCmsIntl.kt @@ -16,6 +16,12 @@ class HeanCmsIntl(lang: String) { else -> "Status" } + val statusAll: String = when (availableLang) { + BRAZILIAN_PORTUGUESE -> "Todos" + SPANISH -> "Todos" + else -> "All" + } + val statusOngoing: String = when (availableLang) { BRAZILIAN_PORTUGUESE -> "Em andamento" SPANISH -> "En curso" @@ -58,10 +64,10 @@ class HeanCmsIntl(lang: String) { else -> "Latest" } - val sortByRecentlyAdded: String = when (availableLang) { + val sortByCreatedAt: String = when (availableLang) { BRAZILIAN_PORTUGUESE -> "Data de criação" - SPANISH -> "Añadido recientemente" - else -> "Recently added" + SPANISH -> "Fecha de creación" + else -> "Created at" } val filterWarning: String = when (availableLang) { @@ -70,6 +76,26 @@ class HeanCmsIntl(lang: String) { else -> "Filters will be ignored if the search is not empty." } + val prefShowPaidChapterTitle: String = when (availableLang) { + SPANISH -> "Mostrar capítulos de pago" + else -> "Display paid chapters" + } + + val prefShowPaidChapterSummaryOn: String = when (availableLang) { + SPANISH -> "Se mostrarán capítulos de pago. Deberá iniciar sesión" + else -> "Paid chapters will appear. A login might be needed!" + } + + val prefShowPaidChapterSummaryOff: String = when (availableLang) { + SPANISH -> "Solo se mostrarán los capítulos gratuitos" + else -> "Only free chapters will be displayed." + } + + val paidChapterError: String = when (availableLang) { + SPANISH -> "Capítulo no disponible. Debe iniciar sesión en Webview y tener el capítulo comprado." + else -> "Paid chapter unavailable.\nA login/purchase might be needed (using webview)." + } + fun urlChangedError(sourceName: String): String = when (availableLang) { BRAZILIAN_PORTUGUESE -> "A URL da série mudou. Migre de $sourceName " + @@ -82,6 +108,12 @@ class HeanCmsIntl(lang: String) { "to $sourceName to update the URL." } + val idNotFoundError: String = when (availableLang) { + BRAZILIAN_PORTUGUESE -> "Falha ao obter o ID do slug: " + SPANISH -> "No se pudo encontrar el ID para: " + else -> "Failed to get the ID for slug: " + } + companion object { const val BRAZILIAN_PORTUGUESE = "pt-BR" const val ENGLISH = "en" diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/Kemono.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/Kemono.kt index 0585c1f06b..34553c8c5b 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/Kemono.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/Kemono.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.multisrc.kemono import android.app.Application import androidx.preference.ListPreference import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.ConfigurableSource @@ -20,7 +21,6 @@ import okhttp3.Callback import okhttp3.Request import okhttp3.Response import okio.blackholeSink -import org.jsoup.nodes.Element import org.jsoup.select.Evaluator import rx.Observable import uy.kohesive.injekt.Injekt @@ -32,12 +32,12 @@ import kotlin.math.min open class Kemono( override val name: String, - override val baseUrl: String, + private val defaultUrl: String, override val lang: String = "all", ) : HttpSource(), ConfigurableSource { override val supportsLatest = true - private val isNewDesign get() = name == "Kemono" + private val mirrorUrls get() = arrayOf(defaultUrl, defaultUrl.removeSuffix(".party") + ".su") override val client = network.client.newBuilder().rateLimit(2).build() @@ -46,47 +46,35 @@ open class Kemono( private val json: Json by injectLazy() - private val preferences by lazy { + private val preferences = Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/artists?o=${PAGE_SIZE * (page - 1)}", headers) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val cardList = document.selectFirst(Evaluator.Class("card-list"))!! - val creators = cardList.select(Evaluator.Tag("article")).map { - val children = it.children() - val avatar = children[0].selectFirst(Evaluator.Tag("img"))!!.attr("src") - val link = children[1].child(0) - val service = children[2].ownText() - SManga.create().apply { - url = link.attr("href") - title = link.ownText() - author = service - thumbnail_url = baseUrl + avatar - description = PROMPT - initialized = true - } - }.filterUnsupported() - return MangasPage(creators, document.hasNextPage()) - } + override val baseUrl = preferences.getString(BASE_URL_PREF, defaultUrl)!! + + private val apiPath = "api/v1" + + private val imgCdnUrl = when (name) { + "Kemono" -> baseUrl + else -> defaultUrl + }.replace("//", "//img.") - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/artists/updated?o=${PAGE_SIZE * (page - 1)}", headers) + private fun String.formatAvatarUrl(): String = removePrefix("https://").replaceBefore('/', imgCdnUrl) - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) + override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException() + + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException() + + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() + + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException() override fun fetchPopularManga(page: Int): Observable { - if (!isNewDesign) return super.fetchPopularManga(page) return Observable.fromCallable { fetchNewDesignListing(page, "/artists", compareByDescending { it.favorited }) } } override fun fetchLatestUpdates(page: Int): Observable { - if (!isNewDesign) return super.fetchLatestUpdates(page) return Observable.fromCallable { fetchNewDesignListing(page, "/artists/updated", compareByDescending { it.updatedDate }) } @@ -106,7 +94,7 @@ open class Kemono( url = it.attr("href") title = it.selectFirst(Evaluator.Class("user-card__name"))!!.ownText() author = it.selectFirst(Evaluator.Class("user-card__service"))!!.ownText() - thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img"))!!.attr("src") + thumbnail_url = it.selectFirst(Evaluator.Tag("img"))!!.absUrl("src").formatAvatarUrl() description = PROMPT initialized = true } @@ -135,14 +123,14 @@ open class Kemono( page: Int, block: (ArrayList) -> List, ): MangasPage { - val baseUrl = this.baseUrl - val response = client.newCall(GET("$baseUrl/api/creators", headers)).execute() + val imgCdnUrl = this.imgCdnUrl + val response = client.newCall(GET("$baseUrl/$apiPath/creators", headers)).execute() val allCreators = block(response.parseAs()) val count = allCreators.size val fromIndex = (page - 1) * NEW_PAGE_SIZE val toIndex = min(count, fromIndex + NEW_PAGE_SIZE) val creators = allCreators.subList(fromIndex, toIndex) - .map { it.toSManga(baseUrl) } + .map { it.toSManga(imgCdnUrl) } .filterUnsupported() return MangasPage(creators, toIndex < count) } @@ -157,15 +145,18 @@ open class Kemono( override fun onFailure(call: Call, e: IOException) = Unit } - client.newCall(GET("$baseUrl/api/creators", headers)).enqueue(callback) + client.newCall(GET("$baseUrl/$apiPath/creators", headers)).enqueue(callback) } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used.") - override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not used.") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException() + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException() - override fun fetchMangaDetails(manga: SManga): Observable = Observable.just(manga) + override fun fetchMangaDetails(manga: SManga): Observable { + manga.thumbnail_url = manga.thumbnail_url!!.formatAvatarUrl() + return Observable.just(manga) + } - override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used.") + override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException() override fun fetchChapterList(manga: SManga): Observable> = Observable.fromCallable { KemonoPostDto.dateFormat.timeZone = when (manga.author) { @@ -178,7 +169,7 @@ open class Kemono( var hasNextPage = true val result = ArrayList() while (offset < maxPosts && hasNextPage) { - val request = GET("$baseUrl/api${manga.url}?limit=$POST_PAGE_SIZE&o=$offset", headers) + val request = GET("$baseUrl/$apiPath${manga.url}?limit=$POST_PAGE_SIZE&o=$offset", headers) val page: List = client.newCall(request).execute().parseAs() page.forEach { post -> if (post.images.isNotEmpty()) result.add(post.toSChapter()) } offset += POST_PAGE_SIZE @@ -187,17 +178,29 @@ open class Kemono( result } - override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used.") + override fun chapterListParse(response: Response) = throw UnsupportedOperationException() override fun pageListRequest(chapter: SChapter): Request = - GET("$baseUrl/api${chapter.url}", headers) + GET("$baseUrl/$apiPath${chapter.url}", headers) override fun pageListParse(response: Response): List { - val post: List = response.parseAs() - return post[0].images.mapIndexed { i, path -> Page(i, imageUrl = baseUrl + path) } + val post: KemonoPostDto = response.parseAs() + return post.images.mapIndexed { i, path -> Page(i, imageUrl = baseUrl + path) } } - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.") + override fun imageRequest(page: Page): Request { + val imageUrl = page.imageUrl!! + if (!preferences.getBoolean(USE_LOW_RES_IMG, false)) return GET(imageUrl, headers) + val index = imageUrl.indexOf('/', startIndex = 8) // https:// + val url = buildString { + append(imageUrl, 0, index) + append("/thumbnail") + append(imageUrl, index, imageUrl.length) + } + return GET(url, headers) + } + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() private inline fun Response.parseAs(): T = use { json.decodeFromStream(it.body.byteStream()) @@ -214,10 +217,25 @@ open class Kemono( }.toTypedArray() setDefaultValue(POST_PAGES_DEFAULT) }.let { screen.addPreference(it) } + + ListPreference(screen.context).apply { + key = BASE_URL_PREF + title = "Mirror URL" + summary = "%s\nRequires app restart to take effect" + entries = mirrorUrls + entryValues = mirrorUrls + setDefaultValue(defaultUrl) + }.let(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = USE_LOW_RES_IMG + title = "Use low resolution images" + summary = "Reduce load time significantly. When turning off, clear chapter cache to remove cached low resolution images." + setDefaultValue(false) + }.let(screen::addPreference) } companion object { - private const val PAGE_SIZE = 25 private const val NEW_PAGE_SIZE = 50 const val PROMPT = "You can change how many posts to load in the extension preferences." @@ -226,11 +244,9 @@ open class Kemono( private const val POST_PAGES_DEFAULT = "1" private const val POST_PAGES_MAX = 50 - private fun Element.hasNextPage(): Boolean { - val pagination = selectFirst(Evaluator.Class("paginator"))!! - return pagination.selectFirst("a[title=Next page]") != null - } - private fun List.filterUnsupported() = filterNot { it.author == "Discord" } + + private const val BASE_URL_PREF = "BASE_URL" + private const val USE_LOW_RES_IMG = "USE_LOW_RES_IMG" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoDto.kt index 4a7bcbc4e4..75d0d77628 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoDto.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoDto.kt @@ -21,11 +21,11 @@ class KemonoCreatorDto( else -> (updated.double * 1000).toLong() } - fun toSManga(baseUrl: String) = SManga.create().apply { + fun toSManga(imgCdnUrl: String) = SManga.create().apply { url = "/$service/user/$id" // should be /server/ for Discord but will be filtered anyway title = name author = service.serviceName() - thumbnail_url = "$baseUrl/icons/$service/$id" + thumbnail_url = "$imgCdnUrl/icons/$service/$id" description = Kemono.PROMPT initialized = true } @@ -97,7 +97,7 @@ class KemonoAttachmentDto(val name: String, val path: String) { } private fun getApiDateFormat() = - SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH) + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH) private fun getChapterNameDateFormat() = SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss", Locale.ENGLISH) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoGenerator.kt index 8fdc9c010f..adede66221 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/kemono/KemonoGenerator.kt @@ -9,7 +9,7 @@ class KemonoGenerator : ThemeSourceGenerator { override val themePkg = "kemono" - override val baseVersionCode = 5 + override val baseVersionCode = 8 override val sources = listOf( SingleLang("Kemono", "https://kemono.party", "all", isNsfw = true), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGenerator.kt index b76e5389ba..12bd8523df 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGenerator.kt @@ -9,11 +9,11 @@ class LibGenerator : ThemeSourceGenerator { override val themeClass = "LibGroup" - override val baseVersionCode: Int = 21 + override val baseVersionCode: Int = 25 override val sources = listOf( SingleLang("MangaLib", "https://mangalib.me", "ru", overrideVersionCode = 74), - SingleLang("HentaiLib", "https://v1.hentailib.org", "ru", isNsfw = true, overrideVersionCode = 19), + SingleLang("HentaiLib", "https://hentailib.me", "ru", isNsfw = true, overrideVersionCode = 19), SingleLang("YaoiLib", "https://v1.slashlib.me", "ru", isNsfw = true, overrideVersionCode = 2), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGroup.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGroup.kt index 558d7a45dc..a8dfce12cf 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGroup.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/libgroup/LibGroup.kt @@ -45,6 +45,7 @@ import uy.kohesive.injekt.injectLazy import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit import kotlin.math.absoluteValue import kotlin.random.Random @@ -77,6 +78,9 @@ abstract class LibGroup( } override val client: OkHttpClient = network.cloudflareClient.newBuilder() .rateLimit(3) + .connectTimeout(5, TimeUnit.MINUTES) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) .addNetworkInterceptor { imageContentTypeIntercept(it) } .addInterceptor { chain -> val response = chain.proceed(chain.request()) @@ -146,7 +150,7 @@ abstract class LibGroup( popularMangaParse(response) // Popular - override fun popularMangaRequest(page: Int) = GET("$baseUrl/?section=home-updates", headers) + override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) override fun fetchPopularManga(page: Int): Observable { if (csrfToken.isEmpty()) { return client.newCall(popularMangaRequest(page)) @@ -308,7 +312,7 @@ abstract class LibGroup( } val redirect = document.html() if (redirect.contains("paper empty section")) { - return emptyList() + throw Exception("Лицензировано - Нет глав") } val dataStr = document .toString() @@ -410,7 +414,7 @@ abstract class LibGroup( chapter.scanlator = if (teams?.size == 1) teams[0].jsonObject["name"]?.jsonPrimitive?.content else if (isScanlatorId.orEmpty().isNotEmpty()) isScanlatorId!![0].jsonObject["name"]?.jsonPrimitive?.content else branches?.let { getScanlatorTeamName(it, chapterItem) } ?: if ((preferences.getBoolean(isScan_USER, false)) || (chaptersList?.distinctBy { it.jsonObject["username"]!!.jsonPrimitive.content }?.size == 1)) chapterItem.jsonObject["username"]!!.jsonPrimitive.content else null chapter.name = if (nameChapter.isNullOrBlank()) fullNameChapter else "$fullNameChapter - $nameChapter" chapter.date_upload = simpleDateFormat.parse(chapterItem.jsonObject["chapter_created_at"]!!.jsonPrimitive.content.substringBefore(" "))?.time ?: 0L - chapter.chapter_number = number.toFloat() + chapter.chapter_number = number.toFloatOrNull() ?: -1f return chapter } @@ -461,15 +465,9 @@ abstract class LibGroup( val chapInfoJson = json.decodeFromString(chapInfo) val servers = chapInfoJson["servers"]!!.jsonObject.toMap() - val defaultServer: String = chapInfoJson["img"]!!.jsonObject["server"]!!.jsonPrimitive.content - val autoServer = setOf("secondary", "fourth", defaultServer, "compress") val imgUrl: String = chapInfoJson["img"]!!.jsonObject["url"]!!.jsonPrimitive.content - val serverToUse = when (this.server) { - null -> autoServer - "auto" -> autoServer - else -> listOf(this.server) - } + val serverToUse = listOf(isServer, "secondary", "fourth", "main", "compress").distinct() // Get pages val pagesArr = document @@ -487,7 +485,7 @@ abstract class LibGroup( val keys = servers.keys.filter { serverToUse.indexOf(it) >= 0 }.sortedBy { serverToUse.indexOf(it) } val serversUrls = keys.map { servers[it]?.jsonPrimitive?.contentOrNull + imgUrl + page.jsonObject["u"]!!.jsonPrimitive.content - }.joinToString(separator = ",,") { it } + }.distinct().joinToString(separator = ",,") { it } pages.add(Page(page.jsonObject["p"]!!.jsonPrimitive.int, serversUrls)) } @@ -495,7 +493,7 @@ abstract class LibGroup( } private fun checkImage(url: String): Boolean { - val response = client.newCall(Request.Builder().url(url).headers(imgHeader()).build()).execute() + val response = client.newCall(GET(url, imgHeader())).execute() return response.isSuccessful && (response.header("content-length", "0")?.toInt()!! > 600) } @@ -505,9 +503,6 @@ abstract class LibGroup( } val urls = page.url.split(",,") - if (urls.size == 1) { - return Observable.just(urls[0]) - } return Observable.from(urls).filter { checkImage(it) }.first() } @@ -726,7 +721,6 @@ abstract class LibGroup( companion object { const val PREFIX_SLUG_SEARCH = "slug:" private const val SERVER_PREF = "MangaLibImageServer" - private const val SERVER_PREF_Title = "Сервер изображений" private const val SORTING_PREF = "MangaLibSorting" private const val SORTING_PREF_Title = "Способ выбора переводчиков" @@ -743,19 +737,21 @@ abstract class LibGroup( private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.US) } } - private var server: String? = preferences.getString(SERVER_PREF, null) + private var isServer: String? = preferences.getString(SERVER_PREF, "fourth") private var isEng: String? = preferences.getString(LANGUAGE_PREF, "eng") private var groupTranslates: String = preferences.getString(TRANSLATORS_TITLE, TRANSLATORS_DEFAULT)!! override fun setupPreferenceScreen(screen: PreferenceScreen) { val serverPref = ListPreference(screen.context).apply { key = SERVER_PREF - title = SERVER_PREF_Title - entries = arrayOf("Основной", "Второй (тестовый)", "Третий (эконом трафика)", "Авто") - entryValues = arrayOf("secondary", "fourth", "compress", "auto") - summary = "%s" - setDefaultValue("auto") + title = "Сервер изображений" + entries = arrayOf("Первый", "Второй", "Сжатия") + entryValues = arrayOf("secondary", "fourth", "compress") + summary = "%s \n\nВыбор приоритетного сервера изображений. \n" + + "По умолчанию «Второй». \n\n" + + "ⓘВыбор другого помогает при долгой автоматической смене/загрузке изображений текущего." + setDefaultValue("fourth") setOnPreferenceChangeListener { _, newValue -> - server = newValue.toString() + isServer = newValue.toString() true } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt index 249fd7724b..6f12718793 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt @@ -3,12 +3,12 @@ package eu.kanade.tachiyomi.multisrc.madara import android.app.Application import android.content.SharedPreferences import android.util.Base64 -import android.util.Log -import android.widget.Toast -import androidx.preference.EditTextPreference import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES +import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservable @@ -21,16 +21,13 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import okhttp3.CacheControl import okhttp3.FormBody -import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -40,7 +37,6 @@ import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.io.IOException import java.text.ParseException import java.text.SimpleDateFormat import java.util.Calendar @@ -60,88 +56,19 @@ abstract class Madara( override val supportsLatest = true - // override with true if you want useRandomUserAgentByDefault to be on by default for some source - protected open val useRandomUserAgentByDefault: Boolean = false - - /** - * override include/exclude user-agent string if needed - * some example: - * listOf("chrome") - * listOf("linux", "windows") - * listOf("108") - */ - protected open val filterIncludeUserAgent: List = listOf() - protected open val filterExcludeUserAgent: List = listOf() - - private var userAgent: String? = null - private var checkedUa = false - - private val hasUaIntercept by lazy { - client.interceptors.toString().contains("uaIntercept") - } - - protected val uaIntercept = object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val useRandomUa = preferences.getBoolean(PREF_KEY_RANDOM_UA, false) - val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "") - - try { - if (hasUaIntercept && (useRandomUa || customUa!!.isNotBlank())) { - Log.i("Extension_setting", "$TITLE_RANDOM_UA or $TITLE_CUSTOM_UA option is ENABLED") - - if (customUa!!.isNotBlank() && useRandomUa.not()) { - userAgent = customUa - } - - if (userAgent.isNullOrBlank() && !checkedUa) { - val uaResponse = chain.proceed(GET(UA_DB_URL)) - - if (uaResponse.isSuccessful) { - var listUserAgentString = - json.decodeFromString>>(uaResponse.body.string())["desktop"] - - if (filterIncludeUserAgent.isNotEmpty()) { - listUserAgentString = listUserAgentString!!.filter { - filterIncludeUserAgent.any { filter -> - it.contains(filter, ignoreCase = true) - } - } - } - if (filterExcludeUserAgent.isNotEmpty()) { - listUserAgentString = listUserAgentString!!.filterNot { - filterExcludeUserAgent.any { filter -> - it.contains(filter, ignoreCase = true) - } - } - } - userAgent = listUserAgentString!!.random() - checkedUa = true - } - - uaResponse.close() - } - - if (userAgent.isNullOrBlank().not()) { - val newRequest = chain.request().newBuilder() - .header("User-Agent", userAgent!!.trim()) - .build() - - return chain.proceed(newRequest) - } - } - - return chain.proceed(chain.request()) - } catch (e: Exception) { - throw IOException(e.message) - } - } + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .setRandomUserAgent( + preferences.getPrefUAType(), + preferences.getPrefCustomUA(), + ) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() } - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(uaIntercept) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") protected open val json: Json by injectLazy() @@ -187,9 +114,6 @@ abstract class Madara( */ protected open val mangaSubString = "manga" - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") - // Popular Manga override fun popularMangaParse(response: Response): MangasPage { @@ -549,7 +473,7 @@ abstract class Madara( "OnGoing", "Продолжается", "Updating", "Em Lançamento", "Em lançamento", "Em andamento", "Em Andamento", "En cours", "En Cours", "En cours de publication", "Ativo", "Lançando", "Đang Tiến Hành", "Devam Ediyor", "Devam ediyor", "In Corso", "In Arrivo", "مستمرة", "مستمر", "En Curso", "En curso", "Emision", - "Curso", "En marcha", "Publicandose", "En emision", "连载中", + "Curso", "En marcha", "Publicandose", "En emision", "连载中", "Em Lançamento", ) protected val hiatusStatusList: Array = arrayOf( @@ -713,7 +637,6 @@ abstract class Madara( val xhrHeaders = headersBuilder() .add("Content-Length", form.contentLength().toString()) .add("Content-Type", form.contentType().toString()) - .add("Referer", "$baseUrl/") .add("X-Requested-With", "XMLHttpRequest") .build() @@ -722,7 +645,6 @@ abstract class Madara( protected open fun xhrChaptersRequest(mangaUrl: String): Request { val xhrHeaders = headersBuilder() - .add("Referer", "$baseUrl/") .add("X-Requested-With", "XMLHttpRequest") .build() @@ -1017,57 +939,6 @@ abstract class Madara( } } - override fun setupPreferenceScreen(screen: PreferenceScreen) { - if (hasUaIntercept) { - val prefUserAgent = SwitchPreferenceCompat(screen.context).apply { - key = PREF_KEY_RANDOM_UA - title = TITLE_RANDOM_UA - summary = if (preferences.getBoolean(PREF_KEY_RANDOM_UA, useRandomUserAgentByDefault)) userAgent else "" - setDefaultValue(useRandomUserAgentByDefault) - - setOnPreferenceChangeListener { _, newValue -> - val useRandomUa = newValue as Boolean - preferences.edit().putBoolean(PREF_KEY_RANDOM_UA, useRandomUa).apply() - if (!useRandomUa) { - Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show() - } else { - userAgent = null - if (preferences.getString(PREF_KEY_CUSTOM_UA, "").isNullOrBlank().not()) { - Toast.makeText(screen.context, SUMMARY_CLEANING_CUSTOM_UA, Toast.LENGTH_LONG).show() - } - } - - preferences.edit().putString(PREF_KEY_CUSTOM_UA, "").apply() - // prefCustomUserAgent.summary = "" - true - } - } - screen.addPreference(prefUserAgent) - - val prefCustomUserAgent = EditTextPreference(screen.context).apply { - key = PREF_KEY_CUSTOM_UA - title = TITLE_CUSTOM_UA - summary = preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim() - setOnPreferenceChangeListener { _, newValue -> - val customUa = newValue as String - preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply() - if (customUa.isBlank()) { - Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show() - } else { - userAgent = null - } - summary = customUa.trim() - prefUserAgent.summary = "" - prefUserAgent.isChecked = false - true - } - } - screen.addPreference(prefCustomUserAgent) - } else { - Toast.makeText(screen.context, DOESNOT_SUPPORT_STRING, Toast.LENGTH_LONG).show() - } - } - // https://stackoverflow.com/a/66614516 private fun String.decodeHex(): ByteArray { check(length % 2 == 0) { "Must have an even length" } @@ -1077,20 +948,12 @@ abstract class Madara( .toByteArray() } - companion object { - const val TITLE_RANDOM_UA = "Use Random Latest User-Agent" - const val PREF_KEY_RANDOM_UA = "pref_key_random_ua" - - const val TITLE_CUSTOM_UA = "Custom User-Agent" - const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua" - - const val SUMMARY_CLEANING_CUSTOM_UA = "$TITLE_CUSTOM_UA cleared." + override fun setupPreferenceScreen(screen: PreferenceScreen) { + addRandomUAPreferenceToScreen(screen) + } - const val RESTART_APP_STRING = "Restart Tachiyomi to apply new setting." - const val DOESNOT_SUPPORT_STRING = "This extension doesn't support User-Agent options." + companion object { const val URL_SEARCH_PREFIX = "slug:" - private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" - val SALTED = "Salted__".toByteArray(Charsets.UTF_8) } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt index 76679aee87..65e01f374a 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/MadaraGenerator.kt @@ -10,7 +10,7 @@ class MadaraGenerator : ThemeSourceGenerator { override val themeClass = "Madara" - override val baseVersionCode: Int = 30 + override val baseVersionCode: Int = 32 override val sources = listOf( @@ -31,45 +31,48 @@ class MadaraGenerator : ThemeSourceGenerator { // Extenções Oficiais: MultiLang("Atlantis Scan", "https://atlantisscan.com", listOf("es", "pt-BR"), isNsfw = true), - MultiLang("Leviatan Scans", "https://leviatanscans.com", listOf("en", "es"), className = "LeviatanScansFactory", overrideVersionCode = 14), MultiLang("MangaForFree.net", "https://mangaforfree.net", listOf("en", "ko", "all"), isNsfw = true, className = "MangaForFreeFactory", pkgName = "mangaforfree", overrideVersionCode = 1), MultiLang("Manhwa18.cc", "https://manhwa18.cc", listOf("en", "ko", "all"), isNsfw = true, className = "Manhwa18CcFactory", pkgName = "manhwa18cc", overrideVersionCode = 4), MultiLang("Reaper Scans", "https://reaperscans.com", listOf("fr", "tr"), className = "ReaperScansFactory", pkgName = "reaperscans", overrideVersionCode = 12), - SingleLang("1st Kiss Manga.love", "https://1stkissmanga.love", "en", className = "FirstKissMangaLove", overrideVersionCode = 2), - SingleLang("1st Kiss Manhua", "https://1stkissmanhua.com", "en", className = "FirstKissManhua", overrideVersionCode = 4), - SingleLang("1st Kiss", "https://1stkissmanga.me", "en", className = "FirstKissManga", pkgName = "firstkissmanga", overrideVersionCode = 9), + SingleLang("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en", isNsfw = false, className = "FirstKissDashManga"), + SingleLang("1st Manhwa", "https://1stmanhwa.com", "en", isNsfw = true, className = "FirstManhwa"), + SingleLang("1stKissManga.blog", "https://1stkissmanga.blog", "en", isNsfw = true, className = "FirstKissMangaBlog"), SingleLang("1stKissManga.Club", "https://1stkissmanga.club", "en", className = "FirstKissMangaClub", overrideVersionCode = 2), + SingleLang("1stKissManga.tv", "https://1stkissmanga.tv", "en", isNsfw = true, className = "FirstKissMangaTv"), SingleLang("247Manga", "https://247manga.com", "en", className = "Manga247", overrideVersionCode = 1), SingleLang("365Manga", "https://365manga.com", "en", className = "ThreeSixtyFiveManga", overrideVersionCode = 1), SingleLang("Adonis Fansub", "https://manga.adonisfansub.com", "tr", overrideVersionCode = 1), SingleLang("Adult Webtoon", "https://adultwebtoon.com", "en", isNsfw = true, overrideVersionCode = 1), - SingleLang("AiYuManga", "https://aiyumangascanlation.com", "es", overrideVersionCode = 1), + SingleLang("Akimangá", "https://akimanga.com", "pt-BR", isNsfw = true, className = "Akimanga"), SingleLang("Akuma no Tenshi", "https://akumanotenshi.com", "pt-BR", className = "AkumaNoTenshi"), - SingleLang("AkuManga", "https://akumanga.com", "ar", overrideVersionCode = 1), + SingleLang("AkuManga", "https://akumanga.com", "en", isNsfw = true, overrideVersionCode = 2), SingleLang("Akuzenai Arts", "https://akuzenaiarts.org", "en"), - SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"), SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true), - SingleLang("Aln Scans", "https://alnscans.com", "en"), SingleLang("Amuy", "https://apenasmaisumyaoi.com", "pt-BR", isNsfw = true, overrideVersionCode = 1), SingleLang("Anikiga", "https://anikiga.com", "tr"), SingleLang("Anisa Manga", "https://anisamanga.com", "tr"), - SingleLang("Ansh Scans", "https://anshscans.org", "en"), + SingleLang("Ansh Scans", "https://anshscans.org", "en", overrideVersionCode = 1), SingleLang("ApollComics", "https://apollcomics.xyz", "es", isNsfw = true, overrideVersionCode = 2), SingleLang("Apolltoons", "https://apolltoons.xyz", "es", isNsfw = true), - SingleLang("Aqua Manga", "https://aquamanga.com", "en", overrideVersionCode = 3), + SingleLang("Aqua Manga", "https://aquamanga.com", "en", overrideVersionCode = 7), + SingleLang("AQUA Scans", "https://aquascans.com", "en", className = "ManhwaWorld", overrideVersionCode = 1), SingleLang("ArazNovel", "https://www.araznovel.com", "tr", overrideVersionCode = 3), + SingleLang("ArcheR Scans", "https://www.archerscans.com", "en", isNsfw = false), SingleLang("Arthur Scan", "https://arthurscan.xyz", "pt-BR", overrideVersionCode = 4), SingleLang("Astral Library", "https://www.astrallibrary.net", "en", overrideVersionCode = 2), SingleLang("Astral-Manga", "https://astral-manga.fr", "fr", className = "AstralManga"), - SingleLang("Atikrost", "https://atikrost.com", "tr", overrideVersionCode = 1), + SingleLang("Astrum Scans", "https://astrumscans.xyz", "pt-BR", isNsfw = true), + SingleLang("Asura Scans.us (unoriginal)", "https://asurascans.us", "en", isNsfw = false, className = "AsuraScansUs"), SingleLang("AZManhwa", "https://azmanhwa.net", "en"), SingleLang("Azora", "https://azoranov.com", "ar", overrideVersionCode = 6), + SingleLang("Babel Wuxia", "https://read.babelwuxia.com", "en"), SingleLang("Bakaman", "https://bakaman.net", "th", overrideVersionCode = 1), SingleLang("Banana Cítrica", "https://bananacitrica.com", "pt-BR", isNsfw = true, pkgName = "bananamecanica", className = "BananaCitrica", overrideVersionCode = 4), + SingleLang("Banana Manga", "https://bananamanga.net", "en", isNsfw = true), + SingleLang("BarManga", "https://barmanga.com", "es"), SingleLang("BestManga", "https://bestmanga.club", "ru", overrideVersionCode = 1), SingleLang("BestManhua", "https://bestmanhua.com", "en", overrideVersionCode = 2), - SingleLang("Bichen Traduções", "https://bichentraducoes.com", "pt-BR", isNsfw = true, className = "BichenTraducoes"), - SingleLang("BL Manhwa Club", "https://blmanhwa.club", "pt-BR", isNsfw = true, className = "BlManhwaClub", overrideVersionCode = 2), + SingleLang("BirdToon", "https://birdtoon.net", "id", isNsfw = true), SingleLang("BlogManga", "https://blogmanga.net", "en"), SingleLang("Blue Solo", "https://www1.bluesolo.org", "fr", isNsfw = true), SingleLang("BokugenTranslation", "https://bokugents.com", "es", overrideVersionCode = 1), @@ -79,375 +82,415 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("CAT-translator", "https://cats-translator.com/manga", "th", className = "CatTranslator", overrideVersionCode = 2), SingleLang("Cat300", "https://cat300.com", "th", isNsfw = true, className = "Cat300", overrideVersionCode = 1), SingleLang("CatOnHeadTranslations", "https://catonhead.com", "en", overrideVersionCode = 2), - SingleLang("Cerise Scans", "https://cerisescans.com", "pt-BR", overrideVersionCode = 5), + SingleLang("Cerise Scan", "https://cerisescan.com", "pt-BR", pkgName = "cerisescans", isNsfw = true, overrideVersionCode = 7), SingleLang("Chibi Manga", "https://www.cmreader.info", "en", overrideVersionCode = 1), + SingleLang("Çizgi Roman Arşivi", "https://cizgiromanarsivi.com", "tr", className = "CizgiRomanArsivi"), SingleLang("Clover Manga", "https://clover-manga.com", "tr", overrideVersionCode = 2), - SingleLang("Coffee Manga", "https://coffeemanga.io", "en", overrideVersionCode = 1), - SingleLang("Colored Manga", "https://coloredmanga.com", "en", overrideVersionCode = 1), + SingleLang("Coco Rip", "https://cocorip.net", "es"), + SingleLang("Coffee Manga", "https://coffeemanga.io", "en", isNsfw = false, overrideVersionCode = 2), + SingleLang("CoffeeManga.top (unoriginal)", "https://coffeemanga.top", "en", isNsfw = true, className = "CoffeeMangaTop"), + SingleLang("Colored Manga", "https://coloredmanga.com", "en", overrideVersionCode = 2), + SingleLang("Comic Scans", "https://www.comicscans.org", "en", isNsfw = false), SingleLang("ComicKiba", "https://comickiba.com", "en", overrideVersionCode = 1), SingleLang("Comics Valley", "https://comicsvalley.com", "hi", isNsfw = true, overrideVersionCode = 1), SingleLang("ComicsWorld", "https://comicsworld.in", "hi"), - SingleLang("Comictoon", "https://comictoonthaith-new.com", "th", isNsfw = true), - SingleLang("CookieToon", "https://cookietoon.online", "pt-BR"), + SingleLang("Comicz.net v2", "https://v2.comiz.net", "all", isNsfw = true, className = "ComiczNetV2"), SingleLang("Cookie Kiara", "https://18.kiara.cool", "en", isNsfw = true), SingleLang("CopyPasteScan", "https://copypastescan.xyz", "es", overrideVersionCode = 1), - SingleLang("Cronos Scan", "https://cronosscan.net", "pt-BR", overrideVersionCode = 1), SingleLang("DapRob", "https://daprob.com", "es"), - SingleLang("Dark Scans", "https://darkscans.com", "en"), + SingleLang("Dark Scans", "https://darkscans.com", "en", overrideVersionCode = 1), SingleLang("Decadence Scans", "https://reader.decadencescans.com", "en", isNsfw = true, overrideVersionCode = 2), + SingleLang("Demon Sect", "https://demonsect.com.br", "pt-BR", pkgName = "prismascans", overrideVersionCode = 4), SingleLang("Dessert Scan", "https://cabaredowatame.site", "pt-BR", isNsfw = true), SingleLang("DiamondFansub", "https://diamondfansub.com", "tr", overrideVersionCode = 1), SingleLang("DokkoManga", "https://dokkomanga.com", "es", overrideVersionCode = 1), SingleLang("Doodmanga", "https://www.doodmanga.com", "th"), SingleLang("DoujinHentai", "https://doujinhentai.net", "es", isNsfw = true, overrideVersionCode = 1), - SingleLang("DragonTea", "https://dragontea.ink", "en"), + SingleLang("DragonTea", "https://dragontea.ink", "en", overrideVersionCode = 2), SingleLang("DragonTranslation.net", "https://dragontranslation.net", "es", isNsfw = true, className = "DragonTranslationNet"), - SingleLang("Drake Scans", "https://drakescans.com", "en", overrideVersionCode = 3), + SingleLang("Drake Scans", "https://drakescans.com", "en", overrideVersionCode = 4), SingleLang("Dream Manga", "https://www.swarmmanga.com", "en", overrideVersionCode = 3), - SingleLang("EGY Manga", "https://egymanga.net", "ar", overrideVersionCode = 1), + SingleLang("Elite Manga", "https://www.elitemanga.org", "en", isNsfw = false), SingleLang("Emperor Scan", "https://emperorscan.com", "es", overrideVersionCode = 1), - SingleLang("Empire Webtoon", "https://webtoonempire.com", "ar", isNsfw = true, overrideVersionCode = 2), + SingleLang("Empire Webtoon", "https://webtoonsempireron.com", "ar", isNsfw = true, overrideVersionCode = 3), SingleLang("Eromiau", "https://www.eromiau.com", "es", isNsfw = true), SingleLang("Esomanga", "https://esomanga.com", "tr", overrideVersionCode = 1), - SingleLang("Estufa de Cristal", "https://scanestufadecristal.site", "pt-BR", className = "EstufaDeCristal"), - SingleLang("EvaScans", "https://evascans.com", "tr"), - SingleLang("FreeMangaTop", "https://freemangatop.com", "en", overrideVersionCode = 2), - SingleLang("FaeStorm", "https://faestormmanga.com", "tr"), + SingleLang("FactManga", "https://factmanga.com", "en", isNsfw = false), SingleLang("Fay Scans", "https://fayscans.com.br", "pt-BR", overrideVersionCode = 1), - SingleLang("FDM Scan", "https://fdmscan.com", "pt-BR", overrideVersionCode = 3), - SingleLang("Fiz Manga", "https://fizmanga.com", "en"), + SingleLang("Fire Scans", "https://firescans.xyz", "en", overrideVersionCode = 1), SingleLang("Fleur Blanche", "https://fbsquads.com", "pt-BR", isNsfw = true, overrideVersionCode = 2), SingleLang("Flex Tape Scans", "https://flextapescans.com", "en", isNsfw = true), + SingleLang("Flower Manga", "https://flowermanga.com", "pt-BR"), SingleLang("Fox White", "https://foxwhite.com.br", "pt-BR"), + SingleLang("FR-Scan", "https://fr-scan.com", "fr", pkgName = "frdashscan", className = "FRScan", overrideVersionCode = 4), SingleLang("Free Manga", "https://freemanga.me", "en", isNsfw = true, overrideVersionCode = 3), + SingleLang("Free Manhwa", "https://manhwas.com", "en", isNsfw = false), + SingleLang("FreeMangaTop", "https://freemangatop.com", "en", overrideVersionCode = 2), SingleLang("FreeWebtoonCoins", "https://freewebtooncoins.com", "en", overrideVersionCode = 1), - SingleLang("FR-Scan", "https://fr-scan.com", "fr", pkgName = "frdashscan", className = "FRScan", overrideVersionCode = 1), - SingleLang("Fug Manga", "https://fugmanga.net", "ar", overrideVersionCode = 1), - SingleLang("Fukushuu no Yuusha", "https://fny-scantrad.com", "fr", overrideVersionCode = 2), - SingleLang("Furio Scans", "https://furioscans.com", "pt-BR", overrideVersionCode = 4), SingleLang("GalaxyDegenScans", "https://gdscans.com", "en", overrideVersionCode = 4), SingleLang("Gatemanga", "https://gatemanga.com", "ar", overrideVersionCode = 1), - SingleLang("GeassToon", "https://geasstoon.com", "tr"), SingleLang("Gekkou Hentai", "https://hentai.gekkouscans.com.br", "pt-BR", isNsfw = true), SingleLang("Gekkou Scans", "https://gekkou.com.br", "pt-BR", isNsfw = true, pkgName = "gekkouscan"), + SingleLang("Ghost Scan", "https://ghostscan.com.br", "pt-BR", isNsfw = true), + SingleLang("Girls Love Manga!", "https://glmanga.com", "en", isNsfw = true, className = "GirlsLoveManga"), SingleLang("Glory Manga", "https://glorymanga.com", "tr"), + SingleLang("Good Girls Scan", "https://goodgirls.moe", "en", isNsfw = true), SingleLang("Goof Fansub", "https://gooffansub.com", "pt-BR", isNsfw = true), + SingleLang("Grabber Zone", "https://grabber.zone", "all"), SingleLang("GuncelManga", "https://guncelmanga.com", "tr", overrideVersionCode = 1), - SingleLang("Hreads", "https://hreads.net", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Hades no Fansub Hentai", "https://h.mangareaderpro.com", "es", isNsfw = true), SingleLang("Hades no Fansub", "https://mangareaderpro.com", "es", overrideVersionCode = 1), - SingleLang("Harimanga", "https://harimanga.com", "en", overrideVersionCode = 2), - SingleLang("Hatachi Manga", "https://hachiraw.com", "ja", isNsfw = true, overrideVersionCode = 2), + SingleLang("Harimanga", "https://harimanga.com", "en", overrideVersionCode = 3), SingleLang("Hattori Manga", "https://hattorimanga.com", "tr", isNsfw = true), SingleLang("Hayalistic", "https://hayalistic.com", "tr"), - SingleLang("Hela Scan", "https://helascan.com", "pt-BR", isNsfw = true), - SingleLang("Hentai CB", "https://cubeteam.xyz", "vi", isNsfw = true, overrideVersionCode = 5, pkgName = "hentaicube"), + SingleLang("Hentai CB", "https://hentaicube.net", "vi", isNsfw = true, overrideVersionCode = 6, pkgName = "hentaicube"), SingleLang("Hentai Manga", "https://hentaimanga.me", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Hentai Teca", "https://hentaiteca.net", "pt-BR", isNsfw = true, overrideVersionCode = 1), + SingleLang("Hentai-Scantrad", "https://hentai.scantrad-vf.cc", "fr", isNsfw = true, className = "HentaiScantrad", overrideVersionCode = 1), SingleLang("Hentai20", "https://hentai20.io", "en", isNsfw = true, overrideVersionCode = 3), + SingleLang("Hentai3z", "https://hentai3z.xyz", "en", isNsfw = true), + SingleLang("Hentai4Free", "https://hentai4free.net", "en", isNsfw = true), SingleLang("HentaiRead", "https://hentairead.com", "en", isNsfw = true, className = "Hentairead", overrideVersionCode = 3), - SingleLang("Hentai-Scantrad", "https://hentai.scantrad-vf.cc", "fr", isNsfw = true, className = "HentaiScantrad", overrideVersionCode = 1), SingleLang("HentaiWebtoon", "https://hentaiwebtoon.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("HentaiXComic", "https://hentaixcomic.com", "en", isNsfw = true), + SingleLang("HentaiXDickgirl", "https://hentaixdickgirl.com", "en", isNsfw = true), SingleLang("HentaiXYuri", "https://hentaixyuri.com", "en", isNsfw = true), - SingleLang("HentaiZone", "https://hentaizone.xyz", "fr", isNsfw = true), + SingleLang("HentaiZone", "https://hentaizone.xyz", "fr", isNsfw = true, overrideVersionCode = 1), SingleLang("HerenScan", "https://herenscan.com", "es"), SingleLang("HipercooL", "https://hipercool.xyz", "pt-BR", isNsfw = true, className = "Hipercool"), - SingleLang("Hiperdex", "https://1sthiperdex.com", "en", isNsfw = true, overrideVersionCode = 9), + SingleLang("Hiperdex", "https://hiperdex.com", "en", isNsfw = true, overrideVersionCode = 11), SingleLang("HistoireDHentai", "https://hhentai.fr", "fr", isNsfw = true), SingleLang("Hizomanga", "https://hizomanga.com", "ar", overrideVersionCode = 1), SingleLang("HM2D", "https://mangadistrict.com/hdoujin", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("hManhwa", "https://hmanhwa.com", "en", isNsfw = true, overrideVersionCode = 1), - SingleLang("Hscans", "https://hscans.com", "en", overrideVersionCode = 3), + SingleLang("HouseMangas", "https://housemangas.com", "es"), + SingleLang("Hreads", "https://hreads.net", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("I Love Manhwa", "https://ilovemanhwa.com", "en", isNsfw = true), - SingleLang("Ichirin No Hana Yuri", "https://ichirinnohanayuriscan.com", "pt-BR", isNsfw = true, overrideVersionCode = 4), SingleLang("Ikifeng", "https://ikifeng.com", "es", isNsfw = true), SingleLang("Illusion Scan", "https://illusionscan.com", "pt-BR", isNsfw = true), SingleLang("Immortal Updates", "https://immortalupdates.com", "en", overrideVersionCode = 6), - SingleLang("InariManga", "https://inarimanga.com", "es", overrideVersionCode = 1), - SingleLang("Inazu Manga", "https://inazumanga.com", "id", isNsfw = true), SingleLang("InfraFandub", "https://infrafandub.xyz", "es"), SingleLang("Inmortal Scan", "https://manga.mundodrama.site", "es"), SingleLang("InstaManhwa", "https://www.instamanhwa.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("IsekaiScan.com", "https://isekaiscan.com", "en", className = "IsekaiScanCom", overrideVersionCode = 4), - SingleLang("IsekaiScan.to (unoriginal)", "https://isekaiscan.to", "en", pkgName = "isekaiscaneu", className = "IsekaiScanTo", overrideVersionCode = 2), - SingleLang("IsekaiScan.top (unoriginal)", "https://isekaiscan.top", "en", pkgName = "isekaiscantop", className = "IsekaiScanTop"), + SingleLang("IsekaiScan.to (unoriginal)", "https://m.isekaiscan.to", "en", isNsfw = true, pkgName = "isekaiscaneu", className = "IsekaiScanTo", overrideVersionCode = 3), + SingleLang("IsekaiScan.top (unoriginal)", "https://isekaiscan.top", "en", pkgName = "isekaiscantop", className = "IsekaiScanTop", overrideVersionCode = 1), SingleLang("IsekaiScanManga (unoriginal)", "https://isekaiscanmanga.com", "en", className = "IsekaiScanManga", overrideVersionCode = 1), SingleLang("Its Your Right Manhua", "https://itsyourightmanhua.com", "en", className = "ItsYourRightManhua", overrideVersionCode = 2), - SingleLang("Izakaya", "https://leitorizakaya.net", "pt-BR", isNsfw = true, overrideVersionCode = 1), - SingleLang("Jiangzaitoon", "https://jiangzaitoon.co", "tr", isNsfw = true, overrideVersionCode = 2), - SingleLang("Kalango Scan", "https://kalangoscan.online", "pt-BR"), + SingleLang("Jiangzaitoon", "https://jiangzaitoon.cc", "tr", isNsfw = true, overrideVersionCode = 3), + SingleLang("Jimanga", "https://jimanga.com", "en", isNsfw = false), + SingleLang("Kakusei Project", "https://kakuseiproject.com.br", "pt-BR"), SingleLang("Kami Sama Explorer", "https://leitor.kamisama.com.br", "pt-BR", overrideVersionCode = 2), SingleLang("Karatcam Scans", "https://karatcam-scans.fr", "fr", isNsfw = true), + SingleLang("Kataitake", "https://www.kataitake.fr", "fr", isNsfw = true), SingleLang("KawaScans", "https://kawascans.com", "en", overrideVersionCode = 1), - SingleLang("Kiara", "https://kiara.cool", "en"), + SingleLang("Kings-Manga", "https://www.kings-manga.co", "th", isNsfw = false, className = "KingsManga"), SingleLang("Kissmanga.in", "https://kissmanga.in", "en", className = "KissmangaIn", overrideVersionCode = 3), SingleLang("KlikManga", "https://klikmanga.id", "id", overrideVersionCode = 2), - SingleLang("KomikRame", "https://komikra.me", "id"), + SingleLang("Koinobori Scan", "https://koinoboriscan.com", "es", isNsfw = true, className = "KoinoboriScan"), SingleLang("Komik Chan", "https://komikchan.com", "en", className = "KomikChan", overrideVersionCode = 1), + SingleLang("Komik Gue", "https://komikgue.pro", "id", isNsfw = true), + SingleLang("KomikRame", "https://komikra.me", "id"), SingleLang("KSGroupScans", "https://ksgroupscans.com", "en"), SingleLang("Kun Manga", "https://kunmanga.com", "en", overrideVersionCode = 1), + SingleLang("Lady Estelar Scan", "https://ladyestelarscan.com.br", "pt-BR"), SingleLang("Lady Manga", "https://ladymanga.com", "en"), SingleLang("Lala Manga", "https://lalamanga.com", "en", isNsfw = true), SingleLang("Lara Manga", "https://laramanga.love", "en", overrideVersionCode = 1), - SingleLang("Legion Scan", "https://legionscans.com", "es"), + SingleLang("Last Knight Translation", "https://lkscanlation.com", "es", isNsfw = true, className = "LKScanlation"), SingleLang("Ler Yaoi", "https://leryaoi.com", "pt-BR", isNsfw = true), + SingleLang("Leviatan Scans", "https://lscomic.com", "en", overrideVersionCode = 15), SingleLang("LHTranslation", "https://lhtranslation.net", "en", overrideVersionCode = 1), SingleLang("Lily Manga", "https://lilymanga.net", "en", isNsfw = true, overrideVersionCode = 3), - SingleLang("Lima Scans", "http://limascans.xyz/v2", "pt-BR", isNsfw = true, overrideVersionCode = 2), + SingleLang("Limbo Scan", "https://limboscan.com.br", "pt-BR", isNsfw = true), + SingleLang("Link Start Scan", "https://www.linkstartscan.xyz", "pt-BR", isNsfw = true), SingleLang("Lolicon", "https://lolicon.mobi", "en", isNsfw = true, overrideVersionCode = 2), SingleLang("Lord Manga", "https://lordmanga.com", "en"), + SingleLang("Luffy Manga", "https://luffymanga.com", "en", isNsfw = false), SingleLang("LuxManga", "https://luxmanga.com", "en"), SingleLang("MadaraDex", "https://madaradex.org", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("Maid Scan", "https://maidscan.com.br", "pt-BR"), + SingleLang("Manga 18h", "https://manga18h.com", "en", isNsfw = true), + SingleLang("Manga 18x", "https://manga18x.net", "en", isNsfw = true), SingleLang("Manga Action", "https://mangaaction.com", "en", overrideVersionCode = 2), - SingleLang("Manga Bilgini", "https://mangabilgini.com", "tr"), + SingleLang("Manga Bee", "https://mangabee.net", "en", isNsfw = true), SingleLang("Manga Bin", "https://mangabin.com", "en", overrideVersionCode = 1), SingleLang("Manga Chill", "https://toonchill.com", "en", overrideVersionCode = 7), - SingleLang("Manga Crab", "https://mangacrab.com", "es", overrideVersionCode = 4), - SingleLang("Manga District", "https://mangadistrict.com", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("Manga Crab", "https://mangacrab3.com", "es", overrideVersionCode = 7), + SingleLang("Manga District", "https://mangadistrict.com", "en", isNsfw = true, overrideVersionCode = 2), SingleLang("Manga Diyari", "https://manga-diyari.com", "tr", overrideVersionCode = 2), - SingleLang("Manga Fenix", "https://manga-fenix.com", "es", overrideVersionCode = 2), + SingleLang("Manga Fenix", "https://manhua-fenix.com", "es", overrideVersionCode = 3), SingleLang("Manga Galaxy", "https://mangagalaxy.me", "en", overrideVersionCode = 1), - SingleLang("Manga Hentai", "https://mangahentai.me", "en", isNsfw = true, overrideVersionCode = 2), + SingleLang("Manga Hentai", "https://mangahentai.me", "en", isNsfw = true, overrideVersionCode = 3), SingleLang("Manga Keyfi", "https://mangakeyfi.net", "tr"), - SingleLang("Manga Kio", "https://mangakio.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Manga Kiss", "https://mangakiss.org", "en", overrideVersionCode = 1), + SingleLang("Manga Kitsu", "https://mangakitsu.com", "en", isNsfw = false), SingleLang("Manga Leveling", "https://mangaleveling.com", "en", overrideVersionCode = 1), SingleLang("Manga Lord", "https://mangalord.com", "en", overrideVersionCode = 1), + SingleLang("Manga Mammy", "https://mangamammy.ru", "ru", isNsfw = true), SingleLang("Manga Mitsu", "https://mangamitsu.com", "en", isNsfw = true, overrideVersionCode = 2), + SingleLang("Mangá Nanquim", "https://mangananquim.com", "pt-BR", className = "MangaNanquim"), + SingleLang("Manga Nerds", "https://manganerds.com", "en", isNsfw = false), SingleLang("Manga One Love", "https://mangaonelove.site/", "ru", isNsfw = true), SingleLang("Manga Online Team", "https://mangaonlineteam.com", "en"), + SingleLang("Manga Queen.com", "https://mangaqueen.com", "en", isNsfw = true, className = "MangaQueenCom"), + SingleLang("Manga Queen.online (unoriginal)", "https://mangaqueen.online", "en", isNsfw = true, className = "MangaQueenOnline"), SingleLang("Manga Queen", "https://mangaqueen.net", "en"), SingleLang("Manga Read", "https://mangaread.co", "en", overrideVersionCode = 1), SingleLang("Manga Rock Team", "https://mangarockteam.com", "en", overrideVersionCode = 1), + SingleLang("Manga Rock.team (unoriginal)", "https://mangarock.team", "en", isNsfw = false, className = "MangaRockTeamUnoriginal"), SingleLang("Manga Rocky", "https://mangarocky.com", "en", overrideVersionCode = 1), - SingleLang("Manga Starz", "https://mangastarz.com", "ar", overrideVersionCode = 3), + SingleLang("Manga Rose", "https://mangarose.net", "ar"), + SingleLang("Manga Şehri", "https://manga-sehri.com", "tr", className = "MangaSehri", isNsfw = true, overrideVersionCode = 1), + SingleLang("Manga Starz", "https://mangastarz.org", "ar", overrideVersionCode = 5), SingleLang("Manga Too", "https://mangatoo.com", "en", overrideVersionCode = 1), - SingleLang("Manga Weebs", "https://mangaweebs.in", "en", overrideVersionCode = 7), - SingleLang("Manga Şehri", "https://mangasehri.com", "tr", className = "MangaSehri", isNsfw = true), + SingleLang("Manga Tx.gg (unoriginal)", "https://mangatx.gg", "en", isNsfw = false, className = "MangaTxGg"), + SingleLang("Manga Weebs", "https://mangaweebs.in", "en", overrideVersionCode = 8), + SingleLang("Manga-1001.com", "https://manga-1001.com", "en", isNsfw = false, className = "MangaDash1001Com"), SingleLang("Manga-fast.com", "https://manga-fast.com", "en", className = "Mangafastcom", overrideVersionCode = 3), - SingleLang("Manga-Online.co", "https://www.manga-online.co", "th", className = "MangaOnlineCo"), + SingleLang("Manga-Raw.info (unoriginal)", "https://manga-raw.info", "en", isNsfw = true, className = "MangaRawInfo"), SingleLang("Manga-Scantrad", "https://manga-scantrad.io", "fr", className = "MangaScantrad", overrideVersionCode = 3), SingleLang("Manga-TX", "https://manga-tx.com", "en", className = "Mangatxunoriginal"), SingleLang("Manga18fx", "https://manga18fx.com", "en", isNsfw = true, overrideVersionCode = 5), SingleLang("Manga1st.online", "https://manga1st.online", "en", className = "MangaFirstOnline", overrideVersionCode = 1), SingleLang("Manga347", "https://manga347.com", "en", overrideVersionCode = 3), SingleLang("Manga3S", "https://manga3s.com", "en", overrideVersionCode = 4), - SingleLang("Manga4All", "https://manga4all.net", "en", overrideVersionCode = 3), SingleLang("Manga68", "https://manga68.com", "en", overrideVersionCode = 1), SingleLang("MangaBaz", "https://mangabaz.net", "en"), SingleLang("MangaBob", "https://mangabob.com", "en", overrideVersionCode = 1), - SingleLang("MangaVisa", "https://mangavisa.com", "en", pkgName = "mangaboss", className = "MangaVisa", overrideVersionCode = 1), SingleLang("MangaCC", "https://mangacc.com", "en"), + SingleLang("MangaClash.tv (unoriginal)", "https://mangaclash.tv", "en", isNsfw = true, className = "MangaClashTv"), SingleLang("MangaClash", "https://mangaclash.com", "en", overrideVersionCode = 3), + SingleLang("MangaCrazy", "https://mangacrazy.net", "all", isNsfw = true), SingleLang("MangaCultivator", "https://mangacultivator.com", "en", overrideVersionCode = 2), SingleLang("MangaCV", "https://mangacv.com", "en", isNsfw = true), SingleLang("MangaDeemak", "https://mangadeemak.com", "th", overrideVersionCode = 2), - SingleLang("MangaDods", "https://www.mangadods.com", "en", overrideVersionCode = 2), + SingleLang("MangaDino.top (unoriginal)", "https://mangadino.top", "en", isNsfw = true, className = "MangaDinoTop"), + SingleLang("MangaDods", "https://mangadods.com", "en", overrideVersionCode = 3), SingleLang("MangaDol", "https://mangadol.com", "en"), SingleLang("MangaEffect", "https://mangaeffect.com", "en", overrideVersionCode = 1), SingleLang("Mangaforfree.com", "https://mangaforfree.com", "en", isNsfw = true, className = "Mangaforfreecom"), SingleLang("MangaFoxFull", "https://mangafoxfull.com", "en"), SingleLang("MangaFreak.online", "https://mangafreak.online", "en", className = "MangaFreakOnline"), SingleLang("MangaGG", "https://mangagg.com", "en", overrideVersionCode = 2), - SingleLang("MangaGreat", "https://mangagreat.com", "en", overrideVersionCode = 3), - SingleLang("MangaHub.fr", "https://mangahub.fr", "fr", isNsfw = true, className = "MangaHubFr", pkgName = "mangahubfr"), + SingleLang("MangaGo Yaoi", "https://mangagoyaoi.com", "en", isNsfw = true), + SingleLang("MangaHub.fr", "https://mangahub.fr", "fr", isNsfw = true, className = "MangaHubFr", pkgName = "mangahubfr", overrideVersionCode = 2), SingleLang("MangaHZ", "https://www.mangahz.com", "en", isNsfw = true, overrideVersionCode = 2), SingleLang("MangaK2", "https://mangak2.com", "en", isNsfw = true), + SingleLang("Mangakakalot.io (unoriginal)", "https://mangakakalot.io", "en", isNsfw = true, className = "MangakakalotIo"), + SingleLang("Mangakakalot.one (unoriginal)", "https://mangakakalot.one", "en", isNsfw = true, className = "MangakakalotOne"), SingleLang("Mangakik", "https://mangakik.net", "en", overrideVersionCode = 1), SingleLang("MangaKing", "https://mangaking.net", "en"), SingleLang("MangaKitsune", "https://mangakitsune.com", "en", isNsfw = true, overrideVersionCode = 4), SingleLang("MangaKL", "https://mangakala.com", "ja"), SingleLang("MangaKomi", "https://mangakomi.io", "en", overrideVersionCode = 5), - SingleLang("MangaLionz", "https://mangalionz.com", "ar", overrideVersionCode = 1), - SingleLang("MangaManhua", "https://mangamanhua.online", "en", overrideVersionCode = 1), + SingleLang("Mangaland", "https://mangaland.net", "es", isNsfw = true), + SingleLang("MangaLionz", "https://mangalionz.org", "ar", overrideVersionCode = 2), SingleLang("MangaManiacs", "https://mangamaniacs.org", "en", isNsfw = true), - SingleLang("MangaMe", "https://mangame.org", "en", overrideVersionCode = 1), + SingleLang("Manganelo.biz", "https://manganelo.biz", "en", isNsfw = true, className = "ManganeloBiz"), + SingleLang("Manganelo.website (unoriginal)", "https://manganelo.website", "en", isNsfw = true, className = "ManganeloWebsite"), + SingleLang("MangaOnline.team (unoriginal)", "https://mangaonline.team", "en", isNsfw = false, className = "MangaOnlineTeamUnoriginal"), + SingleLang("MangaOwl.blog (unoriginal)", "https://mangaowl.blog", "en", isNsfw = true, className = "MangaOwlBlog"), + SingleLang("MangaOwl.io (unoriginal)", "https://mangaowl.io", "en", isNsfw = true, className = "MangaOwlIo"), + SingleLang("MangaOwl.one (unoriginal)", "https://mangaowl.one", "en", isNsfw = true, className = "MangaOwlOne"), + SingleLang("MangaOwl.us (unoriginal)", "https://mangaowl.us", "en", isNsfw = true, className = "MangaOwlUs"), SingleLang("MangaPT", "https://mangapt.com", "es", isNsfw = true), + SingleLang("MangaPure", "https://mangapure.net", "en", isNsfw = true), SingleLang("MangaRabic", "https://mangaarabics.com", "ar", overrideVersionCode = 1), - SingleLang("MangaRead.org", "https://www.mangaread.org", "en", className = "MangaReadOrg", overrideVersionCode = 1), - SingleLang("MangaRolls", "https://mangarolls.com", "en"), - SingleLang("Mangas Origines X", "https://x.mangas-origines.fr", "fr", isNsfw = true), + SingleLang("MangaRead.org", "https://www.mangaread.org", "en", className = "MangaReadOrg", overrideVersionCode = 2), + SingleLang("MangaRolls", "https://mangarolls.net", "en", overrideVersionCode = 1), + SingleLang("MangaRosie", "https://mangarosie.in", "en", isNsfw = true), + SingleLang("MangaRuby.com", "https://mangaruby.com", "en", isNsfw = true, className = "MangaRubyCom"), + SingleLang("Mangaryu", "https://mangaryu.com", "en", isNsfw = true), + SingleLang("Mangas No Sekai", "https://mangasnosekai.com", "es", overrideVersionCode = 1), SingleLang("Mangas Origines", "https://mangas-origines.xyz", "fr", isNsfw = true, overrideVersionCode = 4), + SingleLang("Mangas-Origines.fr", "https://mangas-origines.fr", "fr", className = "MangasOriginesFr"), SingleLang("MangaSco", "https://manhwasco.net", "en", overrideVersionCode = 2), SingleLang("MangaSiro", "https://mangasiro.com", "en", isNsfw = true), - SingleLang("MangaSpark", "https://mangaspark.com", "ar", overrideVersionCode = 2), - SingleLang("MangaStic", "https://mangastic9.com", "en", overrideVersionCode = 2), - SingleLang("MangasTK18", "https://mangastk18.com", "es", isNsfw = true), + SingleLang("MangaSpark", "https://mangaspark.org", "ar", overrideVersionCode = 4), + SingleLang("MangaStic", "https://mangastic9.com", "en", isNsfw = true, overrideVersionCode = 3), SingleLang("Mangasushi", "https://mangasushi.org", "en", overrideVersionCode = 3), SingleLang("MangaTone", "https://mangatone.com", "en"), + SingleLang("MangaTop.site", "https://mangatop.site", "all", isNsfw = true, className = "MangaTopSite"), SingleLang("MangaToRead", "https://mangatoread.com", "en"), SingleLang("MangaTX", "https://mangatx.com", "en", overrideVersionCode = 1), - SingleLang("Mangauptocats", "https://manga-uptocats.com", "th", overrideVersionCode = 4), + SingleLang("MangaTyrant", "https://mangatyrant.com", "en", isNsfw = false), + SingleLang("MangaUpdates.top (unoriginal)", "https://mangaupdates.top", "en", isNsfw = true, className = "MangaUpdatesTop"), SingleLang("MangaUS", "https://mangaus.xyz", "en", overrideVersionCode = 2), + SingleLang("MangaVisa", "https://mangavisa.com", "en", pkgName = "mangaboss", className = "MangaVisa", overrideVersionCode = 1), SingleLang("MangaX1", "https://mangax1.com", "en"), + SingleLang("Mangaxico", "https://mangaxico.com", "es", isNsfw = true), SingleLang("MangaXP", "https://mangaxp.com", "en", overrideVersionCode = 1), SingleLang("MangaYami", "https://www.mangayami.club", "en", overrideVersionCode = 2), + SingleLang("Manhastro", "https://manhastro.com", "pt-BR"), SingleLang("Manhatic", "https://manhatic.com", "ar", isNsfw = true), SingleLang("Manhua ES", "https://manhuaes.com", "en", overrideVersionCode = 6), SingleLang("Manhua Kiss", "https://manhuakiss.com", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("Manhua Mix", "https://manhuamix.com", "en", className = "Manhuasnet", overrideVersionCode = 3), SingleLang("Manhua Plus", "https://manhuaplus.com", "en", overrideVersionCode = 6), SingleLang("Manhua SY", "https://www.manhuasy.com", "en", overrideVersionCode = 2), + SingleLang("Manhua Zonghe", "https://manhuazonghe.com", "en", isNsfw = true), SingleLang("ManhuaBox", "https://manhuabox.net", "en", overrideVersionCode = 2), SingleLang("ManhuaChill", "https://manhuachill.com", "en"), - SingleLang("ManhuaFast", "https://manhuafast.com", "en", overrideVersionCode = 2), + SingleLang("ManhuaDex", "https://manhuadex.com", "en", isNsfw = false), + SingleLang("ManhuaFast.net (unoriginal)", "https://manhuafast.net", "en", isNsfw = false, className = "ManhuaFastNet"), + SingleLang("ManhuaFast", "https://manhuafast.com", "en", overrideVersionCode = 3), SingleLang("Manhuaga", "https://manhuaga.com", "en", overrideVersionCode = 2), SingleLang("ManhuaHot", "https://manhuahot.com", "en"), - SingleLang("Manhuas.net", "https://manhuas.net", "en", className = "Manhuasnet", overrideVersionCode = 2), + SingleLang("ManhuaManhwa.online", "https://manhuamanhwa.online", "en", isNsfw = false, className = "ManhuaManhwaOnline"), + SingleLang("ManhuaManhwa", "https://manhuamanhwa.com", "en", isNsfw = true), + SingleLang("ManhuaScan.info (unoriginal)", "https://manhuascan.info", "en", isNsfw = true, className = "ManhuaScanInfo"), SingleLang("ManhuaUS", "https://manhuaus.com", "en", overrideVersionCode = 5), - SingleLang("ManhuaZone", "https://manhuazone.com", "en"), + SingleLang("ManhuaZone", "https://manhuazone.org", "en", overrideVersionCode = 1), SingleLang("Manhwa Raw", "https://manhwaraw.com", "ko", isNsfw = true, overrideVersionCode = 1), - SingleLang("Manhwa-Latino", "https://manhwa-latino.com", "es", isNsfw = true, className = "ManhwaLatino", overrideVersionCode = 3), - SingleLang("Manhwa-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw"), + SingleLang("Manhwa-Latino", "https://manhwa-latino.com", "es", isNsfw = true, className = "ManhwaLatino", overrideVersionCode = 7), + SingleLang("Manhwa-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw", overrideVersionCode = 1), SingleLang("Manhwa18.app", "https://manhwa18.app", "en", isNsfw = true, className = "Manhwa18app"), SingleLang("Manhwa18.org", "https://manhwa18.org", "en", isNsfw = true, className = "Manhwa18Org", overrideVersionCode = 2), + SingleLang("Manhwa2Read", "https://manhwa2read.com", "en", isNsfw = false), SingleLang("Manhwa365", "https://manhwa365.com", "en", isNsfw = true), SingleLang("Manhwa68", "https://manhwa68.com", "en", isNsfw = true, overrideVersionCode = 3), SingleLang("ManhwaBookShelf", "https://manhwabookshelf.com", "en"), SingleLang("ManhwaClan", "https://manhwaclan.com", "en"), SingleLang("Manhwafull", "https://manhwafull.com", "en", overrideVersionCode = 1), SingleLang("Manhwahentai.me", "https://manhwahentai.me", "en", className = "ManhwahentaiMe", isNsfw = true, overrideVersionCode = 3), + SingleLang("ManhwaManhua", "https://manhwamanhua.com", "en", isNsfw = true), + SingleLang("ManhwaNew", "https://manhwanew.com", "en", isNsfw = true), SingleLang("Manhwas Men", "https://manhwas.men", "en", className = "ManhwasMen", isNsfw = true), - SingleLang("ManhwaTime", "https://manhwatime.com", "ar"), SingleLang("Manhwatop", "https://manhwatop.com", "en", overrideVersionCode = 2), - SingleLang("ManhwaWorld", "https://manhwaworld.com", "en"), SingleLang("Manhwua.fans", "https://manhwua.fans", "en", isNsfw = true, className = "Manhwuafans"), + SingleLang("Mantraz Scan", "https://mantrazscan.com", "es"), + SingleLang("ManWe", "https://manwe.pro", "tr", className = "EvaScans", overrideVersionCode = 1), SingleLang("ManyComic", "https://manycomic.com", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("ManyToon.me", "https://manytoon.me", "en", isNsfw = true, className = "ManyToonMe", overrideVersionCode = 5), SingleLang("ManyToon", "https://manytoon.com", "en", isNsfw = true, overrideVersionCode = 5), - SingleLang("ManyToon.me", "https://manytoon.me", "en", isNsfw = true, className = "ManyToonMe", overrideVersionCode = 4), - SingleLang("ManyToonClub", "https://manytoon.club", "ko", isNsfw = true, overrideVersionCode = 1), - SingleLang("MG Komik", "https://mgkomik.com", "id", overrideVersionCode = 4), - SingleLang("MHentais", "https://mhentais.com", "pt-BR", isNsfw = true, overrideVersionCode = 1), + SingleLang("ManyToonClub", "https://manytoon.club", "ko", isNsfw = true, overrideVersionCode = 2), + SingleLang("MG Komik", "https://mgkomik.id", "id", overrideVersionCode = 11), SingleLang("Midnight Mess Scans", "https://midnightmess.org", "en", isNsfw = true, overrideVersionCode = 6), SingleLang("MidnightManga", "http://midnightmanga.com", "es"), SingleLang("Milftoon", "https://milftoon.xxx", "en", isNsfw = true, overrideVersionCode = 2), - SingleLang("Mirad Scanlator", "https://miradscanlator.site", "pt-BR", overrideVersionCode = 1), - SingleLang("Mixed Manga", "https://mixedmanga.com", "en", overrideVersionCode = 1), - SingleLang("MMScans", "https://mm-scans.org", "en", overrideVersionCode = 5), + SingleLang("MMScans", "https://mm-scans.org", "en", overrideVersionCode = 7), SingleLang("MonarcaManga", "https://monarcamanga.com", "es"), - SingleLang("MoonLovers Scan", "https://moonloversscan.com.br", "pt-BR", isNsfw = true), SingleLang("Moon Witch In Love", "https://moonwitchinlovescan.com", "pt-BR"), + SingleLang("MoonLovers Scan", "https://moonloversscan.com.br", "pt-BR", isNsfw = true), SingleLang("Mortals Groove", "https://mortalsgroove.com", "en", overrideVersionCode = 1), SingleLang("MR Yaoi Fansub", "https://mrbenne.com", "pt-BR", isNsfw = true, className = "MrYaoiFansub", overrideVersionCode = 1), SingleLang("Muctau", "https://bibimanga.com", "en", isNsfw = true, overrideVersionCode = 4), SingleLang("MurimScan", "https://murimscan.run", "en", isNsfw = true), SingleLang("My Manhwa", "https://mymanhwa.net", "en"), - SingleLang("My Universe Scanlator", "https://muscan.com.br", "pt-BR"), SingleLang("Mystical Merries", "https://mysticalmerries.com", "en", overrideVersionCode = 2), SingleLang("NeatManga", "https://neatmanga.com", "en", overrideVersionCode = 2), + SingleLang("NekoPost.co (unoriginal)", "https://www.nekopost.co", "th", isNsfw = false, className = "NekoPostCo"), SingleLang("NekoScan", "https://nekoscan.com", "en", overrideVersionCode = 2), SingleLang("Night Comic", "https://www.nightcomic.com", "en", overrideVersionCode = 1), SingleLang("Niji Translations", "https://niji-translations.com", "ar", overrideVersionCode = 1), - SingleLang("Ninja Scan", "https://ninjascan.xyz", "pt-BR", overrideVersionCode = 1), - SingleLang("Nitro Scans", "https://nitroscans.com", "en"), + SingleLang("Nitro Manga", "https://nitromanga.com", "en", className = "NitroScans", overrideVersionCode = 1), SingleLang("No Index Scan", "https://noindexscan.com", "pt-BR", isNsfw = true), + SingleLang("Noblesse Translations", "https://noblessetranslations.com", "es", overrideVersionCode = 1), SingleLang("Nocturne Summer", "https://nocsummer.com.br", "pt-BR", isNsfw = true), - SingleLang("Noblesse Translations", "https://www.noblessetranslations.com", "es"), SingleLang("NovelCrow", "https://novelcrow.com", "en", isNsfw = true), SingleLang("NovelMic", "https://novelmic.com", "en", overrideVersionCode = 1), SingleLang("Novels Town", "https://novelstown.cyou", "ar"), SingleLang("Oh No Manga", "https://ohnomanga.com", "en", isNsfw = true), - SingleLang("Painful Nightz Scan", "https://painfulnightz.com", "en", overrideVersionCode = 1), + SingleLang("OnlyManhwa", "https://onlymanhwa.org", "en", isNsfw = true), SingleLang("Pantheon Scan", "https://pantheon-scan.com", "fr", overrideVersionCode = 1), - SingleLang("Peach Scan", "https://www.peachscan.com", "pt-BR", isNsfw = true), + SingleLang("Paragon Scans", "https://paragonscans.com", "en", isNsfw = true), + SingleLang("Passa Mão Scan", "https://passamaoscan.com", "pt-BR", isNsfw = true, className = "PassaMaoScan"), + SingleLang("Paw Manga", "https://pawmanga.com", "en", isNsfw = true), SingleLang("Petrotechsociety", "https://www.petrotechsociety.org", "en", isNsfw = true), - SingleLang("Phoenix Fansub", "https://phoenixmangas.com", "es"), SingleLang("Pian Manga", "https://pianmanga.me", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("Pikiran Wibu", "https://pikiran-wibu.com", "id"), SingleLang("Pink Sea Unicorn", "https://psunicorn.com", "pt-BR", isNsfw = true), SingleLang("Pirulito Rosa", "https://pirulitorosa.site", "pt-BR", isNsfw = true), SingleLang("Platinum Crown", "https://platinumscans.com", "en", overrideVersionCode = 1), - SingleLang("Pojok Manga", "https://pojokmanga.net", "id", overrideVersionCode = 4), + SingleLang("PMScans", "https://rackusreads.com", "en"), + SingleLang("Pojok Manga", "https://pojokmanga.net", "id", overrideVersionCode = 5), SingleLang("PoManga", "https://pomanga.com", "en"), - SingleLang("Pornhwa18", "https://pornhwa18.com", "id", isNsfw = true), + SingleLang("Pony Manga", "https://ponymanga.com", "en", isNsfw = true), SingleLang("PornComix", "https://www.porncomixonline.net", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("Pornhwa18", "https://pornhwa18.com", "id", isNsfw = true), SingleLang("Pornwha", "https://pornwha.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Portal Yaoi", "https://portalyaoi.com", "pt-BR", isNsfw = true), SingleLang("Prisma Hentais", "https://prismahentai.com", "pt-BR", isNsfw = true), - SingleLang("Prisma Scans", "https://prismascans.net", "pt-BR", overrideVersionCode = 2), SingleLang("Projeto Scanlator", "https://projetoscanlator.com", "pt-BR", overrideVersionCode = 3), - SingleLang("ROG Mangás", "https://rogmangas.com", "pt-BR", pkgName = "mangasoverall", className = "RogMangas", overrideVersionCode = 1), SingleLang("Ragnarok Scanlation", "https://ragnarokscanlation.com", "es", className = "RagnarokScanlation"), SingleLang("RagnarokScan", "https://ragnarokscan.com", "es", overrideVersionCode = 1), SingleLang("Raijin Scans", "https://raijinscans.fr", "fr"), SingleLang("Rainbow Fairy Scan", "https://rainbowfairyscan.com", "pt-BR"), - SingleLang("Random Scan", "https://randomscans.com", "pt-BR", overrideVersionCode = 5), + SingleLang("Random Scan", "https://randomscanlators.net", "pt-BR", overrideVersionCode = 6), SingleLang("RawDEX", "https://rawdex.net", "ko", isNsfw = true, overrideVersionCode = 1), SingleLang("ReadAdult", "https://readadult.net", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("ReaderGen", "https://fr.readergen.fr", "fr"), SingleLang("Readers Point", "https://readers-point.space", "en"), SingleLang("ReadFreeComics", "https://readfreecomics.com", "en"), SingleLang("ReadMangaFree", "https://readmangafree.net", "en", isNsfw = true), SingleLang("ReadManhua", "https://readmanhua.net", "en", overrideVersionCode = 2), - SingleLang("Rh2PlusManga", "https://www.rh2plusmanga.com", "th", overrideVersionCode = 4), + SingleLang("Rh2PlusManga", "https://www.rh2plusmanga.com", "th", isNsfw = true, overrideVersionCode = 5), + SingleLang("RichtoScan", "https://richtoscan.com", "es"), + SingleLang("Rightdark Scan", "https://rightdark-scan.com", "es"), SingleLang("Rio2 Manga", "https://rio2manga.com", "en"), + SingleLang("ROG Mangás", "https://rogmangas.com", "pt-BR", pkgName = "mangasoverall", className = "RogMangas", overrideVersionCode = 1), SingleLang("Romantik Manga", "https://romantikmanga.com", "tr"), - SingleLang("RWBY Scan", "https://rwbyscan.site", "pt-BR", isNsfw = true, className = "RwbyScan"), SingleLang("Rüya Manga", "https://www.ruyamanga.com", "tr", className = "RuyaManga", overrideVersionCode = 1), - SingleLang("S2Manga", "https://s2manga.com", "en", overrideVersionCode = 1), + SingleLang("S2Manga", "https://www.s2manga.com", "en", overrideVersionCode = 2), SingleLang("Sagrado Império da Britannia", "https://imperiodabritannia.com", "pt-BR", className = "ImperioDaBritannia"), - SingleLang("SamuraiScan", "https://samuraiscan.com", "es", overrideVersionCode = 1), + SingleLang("SamuraiScan", "https://samuraiscan.com", "es", overrideVersionCode = 3), SingleLang("Sawamics", "https://sawamics.com", "en"), SingleLang("ScamberTraslator", "https://scambertraslator.com", "es", overrideVersionCode = 3), SingleLang("Scan Hentai Menu", "https://scan.hentai.menu", "fr", isNsfw = true, overrideVersionCode = 1), - SingleLang("Scans Raw", "https://scansraw.com", "en", overrideVersionCode = 1), SingleLang("Scantrad-VF", "https://scantrad-vf.co", "fr", className = "ScantradVF"), SingleLang("Sdl scans", "https://sdlscans.com", "es", className = "SdlScans"), - SingleLang("Sensaina Yuri", "https://sensainayuri.dropescan.com", "pt-BR", isNsfw = true, overrideVersionCode = 2), + SingleLang("Shadowtrad", "https://shadowtrad.net", "fr"), SingleLang("ShavelProiection", "https://www.shavelproiection.com", "it", true), SingleLang("Shayami", "https://shayami.com", "es"), + SingleLang("Shiba Manga", "https://shibamanga.com", "en"), SingleLang("Shield Manga", "https://shieldmanga.io", "en", overrideVersionCode = 3), - SingleLang("Shimada Scans", "https://shimadascans.com", "en"), - SingleLang("Shinigami", "https://shinigami.id", "id", overrideVersionCode = 1), - SingleLang("Shirai Scans", "https://shiraiscans.com.br", "pt-BR"), + SingleLang("Shinigami", "https://shinigami.sh", "id", overrideVersionCode = 7), SingleLang("Shooting Star Scans", "https://shootingstarscans.com", "en"), SingleLang("ShoujoHearts", "https://shoujohearts.com", "en", overrideVersionCode = 2), - SingleLang("Sinensis Scan", "https://sinensisscans.com", "pt-BR", pkgName = "sinensis", overrideVersionCode = 5), + SingleLang("Sinensis Scan", "https://sinensisscan.net", "pt-BR", pkgName = "sinensis", overrideVersionCode = 6), SingleLang("SISI GELAP", "https://sigel.asia", "id", overrideVersionCode = 4), SingleLang("Siyahmelek", "https://siyahmelek.net", "tr", isNsfw = true, overrideVersionCode = 3), SingleLang("SkyManga.xyz", "https://skymanga.xyz", "en", isNsfw = true, className = "SkyMangaXyz"), - SingleLang("Sleeping Knight Scans", "https://skscans.com", "en", overrideVersionCode = 2), SingleLang("Sleepy Translations", "https://sleepytranslations.com", "en", overrideVersionCode = 1), - SingleLang("SodaScan", "https://sodascan.xyz", "pt-BR", isNsfw = true, overrideVersionCode = 1), SingleLang("Solo Leveling", "https://readsololeveling.online", "en"), SingleLang("Sugar Babies", "https://sugarbbscan.com", "en", overrideVersionCode = 2), SingleLang("Summanga", "https://summanga.com", "en", isNsfw = true), SingleLang("Sunshine Butterfly Scans", "https://sunshinebutterflyscan.com", "en", isNsfw = true, overrideVersionCode = 1), - SingleLang("Sweet Desire Scan", "https://sweetdesire.com.br", "pt-BR", isNsfw = true), SingleLang("Sweet Time Scan", "https://sweetscan.net", "pt-BR", overrideVersionCode = 2), + SingleLang("Taberu Mangás", "https://taberu.org", "pt-BR", className = "TaberuMangas", isNsfw = true), SingleLang("Tankou Hentai", "https://tankouhentai.com", "pt-BR", isNsfw = true), SingleLang("TappyToon.Net", "https://tappytoon.net", "en", className = "Tappytoonnet"), SingleLang("Tatakae Scan", "https://tatakaescan.com", "pt-BR", isNsfw = true, overrideVersionCode = 2), - SingleLang("Taurus Fansub", "https://taurusfansub.com", "es"), - SingleLang("Tecno Scan", "https://tecnoscann.com", "es"), - SingleLang("TeenManhua", "https://teenmanhua.com", "en"), - SingleLang("Temple Scan", "https://templescan.net", "en"), + SingleLang("Taurus Fansub", "https://taurusmanga.com", "es", overrideVersionCode = 1), + SingleLang("TeenManhua", "https://teenmanhua.com", "en", overrideVersionCode = 1), SingleLang("The Beginning After The End", "https://www.thebeginningaftertheend.fr", "fr", overrideVersionCode = 1), + SingleLang("The Blank Scanlation", "https://theblank.net", "en", className = "TheBlank", isNsfw = true), SingleLang("The Guild", "https://theguildscans.com", "en"), - SingleLang("The Sugar", "https://thesugarscan.com", "pt-BR"), - SingleLang("Three Queens Scanlator", "https://tqscan.com.br", "pt-BR", overrideVersionCode = 3), SingleLang("Time Naight", "https://timenaight.com", "tr"), SingleLang("Todaymic", "https://todaymic.com", "en", overrideVersionCode = 1), SingleLang("TonizuToon", "https://tonizutoon.com", "tr", isNsfw = true), SingleLang("ToonChill", "https://toonchill.com", "en", overrideVersionCode = 1), - SingleLang("ToonGod", "https://www.toongod.com", "en", isNsfw = true, overrideVersionCode = 4), - SingleLang("Toonily", "https://toonily.com", "en", isNsfw = true, overrideVersionCode = 11), + SingleLang("ToonGod", "https://www.toongod.org", "en", isNsfw = true, overrideVersionCode = 5), SingleLang("Toonily.net", "https://toonily.net", "en", isNsfw = true, className = "Toonilynet", overrideVersionCode = 2), + SingleLang("Toonily", "https://toonily.com", "en", isNsfw = true, overrideVersionCode = 11), + SingleLang("Toonizy", "https://toonizy.com", "en", isNsfw = true), SingleLang("ToonMany", "https://toonmany.com", "en", isNsfw = true), SingleLang("Top Manhua", "https://topmanhua.com", "en", overrideVersionCode = 2), SingleLang("Tortuga Ceviri", "https://tortuga-ceviri.com", "tr"), - SingleLang("Trap Scans", "https://trapscans.com", "en"), + SingleLang("Traducciones Moonlight", "https://traduccionesmoonlight.com", "es"), SingleLang("TreeManga", "https://treemanga.com", "en", overrideVersionCode = 1), SingleLang("TritiniaScans", "https://tritinia.org", "en", overrideVersionCode = 4), - SingleLang("Tudo Quadrinhos", "https://tudoquadrinhos.com.br", "pt-BR"), SingleLang("Tumangaonline.site", "https://tumangaonline.site", "es", isNsfw = true, className = "TumangaonlineSite", pkgName = "tumangaonlinesite"), - SingleLang("Türkçe Manga", "https://turkcemanga.com", "tr", className = "TurkceManga", overrideVersionCode = 2), + SingleLang("Unitoon Oficial", "https://unitoonoficial.com", "es"), + SingleLang("Unitoon", "https://lectorunitoon.com", "es"), SingleLang("Valkyrie Scan", "https://valkyriescan.com", "pt-BR", isNsfw = true), SingleLang("Ver Manhwas", "https://vermanhwa.es", "es", isNsfw = true, overrideVersionCode = 1), SingleLang("VinManga", "https://vinload.com", "en", isNsfw = true), - SingleLang("Visbellum", "https://visbellum.com", "pt-BR", overrideVersionCode = 2), - SingleLang("VoirComic", "https://voircomic.com", "fr"), + SingleLang("Vórtce Scan", "https://vortcescan.com.br", "pt-BR", className = "VortceScan"), SingleLang("Wakamics", "https://wakamics.net", "en"), - SingleLang("Wakascan", "https://wakascan.com", "fr", overrideVersionCode = 1), - SingleLang("War Queen Scan", "https://wqscan.com", "pt-BR", overrideVersionCode = 6), + SingleLang("Webdex Scans", "https://webdexscans.com", "en", isNsfw = false), + SingleLang("Webtoon City", "https://webtooncity.com", "en", isNsfw = false), SingleLang("Webtoon Hatti", "https://webtoonhatti.com", "tr", overrideVersionCode = 1), SingleLang("Webtoon TR", "https://webtoon-tr.com", "tr", overrideVersionCode = 1), SingleLang("WebToonily", "https://webtoonily.com", "en", isNsfw = true, overrideVersionCode = 1), @@ -455,6 +498,7 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("WebtoonsTOP", "https://webtoons.top", "en", isNsfw = true), SingleLang("WebtoonUK", "https://webtoon.uk", "en", overrideVersionCode = 2), SingleLang("WebtoonXYZ", "https://www.webtoon.xyz", "en", isNsfw = true, overrideVersionCode = 3), + SingleLang("Wicked Witch Scan", "https://wickedwitchscan.com", "pt-BR"), SingleLang("Winter Scan", "https://winterscan.com", "pt-BR", overrideVersionCode = 4), SingleLang("Wonderland Scan", "https://wonderlandscan.com", "pt-BR", overrideVersionCode = 3), SingleLang("WoopRead", "https://woopread.com", "en", overrideVersionCode = 1), @@ -468,21 +512,21 @@ class MadaraGenerator : ThemeSourceGenerator { SingleLang("YaoiScan", "https://yaoiscan.com", "en", isNsfw = true), SingleLang("YaoiToon", "https://yaoitoon.com", "en", isNsfw = true), SingleLang("YonaBar", "https://yonabar.com", "ar", isNsfw = true, overrideVersionCode = 2), - SingleLang("YugenMangas", "https://yugenmangas.com.br", "pt-BR", overrideVersionCode = 2), SingleLang("Yuri Verso", "https://yuri.live", "pt-BR", overrideVersionCode = 3), SingleLang("Zandy no Fansub", "https://zandynofansub.aishiteru.org", "en"), SingleLang("ZinChanManga", "https://zinchanmanga.com", "en", isNsfw = true), + SingleLang("ZinManga.top (unoriginal)", "https://zinmanga.top", "en", isNsfw = false, className = "ZinMangaTop"), SingleLang("Zinmanga", "https://zinmanga.com", "en", overrideVersionCode = 1), SingleLang("Zinmanhwa", "https://zinmanhwa.com", "en"), SingleLang("ZuttoManga", "https://zuttomanga.com", "en", overrideVersionCode = 1), - SingleLang("Çizgi Roman Arşivi", "https://cizgiromanarsivi.com", "tr", className = "CizgiRomanArsivi"), SingleLang("شبكة كونان العربية", "https://manga.detectiveconanar.com", "ar", className = "DetectiveConanAr", overrideVersionCode = 2), SingleLang("عرب تونز", "https://arabtoons.net", "ar", isNsfw = true, className = "ArabToons"), SingleLang("مانجا العاشق", "https://3asq.org", "ar", className = "Manga3asq", overrideVersionCode = 2), - SingleLang("مانجا العرب Manga Alarab", "https://manga-alarab.com", "ar", className = "MangAlarab", overrideVersionCode = 1), - SingleLang("مانجا عرب تيم Manga Arab Team", "https://mangaarbteam.com", "ar", className = "MangaArabTeam", overrideVersionCode = 1), - SingleLang("مانجا ليك", "https://mangalek.com", "ar", className = "Mangalek", overrideVersionCode = 2), + SingleLang("مانجا ليك", "https://manga-lek.net", "ar", className = "Mangalek", overrideVersionCode = 4), + SingleLang("مانجا ليكس", "https://mangaleks.com", "ar", className = "MangaLeks"), SingleLang("مانجا لينك", "https://mangalink.io", "ar", className = "MangaLinkio", overrideVersionCode = 3), + SingleLang("كوميك العرب", "https://comicarab.com", "ar", isNsfw = true, className = "ComicArab"), + SingleLang("فالكون مانجا", "https://falconmanga.com", "ar", className = "FalconManga"), SingleLang("巴卡漫画", "https://bakamh.com", "zh", isNsfw = true, className = "Bakamh"), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalog.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalog.kt index d1cbd0d4c5..14ebf65e85 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalog.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalog.kt @@ -88,14 +88,14 @@ abstract class MangaCatalog( override fun chapterListSelector(): String = "div.w-full > div.bg-bg-secondary > div.grid" override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val name1 = element.select(".col-span-3 > a").text() + val name1 = element.select(".col-span-4 > a").text() val name2 = element.select(".text-xs:not(a)").text() if (name2 == "") { name = name1 } else { name = "$name1 - $name2" } - url = element.select(".col-span-3 > a").attr("abs:href") + url = element.select(".col-span-4 > a").attr("abs:href") date_upload = System.currentTimeMillis() } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalogGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalogGenerator.kt index 94ad066701..dc926a643d 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalogGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangacatalog/MangaCatalogGenerator.kt @@ -9,11 +9,12 @@ class MangaCatalogGenerator : ThemeSourceGenerator { override val themeClass = "MangaCatalog" - override val baseVersionCode: Int = 3 + override val baseVersionCode: Int = 4 override val sources = listOf( SingleLang("Read Attack on Titan Shingeki no Kyojin Manga", "https://ww8.readsnk.com", "en", className = "ReadAttackOnTitanShingekiNoKyojinManga", overrideVersionCode = 4), SingleLang("Read Berserk Manga", "https://readberserk.com", "en"), + SingleLang("Read Black Clover Manga Online", "https://ww7.readblackclover.com", "en"), SingleLang("Read Boku no Hero Academia My Hero Academia Manga", "https://ww6.readmha.com", "en", className = "ReadBokuNoHeroAcademiaMyHeroAcademiaManga", overrideVersionCode = 2), SingleLang("Read Chainsaw Man Manga Online", "https://ww1.readchainsawman.com", "en"), SingleLang("Read Dr. Stone Manga Online", "https://ww3.readdrstone.com", "en", className = "ReadDrStoneMangaOnline"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt index 00df4ebdcc..7589f1ef0a 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHub.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.multisrc.mangahub +import eu.kanade.tachiyomi.lib.randomua.UserAgentType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.interceptor.rateLimit @@ -48,7 +50,10 @@ abstract class MangaHub( private var baseCdnUrl = "https://imgx.mghubcdn.com" override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(::uaIntercept) + .setRandomUserAgent( + userAgentType = UserAgentType.DESKTOP, + filterInclude = listOf("chrome"), + ) .addInterceptor(::apiAuthInterceptor) .rateLimit(1) .build() @@ -65,35 +70,6 @@ abstract class MangaHub( open val json: Json by injectLazy() - private var userAgent: String? = null - private var checkedUa = false - - private fun uaIntercept(chain: Interceptor.Chain): Response { - if (userAgent == null && !checkedUa) { - val uaResponse = chain.proceed(GET(UA_DB_URL)) - - if (uaResponse.isSuccessful) { - // only using desktop chromium-based browsers, apparently they refuse to load(403) if not chrome(ium) - val uaList = json.decodeFromString>>(uaResponse.body.string()) - val chromeUserAgentString = uaList["desktop"]!!.filter { it.contains("chrome", ignoreCase = true) } - userAgent = chromeUserAgentString.random() - checkedUa = true - } - - uaResponse.close() - } - - if (userAgent != null) { - val newRequest = chain.request().newBuilder() - .header("User-Agent", userAgent!!) - .build() - - return chain.proceed(newRequest) - } - - return chain.proceed(chain.request()) - } - private fun apiAuthInterceptor(chain: Interceptor.Chain): Response { val originalRequest = chain.request() @@ -514,8 +490,4 @@ abstract class MangaHub( Genre("Wuxia", "wuxia"), Genre("Yuri", "yuri"), ) - - companion object { - private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" - } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt index 43279683ad..3b05e98dfa 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangahub/MangaHubGenerator.kt @@ -9,7 +9,7 @@ class MangaHubGenerator : ThemeSourceGenerator { override val themeClass = "MangaHub" - override val baseVersionCode: Int = 21 + override val baseVersionCode: Int = 22 override val sources = listOf( // SingleLang("1Manga.co", "https://1manga.co", "en", isNsfw = true, className = "OneMangaCo"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawGenerator.kt index 1c7c343cad..afc41109e9 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawGenerator.kt @@ -8,12 +8,11 @@ class MangaRawGenerator : ThemeSourceGenerator { override val themePkg = "mangaraw" - override val baseVersionCode = 4 + override val baseVersionCode = 5 override val sources = listOf( SingleLang("SyoSetu", "https://syosetu.top", "ja"), - SingleLang("MangaRaw", "https://manga1001.in", "ja", pkgName = "manga9co", overrideVersionCode = 2), - SingleLang("MangaRawRU", "https://mangaraw.ru", "ja", overrideVersionCode = 1), + SingleLang("MangaRaw", "https://manga1001.in", "ja", pkgName = "manga9co", isNsfw = true, overrideVersionCode = 2), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawTheme.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawTheme.kt index bf463a527b..c84d214cb3 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawTheme.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaraw/MangaRawTheme.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.HttpUrl.Companion.toHttpUrl import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Evaluator @@ -23,7 +24,11 @@ abstract class MangaRawTheme( override fun popularMangaFromElement(element: Element) = SManga.create().apply { setUrlWithoutDomain(element.selectFirst(Evaluator.Tag("a"))!!.attr("href")) - title = element.selectFirst(Evaluator.Tag("h3"))!!.text().sanitizeTitle() + + // Title could be missing. Uses URL as a last effort, ex: okaeri-alice-raw + title = element.selectFirst(Evaluator.Tag("h3"))?.text()?.sanitizeTitle() + ?: (baseUrl + url).toHttpUrl().pathSegments.first() + thumbnail_url = element.selectFirst(Evaluator.Tag("img"))?.absUrl("data-src") } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt index 63bcb401f4..867c81e97c 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangareader/MangaReaderGenerator.kt @@ -21,7 +21,7 @@ class MangaReaderGenerator : ThemeSourceGenerator { baseUrl = "https://mangafire.to", langs = listOf("en", "es", "es-419", "fr", "ja", "pt", "pt-BR"), isNsfw = true, - overrideVersionCode = 1, + overrideVersionCode = 2, ), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSar.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSar.kt deleted file mode 100644 index 34e35e203e..0000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSar.kt +++ /dev/null @@ -1,301 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.mangasar - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.booleanOrNull -import kotlinx.serialization.json.floatOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import org.jsoup.parser.Parser -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -abstract class MangaSar( - override val name: String, - override val baseUrl: String, - override val lang: String, -) : HttpSource() { - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(::searchIntercept) - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT_HTML) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", "$baseUrl/") - - protected fun apiHeadersBuilder(): Headers.Builder = headersBuilder() - .set("Accept", ACCEPT) - .add("X-Requested-With", "XMLHttpRequest") - - private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() } - - protected val json: Json by injectLazy() - - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()) - .map(::popularMangaFromElement) - - return MangasPage(mangas, hasNextPage = false) - } - - protected open fun popularMangaSelector(): String = - "div:contains(Populares) ~ ul.mangasList li div.gridbox" - - protected open fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.title a").first()!!.text() - thumbnail_url = element.select("div.thumb img").first()!!.attr("abs:src") - setUrlWithoutDomain(element.select("a").first()!!.attr("href")) - } - - override fun latestUpdatesRequest(page: Int): Request { - val form = FormBody.Builder() - .add("pagina", page.toString()) - .build() - - val newHeaders = apiHeadersBuilder() - .add("Content-Length", form.contentLength().toString()) - .add("Content-Type", form.contentType().toString()) - .build() - - return POST("$baseUrl/jsons/news/chapters.json", newHeaders, form) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs() - - val latestMangas = result.releases - .map(::latestUpdatesFromObject) - .distinctBy { it.url } - - val hasNextPage = result.page.toInt() < result.totalPage!! - - return MangasPage(latestMangas, hasNextPage) - } - - protected fun latestUpdatesFromObject(release: MangaSarReleaseDto) = SManga.create().apply { - title = release.name.withoutEntities() - thumbnail_url = release.image - url = release.link - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/wp-json/site/search/".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("keyword", query) - .addQueryParameter("type", "undefined") - .toString() - - return GET(url, apiHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = response.parseAs>() - - val searchResults = result.values.map(::searchMangaFromObject) - - return MangasPage(searchResults, hasNextPage = false) - } - - private fun searchMangaFromObject(manga: MangaSarTitleDto) = SManga.create().apply { - title = manga.title - thumbnail_url = manga.image - setUrlWithoutDomain(manga.url) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - val infoElement = document.selectFirst("div.manga-single div.dados")!! - - return SManga.create().apply { - title = infoElement.selectFirst("h1")!!.text() - thumbnail_url = infoElement.selectFirst("div.thumb img")!!.attr("abs:src") - description = infoElement.selectFirst("div.sinopse")!!.text() - genre = infoElement.select("ul.generos li a span.button").joinToString { it.text() } - } - } - - override fun chapterListRequest(manga: SManga): Request = chapterListPaginatedRequest(manga.url) - - protected open fun chapterListPaginatedRequest(mangaUrl: String, page: Int = 1): Request { - val mangaId = mangaUrl.substringAfterLast("/") - - val newHeaders = apiHeadersBuilder() - .set("Referer", baseUrl + mangaUrl) - .build() - - val url = "$baseUrl/jsons/series/chapters_list.json".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("order", "desc") - .addQueryParameter("id_s", mangaId) - .toString() - - return GET(url, newHeaders) - } - - override fun chapterListParse(response: Response): List { - val mangaUrl = response.request.header("Referer")!!.substringAfter(baseUrl) - - var result = response.parseAs() - - if (result.chapters.isNullOrEmpty()) { - return emptyList() - } - - val chapters = result.chapters!! - .map(::chapterFromObject) - .toMutableList() - - var page = result.page!! + 1 - val lastPage = result.totalPages - - while (++page <= lastPage!!) { - val nextPageRequest = chapterListPaginatedRequest(mangaUrl, page) - result = client.newCall(nextPageRequest).execute().parseAs() - - chapters += result.chapters!! - .map(::chapterFromObject) - .toMutableList() - } - - return chapters - } - - private fun chapterFromObject(chapter: MangaSarChapterDto): SChapter = SChapter.create().apply { - name = "Cap. " + (if (chapter.number.booleanOrNull != null) "0" else chapter.number.content) + - (if (chapter.name.isString) " - " + chapter.name.content else "") - chapter_number = chapter.number.floatOrNull ?: -1f - date_upload = chapter.dateCreated.substringBefore("T").toDate() - setUrlWithoutDomain(chapter.link) - } - - protected open fun pageListApiRequest(chapterUrl: String, serieId: String, token: String): Request { - val newHeaders = apiHeadersBuilder() - .set("Referer", chapterUrl) - .build() - - val url = "$baseUrl/jsons/series/images_list.json".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("id_serie", serieId) - .addQueryParameter("secury", token) - .toString() - - return GET(url, newHeaders) - } - - override fun pageListParse(response: Response): List { - val document = response.asJsoup() - val apiParams = document.selectFirst("script:containsData(id_serie)")?.data() - ?: throw Exception(TOKEN_NOT_FOUND) - - val chapterUrl = response.request.url.toString() - val serieId = apiParams.substringAfter("\"") - .substringBefore("\"") - val token = TOKEN_REGEX.find(apiParams)!!.groupValues[1] - - val apiRequest = pageListApiRequest(chapterUrl, serieId, token) - val apiResponse = client.newCall(apiRequest).execute().parseAs() - - return apiResponse.images - .filter { it.url.startsWith("http") } - .mapIndexed { i, page -> Page(i, chapterUrl, page.url) } - } - - override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - protected fun searchIntercept(chain: Interceptor.Chain): Response { - if (chain.request().url.toString().contains("/search/")) { - val homeRequest = popularMangaRequest(1) - val document = chain.proceed(homeRequest).asJsoup() - - val apiParams = document.select("script:containsData(pAPI)").first()!!.data() - .substringAfter("pAPI = ") - .substringBeforeLast(";") - .let { json.parseToJsonElement(it) } - .jsonObject - - val newUrl = chain.request().url.newBuilder() - .addQueryParameter("nonce", apiParams["nonce"]!!.jsonPrimitive.content) - .build() - - val newRequest = chain.request().newBuilder() - .url(newUrl) - .build() - - return chain.proceed(newRequest) - } - - return chain.proceed(chain.request()) - } - - protected inline fun Response.parseAs(): T = use { - json.decodeFromString(body.string()) - } - - protected fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.withoutEntities(): String { - return Parser.unescapeEntities(this, true) - } - - companion object { - private const val ACCEPT = "application/json, text/plain, */*" - private const val ACCEPT_HTML = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - - private val TOKEN_REGEX = "token\\s+= \"(.*)\"".toRegex() - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - - const val TOKEN_NOT_FOUND = "Não foi possível obter o token de leitura." - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarDto.kt deleted file mode 100644 index 432f64be2c..0000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarDto.kt +++ /dev/null @@ -1,52 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.mangasar - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonNames -import kotlinx.serialization.json.JsonPrimitive - -@Serializable -data class MangaSarLatestDto( - val page: String, - @JsonNames("lancamentos") val releases: List = emptyList(), - @SerialName("total_page") val totalPage: Int? = 0, -) - -@Serializable -data class MangaSarReleaseDto( - val image: String, - val link: String, - val name: String, -) - -@Serializable -data class MangaSarTitleDto( - @SerialName("img") val image: String, - val title: String, - val url: String, -) - -@Serializable -data class MangaSarPaginatedChaptersDto( - val chapters: List? = emptyList(), - @SerialName("pagina") val page: Int? = -1, - @SerialName("total_pags") val totalPages: Int? = -1, -) - -@Serializable -data class MangaSarChapterDto( - @SerialName("date_created") val dateCreated: String, - val link: String, - @SerialName("chapter_name") val name: JsonPrimitive, - val number: JsonPrimitive, -) - -@Serializable -data class MangaSarReaderDto( - val images: List = emptyList(), -) - -@Serializable -data class MangaSarPageDto( - val url: String, -) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarGenerator.kt deleted file mode 100644 index 89d49bb0c4..0000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangasar/MangaSarGenerator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.mangasar - -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class MangaSarGenerator : ThemeSourceGenerator { - - override val themePkg = "mangasar" - - override val themeClass = "MangaSar" - - override val baseVersionCode: Int = 7 - - override val sources = listOf( - SingleLang("Mangazim", "https://mangazim.com", "pt-BR"), - SingleLang("MangásUp", "https://mangasup.net", "pt-BR", className = "MangasUp"), - SingleLang("Seemangas", "https://seemangas.com", "pt-BR", isNsfw = true), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - MangaSarGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt index d29b8fb2a8..394f0ecba6 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.multisrc.mangathemesia import android.app.Application import android.content.SharedPreferences -import android.util.Log -import android.widget.Toast -import androidx.preference.EditTextPreference import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.ConfigurableSource @@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive @@ -26,7 +25,6 @@ import okhttp3.FormBody import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -37,7 +35,6 @@ import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.io.IOException import java.lang.IllegalArgumentException import java.text.SimpleDateFormat import java.util.Locale @@ -60,81 +57,19 @@ abstract class MangaThemesia( override val supportsLatest = true - // override with true if you want useRandomUserAgentByDefault to be on by default for some source - protected open val useRandomUserAgentByDefault: Boolean = false - - protected open val filterIncludeUserAgent: List = listOf() - protected open val filterExcludeUserAgent: List = listOf() - - private var userAgent: String? = null - private var checkedUa = false - - protected val hasUaIntercept by lazy { - client.interceptors.toString().contains("uaIntercept") - } - - protected val uaIntercept = object : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val useRandomUa = preferences.getBoolean(PREF_KEY_RANDOM_UA, false) - val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "") - - try { - if (hasUaIntercept && (useRandomUa || customUa!!.isNotBlank())) { - Log.i("Extension_setting", "$TITLE_RANDOM_UA or $TITLE_CUSTOM_UA option is ENABLED") - - if (customUa!!.isNotBlank() && useRandomUa.not()) { - userAgent = customUa - } - - if (userAgent.isNullOrBlank() && !checkedUa) { - val uaResponse = chain.proceed(GET(UA_DB_URL)) - - if (uaResponse.isSuccessful) { - var listUserAgentString = - json.decodeFromString>>(uaResponse.body.string())["desktop"] - - if (filterIncludeUserAgent.isNotEmpty()) { - listUserAgentString = listUserAgentString!!.filter { - filterIncludeUserAgent.any { filter -> - it.contains(filter, ignoreCase = true) - } - } - } - if (filterExcludeUserAgent.isNotEmpty()) { - listUserAgentString = listUserAgentString!!.filterNot { - filterExcludeUserAgent.any { filter -> - it.contains(filter, ignoreCase = true) - } - } - } - userAgent = listUserAgentString!!.random() - checkedUa = true - } - - uaResponse.close() - } - - if (userAgent.isNullOrBlank().not()) { - val newRequest = chain.request().newBuilder() - .header("User-Agent", userAgent!!.trim()) - .build() - - return chain.proceed(newRequest) - } - } - - return chain.proceed(chain.request()) - } catch (e: Exception) { - throw IOException(e.message) - } - } + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .setRandomUserAgent( + preferences.getPrefUAType(), + preferences.getPrefCustomUA(), + ) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() } - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(uaIntercept) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() + override fun headersBuilder() = super.headersBuilder() + .set("Referer", "$baseUrl/") open val projectPageString = "/project" @@ -209,7 +144,7 @@ abstract class MangaThemesia( } } url.addPathSegment("") - return GET(url.toString()) + return GET(url.build(), headers) } override fun searchMangaParse(response: Response): MangasPage { @@ -274,7 +209,7 @@ abstract class MangaThemesia( } } - private fun String?.removeEmptyPlaceholder(): String? { + protected fun String?.removeEmptyPlaceholder(): String? { return if (this.isNullOrBlank() || this == "-" || this == "N/A") null else this } @@ -283,6 +218,7 @@ abstract class MangaThemesia( listOf("ongoing", "publishing").any { this.contains(it, ignoreCase = true) } -> SManga.ONGOING this.contains("hiatus", ignoreCase = true) -> SManga.ON_HIATUS this.contains("completed", ignoreCase = true) -> SManga.COMPLETED + listOf("dropped", "cancelled").any { this.contains(it, ignoreCase = true) } -> SManga.CANCELLED else -> SManga.UNKNOWN } @@ -331,9 +267,10 @@ abstract class MangaThemesia( open val pageSelector = "div#readerarea img" override fun pageListParse(document: Document): List { + val chapterUrl = document.location() val htmlPages = document.select(pageSelector) .filterNot { it.imgAttr().isEmpty() } - .mapIndexed { i, img -> Page(i, "", img.imgAttr()) } + .mapIndexed { i, img -> Page(i, chapterUrl, img.imgAttr()) } countViews(document) @@ -348,12 +285,21 @@ abstract class MangaThemesia( emptyList() } val scriptPages = imageList.mapIndexed { i, jsonEl -> - Page(i, "", jsonEl.jsonPrimitive.content) + Page(i, chapterUrl, jsonEl.jsonPrimitive.content) } return scriptPages } + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*") + .set("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + /** * Set it to false if you want to disable the extension reporting the view count * back to the source website through admin-ajax.php. @@ -524,7 +470,9 @@ abstract class MangaThemesia( val links = response.asJsoup().select("a[itemprop=item]") // near the top of page: home > manga > current chapter if (links.size == 3) { - return links[1].attr("href").toHttpUrlOrNull()?.encodedPath + val newUrl = links[1].attr("href").toHttpUrlOrNull() ?: return null + val isNewMangaUrl = (baseMangaUrl.host == newUrl.host && pathLengthIs(newUrl, 2) && newUrl.pathSegments[0] == baseMangaUrl.pathSegments[0]) + if (isNewMangaUrl) return newUrl.pathSegments[1] } } } @@ -566,58 +514,7 @@ abstract class MangaThemesia( override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") override fun setupPreferenceScreen(screen: PreferenceScreen) { - addRandomAndCustomUserAgentPreferences(screen) - } - - protected fun addRandomAndCustomUserAgentPreferences(screen: PreferenceScreen) { - if (!hasUaIntercept) { - return // Unable to change the user agent. Therefore the preferences won't be displayed. - } - - val prefRandomUserAgent = SwitchPreferenceCompat(screen.context).apply { - key = PREF_KEY_RANDOM_UA - title = TITLE_RANDOM_UA - summary = if (preferences.getBoolean(PREF_KEY_RANDOM_UA, useRandomUserAgentByDefault)) userAgent else "" - setDefaultValue(useRandomUserAgentByDefault) - - setOnPreferenceChangeListener { _, newValue -> - val useRandomUa = newValue as Boolean - preferences.edit().putBoolean(PREF_KEY_RANDOM_UA, useRandomUa).apply() - if (!useRandomUa) { - Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show() - } else { - userAgent = null - if (preferences.getString(PREF_KEY_CUSTOM_UA, "").isNullOrBlank().not()) { - Toast.makeText(screen.context, SUMMARY_CLEANING_CUSTOM_UA, Toast.LENGTH_LONG).show() - } - } - - preferences.edit().putString(PREF_KEY_CUSTOM_UA, "").apply() - true - } - } - - val prefCustomUserAgent = EditTextPreference(screen.context).apply { - key = PREF_KEY_CUSTOM_UA - title = TITLE_CUSTOM_UA - summary = preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim() - setOnPreferenceChangeListener { _, newValue -> - val customUa = newValue as String - preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply() - if (customUa.isBlank()) { - Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show() - } else { - userAgent = null - } - summary = customUa.trim() - prefRandomUserAgent.summary = "" - prefRandomUserAgent.isChecked = false - true - } - } - - screen.addPreference(prefRandomUserAgent) - screen.addPreference(prefCustomUserAgent) + addRandomUAPreferenceToScreen(screen) } companion object { @@ -629,16 +526,5 @@ abstract class MangaThemesia( private val CHAPTER_PAGE_ID_REGEX = "chapter_id\\s*=\\s*(\\d+);".toRegex() val JSON_IMAGE_LIST_REGEX = "\"images\"\\s*:\\s*(\\[.*?])".toRegex() - - const val TITLE_RANDOM_UA = "Use Random Latest User-Agent" - const val PREF_KEY_RANDOM_UA = "pref_key_random_ua" - - const val TITLE_CUSTOM_UA = "Custom User-Agent" - const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua" - - const val SUMMARY_CLEANING_CUSTOM_UA = "$TITLE_CUSTOM_UA cleared." - - const val RESTART_APP_STRING = "Restart Tachiyomi to apply new setting." - private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt index 073eba557f..39253dbb93 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt @@ -11,10 +11,9 @@ class MangaThemesiaGenerator : ThemeSourceGenerator { override val themeClass = "MangaThemesia" - override val baseVersionCode: Int = 25 + override val baseVersionCode: Int = 27 override val sources = listOf( - // Extenções Modificadas: SingleLang("Mundo Mangá-Kun", "https://mundomangakun.com.br", "pt-BR", className = "MundoMangaKun", isNsfw = true, overrideVersionCode = 1), @@ -22,115 +21,147 @@ class MangaThemesiaGenerator : ThemeSourceGenerator { SingleLang("Silence Scan", "https://silencescan.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 6), SingleLang("Origami Orpheans", "https://origami-orpheans.com.br", "pt-BR", overrideVersionCode = 10), - // Extenções Oficiais: - MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 23), - MultiLang("Flame Scans", "https://flamescans.org", listOf("en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 4), - MultiLang("Komik Lab", "https://komiklab.com", listOf("en", "id"), className = "KomikLabFactory", pkgName = "komiklab", overrideVersionCode = 2), - MultiLang("Miau Scan", "https://miauscan.com", listOf("es", "pt-BR")), - SingleLang("Animated Glitched Scans", "https://anigliscans.com", "en"), - SingleLang("Arcane scan", "https://arcanescan.fr", "fr"), + // Extenções Oficiais: + MultiLang("Asura Scans", "https://asuratoon.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 31), + MultiLang("Miau Scan", "https://miaucomics.org", listOf("es", "pt-BR"), overrideVersionCode = 2), + SingleLang("Ainz Scans ID", "https://ainzscans.site", "id"), + SingleLang("AiYuManga", "https://aiyumanhua.com", "es", overrideVersionCode = 8), + SingleLang("Alceascan", "https://alceascan.my.id", "id"), + SingleLang("Animated Glitched Scans", "https://anigliscans.xyz", "en", overrideVersionCode = 1), SingleLang("Arena Scans", "https://arenascans.net", "en", overrideVersionCode = 1), + SingleLang("Arkham Scan", "https://arkhamscan.com", "pt-BR"), + SingleLang("Arven Scans", "https://arvenscans.com", "en"), SingleLang("Azure Scans", "https://azuremanga.com", "en", overrideVersionCode = 1), SingleLang("Banana-Scan", "https://banana-scan.com", "fr", className = "BananaScan", isNsfw = true), + SingleLang("Beast Scans", "https://beastscans.net", "ar", overrideVersionCode = 1), SingleLang("Boosei", "https://boosei.net", "id", overrideVersionCode = 2), - SingleLang("Babel Wuxia", "https://babelwuxia.com", "en", overrideVersionCode = 1), SingleLang("Cartel de Manhwas", "https://carteldemanhwas.com", "es", overrideVersionCode = 5), - SingleLang("Clayrer", "https://clayrer.net", "es"), - SingleLang("Constellar Scans", "https://constellarscans.com", "en", isNsfw = true, overrideVersionCode = 14), - SingleLang("Cosmic Scans", "https://cosmicscans.com", "en", overrideVersionCode = 1), - SingleLang("CosmicScans.id", "https://cosmicscans.id", "id", overrideVersionCode = 1, className = "CosmicScansID"), - SingleLang("Diskus Scan", "https://diskusscan.com", "pt-BR", overrideVersionCode = 7), + SingleLang("Cosmic Scans", "https://cosmic-scans.com", "en", overrideVersionCode = 2), + SingleLang("CosmicScans.id", "https://cosmicscans.id", "id", overrideVersionCode = 3, className = "CosmicScansID"), + SingleLang("Diskus Scan", "https://diskusscan.com", "pt-BR", overrideVersionCode = 8), SingleLang("Dojing.net", "https://dojing.net", "id", isNsfw = true, className = "DojingNet"), - SingleLang("DragonTranslation", "https://dragontranslation.com", "es", isNsfw = true, overrideVersionCode = 9), - SingleLang("DuniaKomik.id", "https://duniakomik.id", "id", className = "DuniaKomikId"), - SingleLang("ElarcPage", "https://elarcpage.com", "en"), + SingleLang("DuniaKomik.id", "https://duniakomik.org", "id", className = "DuniaKomikId", overrideVersionCode = 2), + SingleLang("Elarc Reader", "https://elarcreader.com", "en", className = "ElarcPage", overrideVersionCode = 1), + SingleLang("EnryuManga", "https://enryumanga.com", "en"), SingleLang("Epsilon Scan", "https://epsilonscan.fr", "fr", isNsfw = true), + SingleLang("Evil production", "https://evil-manga.eu", "cs", isNsfw = true), + SingleLang("Fairy Manga", "https://fairymanga.com", "en", className = "QueenScans", overrideVersionCode = 1), + SingleLang("Flame Comics", "https://flamecomics.com", "en"), SingleLang("Franxx Mangás", "https://franxxmangas.net", "pt-BR", className = "FranxxMangas", isNsfw = true), - SingleLang("Gecenin Lordu", "https://geceninlordu.com", "tr", overrideVersionCode = 1), + SingleLang("Freak Scans", "https://freakscans.com", "en"), + SingleLang("Glory Scans", "https://gloryscans.fr", "fr"), SingleLang("GoGoManga", "https://gogomanga.fun", "en", overrideVersionCode = 1), SingleLang("Gremory Mangas", "https://gremorymangas.com", "es"), SingleLang("Hanuman Scan", "https://hanumanscan.com", "en"), SingleLang("Heroxia", "https://heroxia.com", "id", isNsfw = true), SingleLang("Imagine Scan", "https://imaginescan.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 1), + SingleLang("InariManga", "https://inarimanga.com", "es", overrideVersionCode = 7), SingleLang("Infernal Void Scans", "https://void-scans.com", "en", overrideVersionCode = 5), + SingleLang("Kai Scans", "https://kaiscans.com", "en", isNsfw = false), + SingleLang("Kanzenin", "https://kanzenin.info", "id", isNsfw = true, overrideVersionCode = 1), SingleLang("KataKomik", "https://katakomik.online", "id"), - SingleLang("Komik Seru", "https://komikseru.me", "id", isNsfw = true), - SingleLang("Kanzenin", "https://kanzenin.xyz", "id", isNsfw = true), - SingleLang("KomikSan", "https://komiksan.ml", "id", overrideVersionCode = 1), + SingleLang("King of Shojo", "https://kingofshojo.com", "ar", overrideVersionCode = 1), SingleLang("Kiryuu", "https://kiryuu.id", "id", overrideVersionCode = 6), SingleLang("Komik AV", "https://komikav.com", "id", overrideVersionCode = 1), - SingleLang("Komik Cast", "https://komikcast.io", "id", overrideVersionCode = 19), + SingleLang("Komik Cast", "https://komikcast.lol", "id", overrideVersionCode = 25), + SingleLang("Komik Lab", "https://komiklab.com", "en", overrideVersionCode = 3), + SingleLang("Komik Seru", "https://komikseru.me", "id", isNsfw = true), + SingleLang("Komik Station", "https://komikstation.co", "id", overrideVersionCode = 4), SingleLang("KomikDewasa", "https://komikdewasa.org", "id", isNsfw = true), - SingleLang("Komik Station", "https://komikstation.co", "id", overrideVersionCode = 3), SingleLang("KomikIndo.co", "https://komikindo.co", "id", className = "KomikindoCo", overrideVersionCode = 3), SingleLang("KomikMama", "https://komikmama.co", "id", overrideVersionCode = 1), SingleLang("KomikManhwa", "https://komikmanhwa.me", "id", isNsfw = true), - SingleLang("KumaPoi", "https://kumapoi.club", "id", isNsfw = true, overrideVersionCode = 2), + SingleLang("Komiksan", "https://komiksan.link", "id", overrideVersionCode = 2), SingleLang("Komiku.com", "https://komiku.com", "id", className = "KomikuCom"), SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans", overrideVersionCode = 1), + SingleLang("KumaPoi", "https://kumapoi.info", "id", isNsfw = true, overrideVersionCode = 3), SingleLang("Legacy Scans", "https://legacy-scans.com", "fr", pkgName = "flamescansfr"), + SingleLang("Lelmanga", "https://www.lelmanga.com", "fr"), SingleLang("LianScans", "https://www.lianscans.my.id", "id", isNsfw = true), - SingleLang("Lunar Scans", "https://lunarscan.org", "en", isNsfw = true), - SingleLang("Magus Manga", "https://magusmanga.com", "ar"), + SingleLang("Lunar Scans", "https://lunarscan.org", "en", isNsfw = true, overrideVersionCode = 1), + SingleLang("LynxScans", "https://lynxscans.com", "en"), + SingleLang("Lyra Scans", "https://lyrascans.com", "en"), + SingleLang("Magus Manga", "https://magusmanga.com", "en", overrideVersionCode = 1), SingleLang("Manga Indo.me", "https://mangaindo.me", "id", className = "MangaIndoMe"), SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1), SingleLang("Mangacim", "https://www.mangacim.com", "tr", overrideVersionCode = 1), - SingleLang("MangaKita", "https://mangakita.net", "id", overrideVersionCode = 1), - SingleLang("Mangakyo", "https://mangakyo.id", "id", overrideVersionCode = 1), + SingleLang("MangaKita", "https://mangakita.id", "id", overrideVersionCode = 2), + SingleLang("Mangakyo", "https://mangakyo.org", "id", overrideVersionCode = 3), + SingleLang("MangaShiina", "https://mangashiina.com", "es"), + SingleLang("MangaShiro", "https://mangashiro.me", "id"), SingleLang("Mangasusu", "https://mangasusuku.xyz/", "id", isNsfw = true, overrideVersionCode = 2), - SingleLang("MangaTale", "https://mangatale.co", "id"), + SingleLang("MangaSwat", "https://goldragon.me", "ar", overrideVersionCode = 15), + SingleLang("MangaTale", "https://mangatale.co", "id", overrideVersionCode = 1), SingleLang("MangaWT", "https://mangawt.com", "tr", overrideVersionCode = 5), - SingleLang("Mangayaro", "https://mangayaro.net", "id"), + SingleLang("Mangayaro", "https://www.mangayaro.id", "id", overrideVersionCode = 1), + SingleLang("Manhwa Freak", "https://manhwa-freak.com", "en", overrideVersionCode = 3), SingleLang("Manhwa Lover", "https://manhwalover.com", "en", isNsfw = true, overrideVersionCode = 1), - SingleLang("MangaSwat", "https://swatmanga.me", "ar", overrideVersionCode = 9), - SingleLang("MangKomik", "https://mangkomik.net", "id", overrideVersionCode = 1), - SingleLang("Manhwa Freak", "https://manhwafreak.com", "en", overrideVersionCode = 1), - SingleLang("ManhwaDesu", "https://manhwadesu.org", "id", isNsfw = true, overrideVersionCode = 3), - SingleLang("ManhwaIndo", "https://manhwaindo.id", "id", isNsfw = true, overrideVersionCode = 2), - SingleLang("ManhwaLand.mom", "https://manhwaland.us", "id", isNsfw = true, className = "ManhwaLandMom", overrideVersionCode = 4), - SingleLang("ManhwaList", "https://manhwalist.xyz", "id", overrideVersionCode = 3), + SingleLang("ManhwaDesu", "https://manhwadesu.one", "id", isNsfw = true, overrideVersionCode = 4), + SingleLang("ManhwaFreak", "https://manhwafreak.fr", "fr", className = "ManhwaFreakFR"), + SingleLang("ManhwaIndo", "https://manhwaindo.id", "id", isNsfw = true, overrideVersionCode = 4), + SingleLang("ManhwaLand.mom", "https://manhwaland.lat", "id", isNsfw = true, className = "ManhwaLandMom", overrideVersionCode = 5), + SingleLang("ManhwaList", "https://manhwalist.com", "id", overrideVersionCode = 4), SingleLang("Manhwax", "https://manhwax.com", "en", isNsfw = true), SingleLang("Mareceh", "https://mareceh.com", "id", isNsfw = true, pkgName = "mangceh", overrideVersionCode = 10), - SingleLang("MasterKomik", "https://masterkomik.com", "id", overrideVersionCode = 1), SingleLang("MELOKOMIK", "https://melokomik.xyz", "id"), SingleLang("Mihentai", "https://mihentai.com", "all", isNsfw = true, overrideVersionCode = 2), + SingleLang("Mirai Scans", "https://miraiscans.com", "id"), SingleLang("MirrorDesu", "https://mirrordesu.me", "id", isNsfw = true), - SingleLang("Mode Scanlator", "https://modescanlator.com", "pt-BR", overrideVersionCode = 8), - SingleLang("Nekomik", "https://nekomik.com", "id"), + SingleLang("Nekomik", "https://nekomik.me", "id", overrideVersionCode = 2), SingleLang("Ngomik", "https://ngomik.net", "id", overrideVersionCode = 2), - SingleLang("NIGHT SCANS", "https://nightscans.org", "en", isNsfw = true, className = "NightScans", overrideVersionCode = 1), + SingleLang("NIGHT SCANS", "https://nightscans.net", "en", isNsfw = true, className = "NightScans", overrideVersionCode = 3), SingleLang("Nocturnal Scans", "https://nocturnalscans.com", "en", overrideVersionCode = 1), - SingleLang("Ozul Scans", "https://ozulscans.com", "ar"), - SingleLang("Patatescans", "https://patatescans.com", "fr", isNsfw = true, overrideVersionCode = 2), + SingleLang("Nonbiri", "https://nonbiri.space", "id"), + SingleLang("Noromax", "https://noromax.my.id", "id"), + SingleLang("OPSCANS", "https://opscans.com", "all"), + SingleLang("Otsugami", "https://otsugami.id", "id"), + SingleLang("Ozul Scans", "https://kingofmanga.com", "ar", overrideVersionCode = 2), SingleLang("Phantom Scans", "https://phantomscans.com", "en", overrideVersionCode = 1), SingleLang("PhenixScans", "https://phenixscans.fr", "fr", className = "PhenixScans", overrideVersionCode = 1), SingleLang("Pi Scans", "https://piscans.in", "id", overrideVersionCode = 1), - SingleLang("PMScans", "https://rackusreads.com", "en", overrideVersionCode = 3), + SingleLang("PotatoManga", "https://potatomanga.xyz", "ar"), + SingleLang("Quantum Scans", "https://readers-point.space", "en"), SingleLang("Raiki Scan", "https://raikiscan.com", "es"), + SingleLang("Raiscans", "https://www.raiscans.com", "en"), SingleLang("Raven Scans", "https://ravenscans.com", "en", overrideVersionCode = 1), SingleLang("Rawkuma", "https://rawkuma.com/", "ja"), SingleLang("ReadGojo", "https://readgojo.com", "en"), SingleLang("Readkomik", "https://readkomik.com", "en", className = "ReadKomik", overrideVersionCode = 1), SingleLang("Ryukonesia", "https://ryukonesia.net", "id"), - SingleLang("Sekaikomik", "https://www.sekaikomik.pro", "id", isNsfw = true, overrideVersionCode = 10), + SingleLang("Sekaikomik", "https://sekaikomik.bio", "id", isNsfw = true, overrideVersionCode = 11), SingleLang("Sekte Doujin", "https://sektedoujin.lol", "id", isNsfw = true, overrideVersionCode = 4), SingleLang("Senpai Ediciones", "http://senpaiediciones.com", "es", overrideVersionCode = 1), SingleLang("Shadow Mangas", "https://shadowmangas.com", "es", overrideVersionCode = 1), SingleLang("Shea Manga", "https://sheakomik.com", "id", overrideVersionCode = 4), + SingleLang("Siren Komik", "https://sirenkomik.my.id", "id", className = "MangKomik", overrideVersionCode = 2), + SingleLang("SkyMangas", "https://skymangas.com", "es", overrideVersionCode = 1), SingleLang("Snudae Scans", "https://snudaescans.com", "en", isNsfw = true, className = "BatotoScans", overrideVersionCode = 1), + SingleLang("Soul Scans", "https://soulscans.my.id", "id", overrideVersionCode = 1), + SingleLang("SSS Hentais", "https://hentais.sssscanlator.com", "pt-BR", isNsfw = true, className = "SssHentais", overrideVersionCode = 1), + SingleLang("SSSScanlator", "https://sssscanlator.com", "pt-BR", overrideVersionCode = 1), + SingleLang("Starlight Scan", "https://starligthscan.com", "pt-BR", isNsfw = true), SingleLang("Summer Fansub", "https://smmr.in", "pt-BR", isNsfw = true), - SingleLang("Surya Scans", "https://suryascans.com", "en"), - SingleLang("Sushi-Scan", "https://sushiscan.net", "fr", className = "SushiScan", overrideVersionCode = 5), + SingleLang("SummerToon", "https://summertoon.com", "tr"), + SingleLang("Surya Scans", "https://suryacomics.com", "en", overrideVersionCode = 2), + SingleLang("Sushi-Scan", "https://sushiscan.net", "fr", className = "SushiScan", overrideVersionCode = 9), + SingleLang("Sushiscan.fr", "https://sushiscan.fr", "fr", className = "SushiScanFR"), SingleLang("Tarot Scans", "https://www.tarotscans.com", "tr"), + SingleLang("Tecno Scan", "https://tecnoscann.com", "es", isNsfw = true, overrideVersionCode = 6), + SingleLang("TenkaiScan", "https://tenkaiscan.net", "es", isNsfw = true), + SingleLang("Tenshi.id", "https://tenshi.id", "id", className = "TenshiId", pkgName = "masterkomik", overrideVersionCode = 4), SingleLang("The Apollo Team", "https://theapollo.team", "en"), SingleLang("Tsundoku Traduções", "https://tsundoku.com.br", "pt-BR", className = "TsundokuTraducoes", overrideVersionCode = 9), SingleLang("TukangKomik", "https://tukangkomik.id", "id", overrideVersionCode = 1), SingleLang("TurkToon", "https://turktoon.com", "tr"), - SingleLang("Uzay Manga", "https://uzaymanga.com", "tr", overrideVersionCode = 4), - SingleLang("Walpurgi Scan", "https://www.walpurgiscan.com", "it", overrideVersionCode = 6, className = "WalpurgisScan", pkgName = "walpurgisscan"), - SingleLang("West Manga", "https://westmanga.info", "id", overrideVersionCode = 1), - SingleLang("World Romance Translation", "https://wrt.my.id", "id", overrideVersionCode = 10), - SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 4), + SingleLang("Uzay Manga", "https://uzaymanga.com", "tr", overrideVersionCode = 6), + SingleLang("Walpurgi Scan", "https://www.walpurgiscan.it", "it", overrideVersionCode = 7, className = "WalpurgisScan", pkgName = "walpurgisscan"), + SingleLang("West Manga", "https://westmanga.org", "id", overrideVersionCode = 2), + SingleLang("World Romance Translation", "https://wrt.my.id", "id", overrideVersionCode = 11), + SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 5), + SingleLang("YumeKomik", "https://yumekomik.com", "id", isNsfw = true, className = "YumeKomik", pkgName = "inazumanga", overrideVersionCode = 6), + SingleLang("Zahard", "https://zahard.xyz", "en"), + SingleLang("أريا مانجا", "https://www.areascans.net", "ar", className = "AreaManga"), + SingleLang("فيكس مانجا", "https://vexmanga.com", "ar", className = "VexManga", overrideVersionCode = 2), SingleLang("สดใสเมะ", "https://www.xn--l3c0azab5a2gta.com", "th", isNsfw = true, className = "Sodsaime", overrideVersionCode = 1), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt index 418803902a..06bc01d826 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt @@ -8,22 +8,6 @@ class MCCMSGenerator : ThemeSourceGenerator { override val themePkg = "mccms" override val baseVersionCode = 6 override val sources = listOf( - SingleLang( - name = "Haoman6", - baseUrl = "https://www.haoman6.com", - lang = "zh", - className = "Haoman6", - sourceName = "好漫6", - overrideVersionCode = 3, - ), - SingleLang( // same as: www.haoman6.cc - name = "Haoman6 (g-lens)", - baseUrl = "https://www.g-lens.com", - lang = "zh", - className = "Haoman6glens", - sourceName = "好漫6 (g-lens)", - overrideVersionCode = 0, - ), SingleLang( name = "Kuaikuai Manhua 3", baseUrl = "https://mobile3.manhuaorg.com", diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/MMRCMSGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/MMRCMSGenerator.kt index b398c91c1f..a76701fd79 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/MMRCMSGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/MMRCMSGenerator.kt @@ -15,22 +15,18 @@ class MMRCMSGenerator : ThemeSourceGenerator { SingleLang("مانجا اون لاين", "https://onma.top", "ar", className = "onma"), SingleLang("Read Comics Online", "https://readcomicsonline.ru", "en"), SingleLang("Fallen Angels", "https://manga.fascans.com", "en", overrideVersionCode = 2), - SingleLang("Zahard", "https://zahard.xyz", "en", overrideVersionCode = 2), SingleLang("Scan FR", "https://www.scan-fr.org", "fr", overrideVersionCode = 2), SingleLang("Scan VF", "https://www.scan-vf.net", "fr", overrideVersionCode = 1), - SingleLang("Scan OP", "https://scan-op.cc", "fr"), SingleLang("Komikid", "https://www.komikid.com", "id"), SingleLang("Mangadoor", "https://mangadoor.com", "es", overrideVersionCode = 1), SingleLang("Utsukushii", "https://manga.utsukushii-bg.com", "bg", overrideVersionCode = 1), SingleLang("Phoenix-Scans", "https://phoenix-scans.pl", "pl", className = "PhoenixScans", overrideVersionCode = 1), - SingleLang("Scan-1", "https://wwv.scan-1.com", "fr", className = "ScanOne", overrideVersionCode = 2), - SingleLang("Lelscan-VF", "https://lelscanvf.com", "fr", className = "LelscanVF", overrideVersionCode = 1), + SingleLang("Lelscan-VF", "https://lelscanvf.cc", "fr", className = "LelscanVF", overrideVersionCode = 2), SingleLang("AnimaRegia", "https://animaregia.net", "pt-BR", overrideVersionCode = 4), SingleLang("MangaID", "https://mangaid.click", "id", overrideVersionCode = 1), - SingleLang("Jpmangas", "https://jpmangas.cc", "fr", overrideVersionCode = 1), - SingleLang("Op-VF", "https://www.op-vf.com", "fr", className = "OpVF"), - SingleLang("FR Scan", "https://frscan.ws", "fr", overrideVersionCode = 2), - SingleLang("Manga-FR", "https://manga-fr.me", "fr", className = "MangaFR"), + SingleLang("Jpmangas", "https://jpmangas.xyz", "fr", overrideVersionCode = 2), + SingleLang("Manga-FR", "https://manga-fr.cc", "fr", className = "MangaFR", overrideVersionCode = 2), + SingleLang("Manga-Scan", "https://manga-scan.me", "fr", className = "MangaScan", overrideVersionCode = 1), SingleLang("Ama Scans", "https://amascan.com", "pt-BR", isNsfw = true, overrideVersionCode = 2), // NOTE: THIS SOURCE CONTAINS A CUSTOM LANGUAGE SYSTEM (which will be ignored)! SingleLang("HentaiShark", "https://www.hentaishark.com", "all", isNsfw = true), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/SourceData.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/SourceData.kt index d80fd3330a..f9086825a0 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/SourceData.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mmrcms/SourceData.kt @@ -11,23 +11,20 @@ object SourceData { "https://zahard.xyz" -> """{"name":"Zahard","base_url":"https://zahard.xyz","supports_latest":true,"item_url":"https://zahard.xyz/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" "https://www.scan-fr.org" -> """{"name":"Scan FR","base_url":"https://www.scan-fr.org","supports_latest":true,"item_url":"https://www.scan-fr.org/manga/","categories":[{"id":"1","name":"Comedy"},{"id":"2","name":"Doujinshi"},{"id":"3","name":"Drama"},{"id":"4","name":"Ecchi"},{"id":"5","name":"Fantasy"},{"id":"6","name":"Gender Bender"},{"id":"7","name":"Josei"},{"id":"8","name":"Mature"},{"id":"9","name":"Mecha"},{"id":"10","name":"Mystery"},{"id":"11","name":"One Shot"},{"id":"12","name":"Psychological"},{"id":"13","name":"Romance"},{"id":"14","name":"School Life"},{"id":"15","name":"Sci-fi"},{"id":"16","name":"Seinen"},{"id":"17","name":"Shoujo"},{"id":"18","name":"Shoujo Ai"},{"id":"19","name":"Shounen"},{"id":"20","name":"Shounen Ai"},{"id":"21","name":"Slice of Life"},{"id":"22","name":"Sports"},{"id":"23","name":"Supernatural"},{"id":"24","name":"Tragedy"},{"id":"25","name":"Yaoi"},{"id":"26","name":"Yuri"},{"id":"27","name":"Comics"},{"id":"28","name":"Autre"},{"id":"29","name":"BD Occidentale"},{"id":"30","name":"Manhwa"},{"id":"31","name":"Action"},{"id":"32","name":"Aventure"}]}""" "https://www.scan-vf.net" -> """{"name":"Scan VF","base_url":"https://www.scan-vf.net","supports_latest":true,"item_url":"https://www.scan-vf.net/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" - "https://scan-op.cc" -> """{"name":"Scan OP","base_url":"https://scan-op.cc","supports_latest":true,"item_url":"https://scan-op.cc/manga/","categories":[{"id":"1","name":"Comedy"},{"id":"2","name":"Doujinshi"},{"id":"3","name":"Drama"},{"id":"4","name":"Ecchi"},{"id":"5","name":"Fantasy"},{"id":"6","name":"Gender Bender"},{"id":"7","name":"Josei"},{"id":"8","name":"Mature"},{"id":"9","name":"Mecha"},{"id":"10","name":"Mystery"},{"id":"11","name":"One Shot"},{"id":"12","name":"Psychological"},{"id":"13","name":"Romance"},{"id":"14","name":"School Life"},{"id":"15","name":"Sci-fi"},{"id":"16","name":"Seinen"},{"id":"17","name":"Shoujo"},{"id":"18","name":"Shoujo Ai"},{"id":"19","name":"Shounen"},{"id":"20","name":"Shounen Ai"},{"id":"21","name":"Slice of Life"},{"id":"22","name":"Sports"},{"id":"23","name":"Supernatural"},{"id":"24","name":"Tragedy"},{"id":"25","name":"Yaoi"},{"id":"26","name":"Yuri"},{"id":"27","name":"Comics"},{"id":"28","name":"Autre"}],"tags":[{"id":"nouveau","name":"nouveau"}]}""" "https://www.komikid.com" -> """{"name":"Komikid","base_url":"https://www.komikid.com","supports_latest":true,"item_url":"https://www.komikid.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Fantasy"},{"id":"7","name":"Gender Bender"},{"id":"8","name":"Historical"},{"id":"9","name":"Horror"},{"id":"10","name":"Josei"},{"id":"11","name":"Martial Arts"},{"id":"12","name":"Mature"},{"id":"13","name":"Mecha"},{"id":"14","name":"Mystery"},{"id":"15","name":"One Shot"},{"id":"16","name":"Psychological"},{"id":"17","name":"Romance"},{"id":"18","name":"School Life"},{"id":"19","name":"Sci-fi"},{"id":"20","name":"Seinen"},{"id":"21","name":"Shoujo"},{"id":"22","name":"Shoujo Ai"},{"id":"23","name":"Shounen"},{"id":"24","name":"Shounen Ai"},{"id":"25","name":"Slice of Life"},{"id":"26","name":"Sports"},{"id":"27","name":"Supernatural"},{"id":"28","name":"Tragedy"},{"id":"29","name":"Yaoi"},{"id":"30","name":"Yuri"}]}""" "http://azbivo.webd.pro" -> """{"name":"Nikushima","base_url":"http://azbivo.webd.pro","supports_latest":false,"item_url":"\u003chtml\u003e \n \u003chead\u003e \n \u003cmeta http-equiv\u003d\"Content-Language\" content\u003d\"pl\"\u003e \n \u003cmeta http-equiv name\u003d\"pragma\" content\u003d\"no-cache\"\u003e \n \u003clink href\u003d\"style/style.css\" rel\u003d\"stylesheet\" type\u003d\"text/css\"\u003e \n \u003cmeta http-equiv\u003d\"Refresh\" content\u003d\"0; url\u003dhttps://www.webd.pl/_errnda.php?utm_source\u003dwn07\u0026amp;utm_medium\u003dwww\u0026amp;utm_campaign\u003dblock\"\u003e \n \u003cmeta name\u003d\"Robots\" content\u003d\"index, follow\"\u003e \n \u003cmeta name\u003d\"revisit-after\" content\u003d\"2 days\"\u003e \n \u003cmeta name\u003d\"rating\" content\u003d\"general\"\u003e \n \u003cmeta name\u003d\"keywords\" content\u003d\"STRONA ZAWIESZONA, WEBD, DOMENY, DOMENA, HOSTING, SERWER, INTERNET, PHP, MySQL, FTP, WEBMASTER, SERWERY WIRTUALNE, WWW, KONTO, MAIL, POCZTA, E-MAIL, NET, .COM, .ORG, TANIE, PHP+MySQL, DOMENY, DOMENA, HOSTING, SERWER, INTERNET, PHP, MySQL, FTP, WEBMASTER, SERWERY WIRTUALNE, WWW, KONTO, MAIL, POCZTA, E-MAIL, DOMENY, DOMENA, NET, .COM, .ORG, TANIE, PHP+MySQL, HOSTING, SERWER, INTERNET, PHP, MySQL, FTP, WEBMASTER, SERWERY WIRTUALNE, WWW, KONTO, MAIL, POCZTA, E-MAIL, NET, .COM, .ORG, TANIE, PHP+MySQL\"\u003e \n \u003cmeta name\u003d\"description\" content\u003d\"STRONA ZAWIESZONA - Oferujemy profesjonalny hosting z PHP + MySQL, rejestrujemy domeny. Sprawdz nasz hosting i przetestuj nasze serwery. Kupuj tanio domeny i serwery!\"\u003e \n \u003ctitle\u003eSTRONA ZAWIESZONA - WEBD.PL - Tw�j profesjonalny hosting za jedyne 4.99PLN! Serwery z PHP+MySQL, tanie domeny, serwer + domena .pl - taniej sie nie da!\u003c/title\u003e \n \u003cscript type\u003d\"text/javascript\"\u003e\nfunction init() {\n if (!document.getElementById) return\n var imgOriginSrc;\n var imgTemp \u003d new Array();\n var imgarr \u003d document.getElementsByTagName(\u0027img\u0027);\n for (var i \u003d 0; i \u003c imgarr.length; i++) {\n if (imgarr[i].getAttribute(\u0027hsrc\u0027)) {\n imgTemp[i] \u003d new Image();\n imgTemp[i].src \u003d imgarr[i].getAttribute(\u0027hsrc\u0027);\n imgarr[i].onmouseover \u003d function() {\n imgOriginSrc \u003d this.getAttribute(\u0027src\u0027);\n this.setAttribute(\u0027src\u0027,this.getAttribute(\u0027hsrc\u0027))\n }\n imgarr[i].onmouseout \u003d function() {\n this.setAttribute(\u0027src\u0027,imgOriginSrc)\n }\n }\n }\n}\nonload\u003dinit;\n\u003c/script\u003e \n \u003c/head\u003e \n \u003cbody\u003e\n Trwa przekierowanie .... \u0026gt;\u0026gt;\u0026gt;\u0026gt; \u003c!--\n--\u003e \n \u003c/body\u003e\n\u003c/html\u003e/","categories":[]}""" "https://mangadoor.com" -> """{"name":"Mangadoor","base_url":"https://mangadoor.com","supports_latest":true,"item_url":"https://mangadoor.com/manga/","categories":[{"id":"1","name":"Acción"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comedia"},{"id":"4","name":"Drama"},{"id":"5","name":"Ecchi"},{"id":"6","name":"Fantasía"},{"id":"7","name":"Gender Bender"},{"id":"8","name":"Harem"},{"id":"9","name":"Histórico"},{"id":"10","name":"Horror"},{"id":"11","name":"Josei"},{"id":"12","name":"Artes Marciales"},{"id":"13","name":"Maduro"},{"id":"14","name":"Mecha"},{"id":"15","name":"Misterio"},{"id":"16","name":"One Shot"},{"id":"17","name":"Psicológico"},{"id":"18","name":"Romance"},{"id":"19","name":"Escolar"},{"id":"20","name":"Ciencia Ficción"},{"id":"21","name":"Seinen"},{"id":"22","name":"Shoujo"},{"id":"23","name":"Shoujo Ai"},{"id":"24","name":"Shounen"},{"id":"25","name":"Shounen Ai"},{"id":"26","name":"Recuentos de la vida"},{"id":"27","name":"Deportes"},{"id":"28","name":"Supernatural"},{"id":"29","name":"Tragedia"},{"id":"30","name":"Yaoi"},{"id":"31","name":"Yuri"},{"id":"32","name":"Demonios"},{"id":"33","name":"Juegos"},{"id":"34","name":"Policial"},{"id":"35","name":"Militar"},{"id":"36","name":"Thriller"},{"id":"37","name":"Autos"},{"id":"38","name":"Música"},{"id":"39","name":"Vampiros"},{"id":"40","name":"Magia"},{"id":"41","name":"Samurai"},{"id":"42","name":"Boys love"},{"id":"43","name":"Hentai"}]}""" "https://mangas.in" -> """{"name":"Mangas.pw","base_url":"https://mangas.in","supports_latest":true,"item_url":"https://mangas.in/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Hentai"},{"id":"34","name":"Smut"}]}""" "https://manga.utsukushii-bg.com" -> """{"name":"Utsukushii","base_url":"https://manga.utsukushii-bg.com","supports_latest":true,"item_url":"https://manga.utsukushii-bg.com/manga/","categories":[{"id":"1","name":"Екшън"},{"id":"2","name":"Приключенски"},{"id":"3","name":"Комедия"},{"id":"4","name":"Драма"},{"id":"5","name":"Фентъзи"},{"id":"6","name":"Исторически"},{"id":"7","name":"Ужаси"},{"id":"8","name":"Джосей"},{"id":"9","name":"Бойни изкуства"},{"id":"10","name":"Меха"},{"id":"11","name":"Мистерия"},{"id":"12","name":"Самостоятелна/Пилотна глава"},{"id":"13","name":"Психологически"},{"id":"14","name":"Романтика"},{"id":"15","name":"Училищни"},{"id":"16","name":"Научна фантастика"},{"id":"17","name":"Сейнен"},{"id":"18","name":"Шоджо"},{"id":"19","name":"Реализъм"},{"id":"20","name":"Спорт"},{"id":"21","name":"Свръхестествено"},{"id":"22","name":"Трагедия"},{"id":"23","name":"Йокаи"},{"id":"24","name":"Паралелна вселена"},{"id":"25","name":"Супер сили"},{"id":"26","name":"Пародия"},{"id":"27","name":"Шонен"}]}""" "https://phoenix-scans.pl" -> """{"name":"Phoenix-Scans","base_url":"https://phoenix-scans.pl","supports_latest":true,"item_url":"https://phoenix-scans.pl/manga/","categories":[{"id":"1","name":"Shounen"},{"id":"2","name":"Tragedia"},{"id":"3","name":"Szkolne życie"},{"id":"4","name":"Romans"},{"id":"5","name":"Zagadka"},{"id":"6","name":"Horror"},{"id":"7","name":"Dojrzałe"},{"id":"8","name":"Psychologiczne"},{"id":"9","name":"Przygodowe"},{"id":"10","name":"Akcja"},{"id":"11","name":"Komedia"},{"id":"12","name":"Zboczone"},{"id":"13","name":"Fantasy"},{"id":"14","name":"Harem"},{"id":"15","name":"Historyczne"},{"id":"16","name":"Manhua"},{"id":"17","name":"Manhwa"},{"id":"18","name":"Sztuki walki"},{"id":"19","name":"One shot"},{"id":"20","name":"Sci fi"},{"id":"21","name":"Seinen"},{"id":"22","name":"Shounen ai"},{"id":"23","name":"Spokojne życie"},{"id":"24","name":"Sport"},{"id":"25","name":"Nadprzyrodzone"},{"id":"26","name":"Webtoons"},{"id":"27","name":"Dramat"},{"id":"28","name":"Hentai"},{"id":"29","name":"Mecha"},{"id":"30","name":"Gender Bender"},{"id":"31","name":"Gry"},{"id":"32","name":"Yaoi"}],"tags":[{"id":"aktywne","name":"aktywne"},{"id":"zakonczone","name":"zakończone"},{"id":"porzucone","name":"porzucone"},{"id":"zawieszone","name":"zawieszone"},{"id":"zlicencjonowane","name":"zlicencjonowane"},{"id":"hentai","name":"Hentai"}]}""" - "https://wwv.scan-1.com" -> """{"name":"Scan-1","base_url":"https://wwv.scan-1.com","supports_latest":true,"item_url":"https://wwv.scan-1.com/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" - "https://lelscanvf.com" -> """{"name":"Lelscan-VF","base_url":"https://lelscanvf.com","supports_latest":true,"item_url":"https://lelscanvf.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" + "https://lelscanvf.cc" -> """{"name":"Lelscan-VF","base_url":"https://lelscanvf.cc","supports_latest":true,"item_url":"https://lelscanvf.cc/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" "https://animaregia.net" -> """{"name":"AnimaRegia","base_url":"https://animaregia.net","supports_latest":true,"item_url":"http://animaregia.net/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" "https://mangaid.click" -> """{"name":"MangaID","base_url":"https://mangaid.click","supports_latest":true,"item_url":"https://mangaid.click/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"Psychological"},{"id":"18","name":"Romance"},{"id":"19","name":"School Life"},{"id":"20","name":"Sci-fi"},{"id":"21","name":"Seinen"},{"id":"22","name":"Shoujo"},{"id":"23","name":"Shoujo Ai"},{"id":"24","name":"Shounen"},{"id":"25","name":"Shounen Ai"},{"id":"26","name":"Slice of Life"},{"id":"27","name":"Sports"},{"id":"28","name":"Supernatural"},{"id":"29","name":"Tragedy"},{"id":"30","name":"Yaoi"},{"id":"31","name":"Yuri"},{"id":"32","name":"School"},{"id":"33","name":"Isekai"},{"id":"34","name":"Military"}]}""" - "https://jpmangas.cc" -> """{"name":"Jpmangas","base_url":"https://jpmangas.cc","supports_latest":true,"item_url":"https://jpmangas.cc/lecture-en-ligne/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" - "https://www.op-vf.com" -> """{"name":"Op-VF","base_url":"https://www.op-vf.com","supports_latest":true,"item_url":"https://www.op-vf.com/manga/","categories":[]}""" - "https://frscan.ws" -> """{"name":"FR Scan","base_url":"https://frscan.ws","supports_latest":true,"item_url":"https://frscan.ws/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"Vie Scolaire"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Tranche de vie"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedie"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Autre"},{"id":"34","name":"BD Occidentale"},{"id":"35","name":"Webtoon"}]}""" + "https://jpmangas.xyz" -> """{"name":"Jpmangas","base_url":"https://jpmangas.xyz","supports_latest":true,"item_url":"https://jpmangas.xyz/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}]}""" "https://www.hentaishark.com" -> """{"name":"HentaiShark","base_url":"https://www.hentaishark.com","supports_latest":true,"item_url":"https://www.hentaishark.com/manga/","categories":[{"id":"1","name":"Doujinshi"},{"id":"2","name":"Manga"},{"id":"3","name":"Western"},{"id":"4","name":"non-h"},{"id":"5","name":"imageset"},{"id":"6","name":"artistcg"},{"id":"7","name":"misc"}]}""" "https://amascan.com" -> """{"name":"Ama Scans","base_url":"https://amascan.com","supports_latest":true,"item_url":"https://amascan.com/manga/","categories":[{"id":"1","name":"Ação"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comédia"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasia"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harém"},{"id":"10","name":"Histórico"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Artes Marciais"},{"id":"14","name":"Adulto"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mistério"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psicológico"},{"id":"19","name":"Romance"},{"id":"20","name":"Vida Escolar"},{"id":"21","name":"Ficcção Científica"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shounen"},{"id":"25","name":"Slice of Life"},{"id":"26","name":"Esportes"},{"id":"27","name":"Sobrenatural"},{"id":"28","name":"Tragédia"},{"id":"29","name":"Hentai"},{"id":"30","name":"Terror"},{"id":"31","name":"LGBTQQICAPF2K+"},{"id":"32","name":"Ação"},{"id":"33","name":"Aventura"},{"id":"34","name":"Comédia"},{"id":"35","name":"Doujinshi"},{"id":"36","name":"Drama"},{"id":"37","name":"Ecchi"},{"id":"38","name":"Fantasia"},{"id":"39","name":"Gender Bender"},{"id":"40","name":"Harém"},{"id":"41","name":"Histórico"},{"id":"42","name":"Horror"},{"id":"43","name":"Josei"},{"id":"44","name":"Artes Marciais"},{"id":"45","name":"Adulto"},{"id":"46","name":"Mecha"},{"id":"47","name":"Mistério"},{"id":"48","name":"One Shot"},{"id":"49","name":"Psicológico"},{"id":"50","name":"Romance"},{"id":"51","name":"Vida Escolar"},{"id":"52","name":"Ficcção Científica"},{"id":"53","name":"Seinen"},{"id":"54","name":"Shoujo"},{"id":"55","name":"Shounen"},{"id":"56","name":"Slice of Life"},{"id":"57","name":"Esportes"},{"id":"58","name":"Sobrenatural"},{"id":"59","name":"Tragédia"},{"id":"60","name":"Hentai"},{"id":"61","name":"Terror"},{"id":"62","name":"LGBTQQICAPF2K+"}]}""" - "https://manga-fr.me" -> """{"name":"Manga-FR","base_url":"https://manga-fr.me","supports_latest":true,"item_url":"https://manga-fr.me/lecture-en-ligne/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Aventure"},{"id":"3","name":"Comédie"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drame"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasie"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historique"},{"id":"11","name":"Horreur"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragédie"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Fantastique"},{"id":"34","name":"Webtoon"},{"id":"35","name":"Manhwa"},{"id":"36","name":"Amour"},{"id":"37","name":"Combats"},{"id":"38","name":"Amitié"},{"id":"39","name":"Psychologique"},{"id":"40","name":"Magie"}]}""" + "https://manga-fr.cc" -> """{"name":"Manga-FR","base_url":"https://manga-fr.cc","supports_latest":true,"item_url":"https://manga-fr.cc/lecture-en-ligne/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Aventure"},{"id":"3","name":"Comédie"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drame"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasie"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historique"},{"id":"11","name":"Horreur"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragédie"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Fantastique"},{"id":"34","name":"Webtoon"},{"id":"35","name":"Manhwa"},{"id":"36","name":"Amour"},{"id":"37","name":"Combats"},{"id":"38","name":"Amitié"},{"id":"39","name":"Psychologique"},{"id":"40","name":"Magie"}]}""" + "https://manga-scan.me" -> """{"name":"Manga-Scan","base_url":"https://manga-scan.me","supports_latest":true,"item_url":"https://manga-scan.me/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Aventure"},{"id":"3","name":"Comédie"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drame"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Webtoon"},{"id":"9","name":"Harem"},{"id":"10","name":"Historique"},{"id":"11","name":"Horreur"},{"id":"12","name":"Thriller"},{"id":"13","name":"Arts Martiaux"},{"id":"14","name":"Mature"},{"id":"15","name":"Tragique"},{"id":"16","name":"Mystère"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychologique"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Science-fiction"},{"id":"22","name":"Seinen"},{"id":"23","name":"Erotique"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sport"},{"id":"29","name":"Surnaturel"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Gangster"},{"id":"32","name":"Crime"},{"id":"33","name":"Biographique"},{"id":"34","name":"Fantastique"}]}""" else -> "" } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt index e3279947fd..9e296df95a 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt @@ -13,7 +13,7 @@ class ChanGenerator : ThemeSourceGenerator { override val sources = listOf( SingleLang("MangaChan", "https://manga-chan.me", "ru", overrideVersionCode = 14), - SingleLang("HenChan", "http://y.hchan.live", "ru", isNsfw = true, overrideVersionCode = 37), + SingleLang("HenChan", "https://xxxxx.hentaichan.live", "ru", isNsfw = true, overrideVersionCode = 38), SingleLang("YaoiChan", "https://yaoi-chan.me", "ru", isNsfw = true, overrideVersionCode = 4), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mymangacms/MyMangaCMSGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mymangacms/MyMangaCMSGenerator.kt index 553a4afaeb..87c3a92c29 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mymangacms/MyMangaCMSGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mymangacms/MyMangaCMSGenerator.kt @@ -18,7 +18,6 @@ class MyMangaCMSGenerator : ThemeSourceGenerator { "vi", overrideVersionCode = 9, ), - SingleLang("LKDTT", "https://lkdttee.com", "vi", true, overrideVersionCode = 4), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/readallcomics/ReadAllComicsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/readallcomics/ReadAllComicsGenerator.kt index d676412ca9..1b22c0bc40 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/readallcomics/ReadAllComicsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/readallcomics/ReadAllComicsGenerator.kt @@ -12,7 +12,7 @@ class ReadAllComicsGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 1 override val sources = listOf( - SingleLang("ReadAllComics", "https://readallcomics.com", "en", className = "ReadAllComicsCom"), + SingleLang("ReadAllComics", "https://readallcomics.com", "en", className = "ReadAllComicsCom", overrideVersionCode = 1), SingleLang("ReadAllManga", "https://readallmanga.com", "en"), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Dto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Dto.kt new file mode 100644 index 0000000000..61ae616905 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Dto.kt @@ -0,0 +1,128 @@ +package eu.kanade.tachiyomi.multisrc.senkuro + +import kotlinx.serialization.Serializable +@Serializable +data class PageWrapperDto( + val data: T, +) + +// Library Container +@Serializable +data class MangaTachiyomiSearchDto( + val mangaTachiyomiSearch: MangasDto, +) { + @Serializable + data class MangasDto( + val mangas: List, + ) +} + +// Manga Details +@Serializable +data class SubInfoDto( + val mangaTachiyomiInfo: MangaTachiyomiInfoDto, +) + +@Serializable +data class MangaTachiyomiInfoDto( + val id: String, + val slug: String, + val cover: SubImgDto? = null, + val status: String? = null, + val type: String? = null, + val rating: String? = null, + val formats: List? = null, + val genres: List? = null, + val tags: List? = null, + val titles: List, + val alternativeNames: List? = null, + val localizations: List? = null, + val mainStaff: List? = null, +) { + @Serializable + data class SubImgDto( + val original: ImgDto, + ) { + @Serializable + data class ImgDto( + val url: String? = null, + ) + } + + @Serializable + data class TagsDto( + val slug: String, + val titles: List, + ) + + @Serializable + data class TitleDto( + val lang: String, + val content: String, + ) + + @Serializable + data class LocalizationsDto( + val lang: String, + val description: String, + ) + + @Serializable + data class MainStaffDto( + val roles: List, + val person: PersonDto, + ) { + @Serializable + data class PersonDto( + val name: String, + ) + } +} + +// Chapters +@Serializable +data class MangaTachiyomiChaptersDto( + val mangaTachiyomiChapters: ChaptersMessage, +) { + @Serializable + data class ChaptersMessage( + val message: String? = null, + val chapters: List, + val teams: List, + ) { + @Serializable + data class BookDto( + val id: String, + val slug: String, + val branchId: String, + val name: String? = null, + val teamIds: List, + val number: String, + val volume: String, + val createdAt: String, + ) + + @Serializable + data class TeamsDto( + val id: String, + val slug: String, + val name: String, + ) + } +} + +// Chapter Pages +@Serializable +data class MangaTachiyomiChapterPages( + val mangaTachiyomiChapterPages: ChaptersPages, +) { + @Serializable + data class ChaptersPages( + val pages: List, + ) { + @Serializable + data class UrlDto( + val url: String, + ) + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Senkuro.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Senkuro.kt new file mode 100644 index 0000000000..227ea16a1c --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/Senkuro.kt @@ -0,0 +1,440 @@ +package eu.kanade.tachiyomi.multisrc.senkuro + +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +abstract class Senkuro( + override val name: String, + override val baseUrl: String, + final override val lang: String, +) : ConfigurableSource, HttpSource() { + + override val supportsLatest = false + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("User-Agent", "Tachiyomi (+https://github.com/tachiyomiorg/tachiyomi)") + .add("Content-Type", "application/json") + + override val client: OkHttpClient = + network.client.newBuilder() + .rateLimit(5) + .build() + + private inline fun T.toJsonRequestBody(): RequestBody = + json.encodeToString(this) + .toRequestBody(JSON_MEDIA_TYPE) + + // Popular + override fun popularMangaRequest(page: Int): Request { + val requestBody = GraphQL( + SEARCH_QUERY, + SearchVariables( + offset = offsetCount * (page - 1), + genre = SearchVariables.FiltersDto( + // Senkuro eternal built-in exclude 18+ filter + exclude = if (name == "Senkuro") { senkuroExcludeGenres } else { listOf() }, + ), + ), + ).toJsonRequestBody() + + fetchTachiyomiSearchFilters(page) + + return POST(API_URL, headers, requestBody) + } + override fun popularMangaParse(response: Response) = searchMangaParse(response) + + // Latest + override fun latestUpdatesRequest(page: Int): Request = throw NotImplementedError("Unused") + + override fun latestUpdatesParse(response: Response): MangasPage = throw NotImplementedError("Unused") + + // Search + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + fetchTachiyomiSearchFilters(page) // reset filters before sending searchMangaRequest + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val includeGenres = mutableListOf() + val excludeGenres = mutableListOf() + val includeTags = mutableListOf() + val excludeTags = mutableListOf() + val includeTypes = mutableListOf() + val excludeTypes = mutableListOf() + val includeFormats = mutableListOf() + val excludeFormats = mutableListOf() + val includeStatus = mutableListOf() + val excludeStatus = mutableListOf() + val includeTStatus = mutableListOf() + val excludeTStatus = mutableListOf() + val includeAges = mutableListOf() + val excludeAges = mutableListOf() + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is GenreList -> filter.state.forEach { genre -> + if (genre.state != Filter.TriState.STATE_IGNORE) { + if (genre.isIncluded()) includeGenres.add(genre.slug) else excludeGenres.add(genre.slug) + } + } + is TagList -> filter.state.forEach { tag -> + if (tag.state != Filter.TriState.STATE_IGNORE) { + if (tag.isIncluded()) includeTags.add(tag.slug) else excludeTags.add(tag.slug) + } + } + is TypeList -> filter.state.forEach { type -> + if (type.state != Filter.TriState.STATE_IGNORE) { + if (type.isIncluded()) includeTypes.add(type.slug) else excludeTypes.add(type.slug) + } + } + is FormatList -> filter.state.forEach { format -> + if (format.state != Filter.TriState.STATE_IGNORE) { + if (format.isIncluded()) includeFormats.add(format.slug) else excludeFormats.add(format.slug) + } + } + is StatList -> filter.state.forEach { stat -> + if (stat.state != Filter.TriState.STATE_IGNORE) { + if (stat.isIncluded()) includeStatus.add(stat.slug) else excludeStatus.add(stat.slug) + } + } + is StatTranslateList -> filter.state.forEach { tstat -> + if (tstat.state != Filter.TriState.STATE_IGNORE) { + if (tstat.isIncluded()) includeTStatus.add(tstat.slug) else excludeTStatus.add(tstat.slug) + } + } + is AgeList -> filter.state.forEach { age -> + if (age.state != Filter.TriState.STATE_IGNORE) { + if (age.isIncluded()) includeAges.add(age.slug) else excludeAges.add(age.slug) + } + } + else -> {} + } + } + + // Senkuro eternal built-in exclude 18+ filter + if (name == "Senkuro") { + excludeGenres.addAll(senkuroExcludeGenres) + } + + val requestBody = GraphQL( + SEARCH_QUERY, + SearchVariables( + query = query, offset = offsetCount * (page - 1), + genre = SearchVariables.FiltersDto( + includeGenres, + excludeGenres, + ), + tag = SearchVariables.FiltersDto( + includeTags, + excludeTags, + ), + type = SearchVariables.FiltersDto( + includeTypes, + excludeTypes, + ), + format = SearchVariables.FiltersDto( + includeFormats, + excludeFormats, + ), + status = SearchVariables.FiltersDto( + includeStatus, + excludeStatus, + ), + translationStatus = SearchVariables.FiltersDto( + includeTStatus, + excludeTStatus, + ), + rating = SearchVariables.FiltersDto( + includeAges, + excludeAges, + ), + ), + ).toJsonRequestBody() + + return POST(API_URL, headers, requestBody) + } + override fun searchMangaParse(response: Response): MangasPage { + val page = json.decodeFromString>>(response.body.string()) + val mangasList = page.data.mangaTachiyomiSearch.mangas.map { + it.toSManga() + } + + return MangasPage(mangasList, mangasList.isNotEmpty()) + } + + // Details + private fun parseStatus(status: String?): Int { + return when (status) { + "FINISHED" -> SManga.COMPLETED + "ONGOING" -> SManga.ONGOING + "HIATUS" -> SManga.ON_HIATUS + "ANNOUNCE" -> SManga.ONGOING + "CANCELLED" -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + } + + private fun MangaTachiyomiInfoDto.toSManga(): SManga { + val o = this + return SManga.create().apply { + title = titles.find { it.lang == "RU" }?.content ?: titles.find { it.lang == "EN" }?.content ?: titles[0].content + url = "$id,,$slug" // mangaId[0],,mangaSlug[1] + thumbnail_url = cover?.original?.url + var altName = alternativeNames?.joinToString(" / ") { it.content } + if (!altName.isNullOrEmpty()) { + altName = "Альтернативные названия:\n$altName\n\n" + } + author = mainStaff?.filter { it.roles.contains("STORY") }?.joinToString(", ") { it.person.name } + artist = mainStaff?.filter { it.roles.contains("ART") }?.joinToString(", ") { it.person.name } + description = altName + localizations?.find { it.lang == "RU" }?.description.orEmpty() + status = parseStatus(o.status) + genre = ( + getTypeList().find { it.slug == type }?.name + ", " + + getAgeList().find { it.slug == rating }?.name + ", " + + getFormatList().filter { formats.orEmpty().contains(it.slug) }.joinToString { it.name } + ", " + + genres?.joinToString { git -> git.titles.find { it.lang == "RU" }!!.content } + ", " + + tags?.joinToString { tit -> tit.titles.find { it.lang == "RU" }!!.content } + ).split(", ").filter { it.isNotEmpty() }.joinToString { it.trim().capitalize() } + } + } + + override fun mangaDetailsRequest(manga: SManga): Request { + val requestBody = GraphQL( + DETAILS_QUERY, + FetchDetailsVariables(mangaId = manga.url.split(",,")[0]), + ).toJsonRequestBody() + + return POST(API_URL, headers, requestBody) + } + + override fun mangaDetailsParse(response: Response): SManga { + val series = json.decodeFromString>(response.body.string()) + return series.data.mangaTachiyomiInfo.toSManga() + } + + override fun getMangaUrl(manga: SManga) = baseUrl + "/manga/" + manga.url.split(",,")[1] + + // Chapters + private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.ROOT) } + + private fun parseDate(date: String?): Long { + date ?: return 0L + return try { + simpleDateFormat.parse(date)!!.time + } catch (_: Exception) { + Date().time + } + } + + override fun fetchChapterList(manga: SManga): Observable> { + return client.newCall(chapterListRequest(manga)) + .asObservableSuccess() + .map { response -> + chapterListParse(response, manga) + } + } + override fun chapterListParse(response: Response) = throw UnsupportedOperationException("chapterListParse(response: Response, manga: SManga)") + private fun chapterListParse(response: Response, manga: SManga): List { + val chaptersList = json.decodeFromString>(response.body.string()) + val teamsList = chaptersList.data.mangaTachiyomiChapters.teams + return chaptersList.data.mangaTachiyomiChapters.chapters.map { chapter -> + SChapter.create().apply { + chapter_number = chapter.number.toFloatOrNull() ?: -2F + name = "${chapter.volume}. Глава ${chapter.number} " + (chapter.name ?: "") + url = "${manga.url},,${chapter.id},,${chapter.slug}" // mangaId[0],,mangaSlug[1],,chapterId[2],,chapterSlug[3] + date_upload = parseDate(chapter.createdAt) + scanlator = teamsList.filter { chapter.teamIds.contains(it.id) }.joinToString { it.name } + } + } + } + override fun chapterListRequest(manga: SManga): Request { + val requestBody = GraphQL( + CHAPTERS_QUERY, + FetchDetailsVariables(mangaId = manga.url.split(",,")[0]), + ).toJsonRequestBody() + + return POST(API_URL, headers, requestBody) + } + + // Pages + override fun pageListRequest(chapter: SChapter): Request { + val mangaChapterId = chapter.url.split(",,") + val requestBody = GraphQL( + CHAPTERS_PAGES_QUERY, + FetchChapterPagesVariables(mangaId = mangaChapterId[0], chapterId = mangaChapterId[2]), + ).toJsonRequestBody() + + return POST(API_URL, headers, requestBody) + } + + override fun getChapterUrl(chapter: SChapter): String { + val mangaChapterSlug = chapter.url.split(",,") + return baseUrl + "/manga/" + mangaChapterSlug[1] + "/chapters/" + mangaChapterSlug[3] + } + + override fun pageListParse(response: Response): List { + val imageList = json.decodeFromString>(response.body.string()) + return imageList.data.mangaTachiyomiChapterPages.pages.mapIndexed { index, page -> + Page(index, "", page.url) + } + } + + override fun imageUrlRequest(page: Page): Request = throw NotImplementedError("Unused") + + override fun imageUrlParse(response: Response): String = throw NotImplementedError("Unused") + + override fun fetchImageUrl(page: Page): Observable { + return Observable.just(page.url) + } + + // Filters + // Filters are fetched immediately once an extension loads + // We're only able to get filters after a loading the manga directory, and resetting + // the filters is the only thing that seems to reinflate the view + private fun fetchTachiyomiSearchFilters(pageRequest: Int) { + // The function must be used in PopularMangaRequest and fetchSearchManga to correctly/guaranteed reset the selected filters! + if (pageRequest == 1) { + val responseBody = client.newCall( + POST( + API_URL, + headers, + GraphQL( + FILTERS_QUERY, + SearchVariables(), + ).toJsonRequestBody(), + ), + ).execute().body.string() + + val filterDto = + json.decodeFromString>(responseBody).data.mangaTachiyomiSearchFilters + + genresList = + filterDto.genres.filterNot { name == "Senkuro" && senkuroExcludeGenres.contains(it.slug) } + .map { genre -> + FilterersTri( + genre.titles.find { it.lang == "RU" }!!.content.capitalize(), + genre.slug, + ) + } + + tagsList = filterDto.tags.map { tag -> + FilterersTri( + tag.titles.find { it.lang == "RU" }!!.content.capitalize(), + tag.slug, + ) + } + } + } + override fun getFilterList(): FilterList { + val filters = mutableListOf>() + filters += if (genresList.isEmpty() or tagsList.isEmpty()) { + listOf( + Filter.Separator(), + Filter.Header("Нажмите «Сбросить», чтобы загрузить все фильтры"), + Filter.Separator(), + ) + } else { + listOf( + GenreList(genresList), + TagList(tagsList), + ) + } + filters += listOf( + TypeList(getTypeList()), + FormatList(getFormatList()), + StatList(getStatList()), + StatTranslateList(getStatTranslateList()), + AgeList(getAgeList()), + ) + return FilterList(filters) + } + + private class FilterersTri(name: String, val slug: String) : Filter.TriState(name) + private class GenreList(genres: List) : Filter.Group("Жанры", genres) + private class TagList(tags: List) : Filter.Group("Тэги", tags) + private class TypeList(types: List) : Filter.Group("Тип", types) + private class FormatList(formats: List) : Filter.Group("Формат", formats) + private class StatList(status: List) : Filter.Group("Статус", status) + private class StatTranslateList(tstatus: List) : Filter.Group("Статус перевода", tstatus) + private class AgeList(ages: List) : Filter.Group("Возрастное ограничение", ages) + + private var genresList: List = listOf() + private var tagsList: List = listOf() + + private fun getTypeList() = listOf( + FilterersTri("Манга", "MANGA"), + FilterersTri("Манхва", "MANHWA"), + FilterersTri("Маньхуа", "MANHUA"), + FilterersTri("Комикс", "COMICS"), + FilterersTri("OEL Манга", "OEL_MANGA"), + FilterersTri("РуМанга", "RU_MANGA"), + ) + private fun getStatList() = listOf( + FilterersTri("Анонс", "ANNOUNCE"), + FilterersTri("Онгоинг", "ONGOING"), + FilterersTri("Выпущено", "FINISHED"), + FilterersTri("Приостановлено", "HIATUS"), + FilterersTri("Отменено", "CANCELLED"), + ) + + private fun getStatTranslateList() = listOf( + FilterersTri("Переводится", "IN_PROGRESS"), + FilterersTri("Завершён", "FINISHED"), + FilterersTri("Заморожен", "FROZEN"), + FilterersTri("Заброшен", "ABANDONED"), + ) + + private fun getAgeList() = listOf( + FilterersTri("0+", "GENERAL"), + FilterersTri("12+", "SENSITIVE"), + FilterersTri("16+", "QUESTIONABLE"), + FilterersTri("18+", "EXPLICIT"), + ) + private fun getFormatList() = listOf( + FilterersTri("Сборник", "DIGEST"), + FilterersTri("Додзинси", "DOUJINSHI"), + FilterersTri("В цвете", "IN_COLOR"), + FilterersTri("Сингл", "SINGLE"), + FilterersTri("Веб", "WEB"), + FilterersTri("Вебтун", "WEBTOON"), + FilterersTri("Ёнкома", "YONKOMA"), + FilterersTri("Short", "SHORT"), + ) + + companion object { + private const val offsetCount = 20 + private const val API_URL = "https://api.senkuro.com/graphql" + private val senkuroExcludeGenres = listOf("hentai", "yaoi", "yuri", "shoujo_ai", "shounen_ai") + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + } + + private val json: Json by injectLazy() +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroGenerator.kt new file mode 100644 index 0000000000..54c6107013 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroGenerator.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.multisrc.senkuro + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class SenkuroGenerator : ThemeSourceGenerator { + + override val themePkg = "senkuro" + + override val themeClass = "Senkuro" + + override val baseVersionCode = 2 + + override val sources = listOf( + SingleLang("Senkuro", "https://senkuro.com", "ru", overrideVersionCode = 0), + SingleLang("Senkognito", "https://senkognito.com", "ru", isNsfw = true, overrideVersionCode = 0), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + SenkuroGenerator().createAll() + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroQueries.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroQueries.kt new file mode 100644 index 0000000000..3a0078e5b3 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/senkuro/SenkuroQueries.kt @@ -0,0 +1,244 @@ +package eu.kanade.tachiyomi.multisrc.senkuro + +import kotlinx.serialization.Serializable + +@Serializable +data class GraphQL( + val query: String, + val variables: T, +) + +private fun buildQuery(queryAction: () -> String): String { + return queryAction() + .trimIndent() + .replace("%", "$") +} + +@Serializable +data class SearchVariables( + val query: String? = null, + val type: FiltersDto? = null, + val status: FiltersDto? = null, + val translationStatus: FiltersDto? = null, + val genre: FiltersDto? = null, + val tag: FiltersDto? = null, + val format: FiltersDto? = null, + val rating: FiltersDto? = null, + val offset: Int? = null, +) { + @Serializable + data class FiltersDto( + val include: List? = null, + val exclude: List? = null, + ) +} + +val SEARCH_QUERY: String = buildQuery { + """ + query searchTachiyomiManga( + %query: String, + %type: MangaTachiyomiSearchTypeFilter, + %status: MangaTachiyomiSearchStatusFilter, + %translationStatus: MangaTachiyomiSearchTranslationStatusFilter, + %genre: MangaTachiyomiSearchGenreFilter, + %tag: MangaTachiyomiSearchTagFilter, + %format: MangaTachiyomiSearchGenreFilter, + %rating: MangaTachiyomiSearchTagFilter, + %offset: Int, + ) { + mangaTachiyomiSearch( + query:%query, + type: %type, + status: %status, + translationStatus: %translationStatus, + genre: %genre, + tag: %tag, + format: %format, + rating: %rating, + offset: %offset, + ) { + mangas { + id + slug + originalName { + lang + content + } + titles { + lang + content + } + alternativeNames { + lang + content + } + cover { + original { + url + } + } + } + } + } + """ +} + +@Serializable +data class FetchDetailsVariables( + val mangaId: String? = null, +) + +val DETAILS_QUERY: String = buildQuery { + """ + query fetchTachiyomiManga(%mangaId: ID!) { + mangaTachiyomiInfo(mangaId: %mangaId) { + id + slug + originalName { + lang + content + } + titles { + lang + content + } + alternativeNames { + lang + content + } + localizations { + lang + description + } + type + rating + status + formats + genres { + slug + titles { + lang + content + } + } + tags { + slug + titles { + lang + content + } + } + translationStatus + cover { + original { + url + } + } + mainStaff { + roles + person { + name + } + } + } + } + """ +} + +val CHAPTERS_QUERY: String = buildQuery { + """ + query fetchTachiyomiChapters(%mangaId: ID!) { + mangaTachiyomiChapters(mangaId: %mangaId) { + message + chapters { + id + slug + branchId + name + teamIds + number + volume + createdAt + } + teams { + id + slug + name + } + } + } + + """ +} + +@Serializable +data class FetchChapterPagesVariables( + val mangaId: String? = null, + val chapterId: String? = null, +) + +val CHAPTERS_PAGES_QUERY: String = buildQuery { + """ + query fetchTachiyomiChapterPages( + %mangaId: ID!, + %chapterId: ID! + ) { + mangaTachiyomiChapterPages( + mangaId: %mangaId, + chapterId: %chapterId + ) { + pages { + url + } + } + } + """ +} + +@Serializable +data class MangaTachiyomiSearchFilters( + val mangaTachiyomiSearchFilters: FilterDto, +) { + @Serializable + data class FilterDto( + val genres: List, + val tags: List, + ) { + @Serializable + data class FilterDataDto( + val slug: String, + val titles: List, + ) { + @Serializable + data class TitleDto( + val lang: String, + val content: String, + ) + } + } +} + +val FILTERS_QUERY: String = buildQuery { + """ + query fetchTachiyomiSearchFilters { + mangaTachiyomiSearchFilters { + genres { + id + slug + titles { + lang + content + } + } + tags { + id + slug + titles { + lang + content + } + } + } + } + """ +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMH.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMH.kt index aa7e2e1b34..aa605aa90a 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMH.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMH.kt @@ -186,7 +186,7 @@ abstract class SinMH( protected open val imageHost: String by lazy { client.newCall(GET("$baseUrl/js/config.js", headers)).execute().let { - Regex("""resHost:.+?"domain":\["(.+?)"""").find(it.body.string())!! + Regex("""resHost:.+?"?domain"?:\["(.+?)"""").find(it.body.string())!! .groupValues[1].substringAfter(':').run { "https:$this" } } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMHGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMHGenerator.kt index 133a2782b5..83d7d7d461 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMHGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/sinmh/SinMHGenerator.kt @@ -6,7 +6,7 @@ import generator.ThemeSourceGenerator class SinMHGenerator : ThemeSourceGenerator { override val themeClass = "SinMH" override val themePkg = "sinmh" - override val baseVersionCode = 9 + override val baseVersionCode = 10 override val sources = listOf( SingleLang( name = "Gufeng Manhua", @@ -35,11 +35,11 @@ class SinMHGenerator : ThemeSourceGenerator { ), SingleLang( name = "Qinqin Manhua", - baseUrl = "https://www.acgqd.com", + baseUrl = "https://www.acgud.com", lang = "zh", className = "Qinqin", sourceName = "亲亲漫画", - overrideVersionCode = 1, + overrideVersionCode = 2, ), SingleLang( name = "57Manhua", diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt index 686ca6ed83..6cc4487ee3 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt @@ -196,7 +196,8 @@ open class Webtoons( open fun parseDetailsThumbnail(document: Document): String? { val picElement = document.select("#content > div.cont_box > div.detail_body") val discoverPic = document.select("#content > div.cont_box > div.detail_header > span.thmb") - return discoverPic.select("img").not("[alt='Representative image']").first()?.attr("src") ?: picElement.attr("style").substringAfter("url(")?.substringBeforeLast(")") + return picElement.attr("style").substringAfter("url(").substringBeforeLast(")") + .ifBlank { discoverPic.select("img").not("[alt='Representative image']").first()?.attr("src") } } override fun mangaDetailsParse(document: Document): SManga { @@ -206,7 +207,9 @@ open class Webtoons( val manga = SManga.create() manga.title = document.selectFirst("h1.subj, h3.subj")!!.text() manga.author = detailElement.select(".author:nth-of-type(1)").first()?.ownText() - manga.artist = detailElement.select(".author:nth-of-type(2)").first()?.ownText() ?: manga.author + ?: detailElement.select(".author_area").first()?.ownText() + manga.artist = detailElement.select(".author:nth-of-type(2)").first()?.ownText() + ?: detailElement.select(".author_area").first()?.ownText() ?: manga.author manga.genre = detailElement.select(".genre").joinToString(", ") { it.text() } manga.description = infoElement.select("p.summary").text() manga.status = infoElement.select("p.day_info").firstOrNull()?.text().orEmpty().toStatus() @@ -214,7 +217,7 @@ open class Webtoons( return manga } - private fun String.toStatus(): Int = when { + open fun String.toStatus(): Int = when { contains("UP") -> SManga.ONGOING contains("COMPLETED") -> SManga.COMPLETED else -> SManga.UNKNOWN diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt index e100a95600..dfd0b56643 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsGenerator.kt @@ -13,7 +13,7 @@ class WebtoonsGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 2 override val sources = listOf( - MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh-Hant", "de"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 37), + MultiLang("Webtoons.com", "https://www.webtoons.com", listOf("en", "fr", "es", "id", "th", "zh-Hant", "de"), className = "WebtoonsFactory", pkgName = "webtoons", overrideVersionCode = 39), SingleLang("Dongman Manhua", "https://www.dongmanmanhua.cn", "zh"), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpcomics/WPComicsGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpcomics/WPComicsGenerator.kt index e7b22f86f4..1e6153e083 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpcomics/WPComicsGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpcomics/WPComicsGenerator.kt @@ -12,8 +12,8 @@ class WPComicsGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 2 override val sources = listOf( - SingleLang("NetTruyen", "https://www.nettruyenmax.com", "vi", overrideVersionCode = 18), - SingleLang("NhatTruyen", "https://nhattruyenmin.com", "vi", overrideVersionCode = 11), + SingleLang("NetTruyen", "https://www.nettruyenus.com", "vi", overrideVersionCode = 19), + SingleLang("NhatTruyen", "https://nhattruyenplus.com", "vi", overrideVersionCode = 12), SingleLang("TruyenChon", "http://truyenchon.com", "vi", overrideVersionCode = 3), SingleLang("XOXO Comics", "https://xoxocomics.net", "en", className = "XoxoComics", overrideVersionCode = 2), ) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt index 42334dfa75..fa9551926c 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistManga.kt @@ -7,159 +7,73 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document -import org.jsoup.nodes.Element import uy.kohesive.injekt.injectLazy abstract class ZeistManga( override val name: String, override val baseUrl: String, override val lang: String, -) : ParsedHttpSource() { +) : HttpSource() { - override val supportsLatest = false - open val hasFilters = false - protected val json: Json by injectLazy() - protected val intl by lazy { ZeistMangaIntl(lang) } + override val supportsLatest = true - open val chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex() - open val scriptSelector = "#clwd > script" + private val json: Json by injectLazy() - open val oldChapterFeedRegex = """([^']+)\?""".toRegex() - open val oldScriptSelector = "#myUL > script" + private val intl by lazy { ZeistMangaIntl(lang) } - open val pageListSelector = "div.check-box div.separator" + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Referer", "$baseUrl/") - open fun getApiUrl(doc: Document): String { - val script = doc.selectFirst(scriptSelector) - - if (script == null) { - val altScript = doc.selectFirst(oldScriptSelector)!!.attr("src") - val feed = oldChapterFeedRegex - .find(altScript) - ?.groupValues?.get(1) - ?: throw Exception("Failed to find chapter feed") + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - return "$baseUrl$feed?alt=json&start-index=2&max-results=999999" - } - - val feed = chapterFeedRegex - .find(script.html()) - ?.groupValues?.get(1) - ?: throw Exception("Failed to find chapter feed") - - return apiUrl("Chapter") - .addPathSegments(feed) - .addQueryParameter("max-results", "999999") // Get all chapters - .build().toString() - } + protected open val popularMangaSelector = "div.PopularPosts div.grid > figure" + protected open val popularMangaSelectorTitle = "figcaption > a" + protected open val popularMangaSelectorUrl = "figcaption > a" - override fun chapterListParse(response: Response): List { + override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - - val url = getApiUrl(document) - - val req = GET(url, headers) - val res = client.newCall(req).execute() - - val jsonString = res.body.string() - val result = json.decodeFromString(jsonString) - - return result.feed?.entry?.map { it.toSChapter(baseUrl) } - ?: throw Exception("Failed to parse from chapter API") - } - - override fun imageUrlParse(document: Document): String { - throw UnsupportedOperationException("Not used.") - } - - override fun latestUpdatesParse(response: Response): MangasPage { - throw UnsupportedOperationException("Not used.") + val mangas = document.select(popularMangaSelector).map { element -> + SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst(popularMangaSelectorTitle)!!.text() + setUrlWithoutDomain(element.selectFirst(popularMangaSelectorUrl)!!.attr("href")) + } + } + return MangasPage(mangas, false) } override fun latestUpdatesRequest(page: Int): Request { - throw UnsupportedOperationException("Not used.") - } - - override fun chapterFromElement(element: Element): SChapter { - throw UnsupportedOperationException("Not used.") - } - - override fun chapterListSelector(): String { - throw UnsupportedOperationException("Not used.") - } - - override fun latestUpdatesFromElement(element: Element): SManga { - throw UnsupportedOperationException("Not used.") - } - - override fun latestUpdatesNextPageSelector(): String? { - throw UnsupportedOperationException("Not used.") - } - - override fun latestUpdatesSelector(): String { - throw UnsupportedOperationException("Not used.") - } - - override fun popularMangaFromElement(element: Element): SManga { - throw UnsupportedOperationException("Not used.") - } - - override fun popularMangaNextPageSelector(): String? { - throw UnsupportedOperationException("Not used.") - } - - override fun popularMangaSelector(): String { - throw UnsupportedOperationException("Not used.") - } - - override fun searchMangaFromElement(element: Element): SManga { - throw UnsupportedOperationException("Not used.") - } - - override fun searchMangaNextPageSelector(): String? { - throw UnsupportedOperationException("Not used.") - } - - override fun searchMangaSelector(): String { - throw UnsupportedOperationException("Not used.") - } - - override fun mangaDetailsParse(document: Document): SManga { - val profileManga = document.selectFirst(".grid.gtc-235fr")!! - return SManga.create().apply { - thumbnail_url = profileManga.selectFirst("img")!!.attr("src") - description = profileManga.select("#synopsis").text() - genre = profileManga.select("div.mt-15 > a[rel=tag]") - .joinToString { it.text() } - } - } + val startIndex = maxMangaResults * (page - 1) + 1 + val url = apiUrl() + .addQueryParameter("orderby", "published") + .addQueryParameter("max-results", (maxMangaResults + 1).toString()) + .addQueryParameter("start-index", startIndex.toString()) + .build() - override fun pageListParse(document: Document): List { - val images = document.select(pageListSelector) - return images.select("img[src]").mapIndexed { i, img -> - Page(i, "", img.attr("src")) - } + return GET(url, headers) } - override fun popularMangaParse(response: Response): MangasPage { + override fun latestUpdatesParse(response: Response): MangasPage { val jsonString = response.body.string() val result = json.decodeFromString(jsonString) val mangas = result.feed?.entry.orEmpty() - .filter { !it.category.orEmpty().any { category -> category.term == "Anime" } } // Skip animes + .filter { it.category.orEmpty().any { category -> category.term == "Series" } } + .filter { !it.category.orEmpty().any { category -> category.term == "Anime" } } .map { it.toSManga(baseUrl) } val mangalist = mangas.toMutableList() - if (mangas.size == maxResults + 1) { + if (mangas.size == maxMangaResults + 1) { mangalist.removeLast() return MangasPage(mangalist, true) } @@ -167,28 +81,16 @@ abstract class ZeistManga( return MangasPage(mangalist, false) } - override fun popularMangaRequest(page: Int): Request { - val startIndex = maxResults * (page - 1) + 1 - val url = apiUrl() - .addQueryParameter("orderby", "published") - .addQueryParameter("max-results", (maxResults + 1).toString()) - .addQueryParameter("start-index", startIndex.toString()) - .build() - - return GET(url, headers) - } - - override fun searchMangaParse(response: Response) = popularMangaParse(response) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val startIndex = maxResults * (page - 1) + 1 + val startIndex = maxMangaResults * (page - 1) + 1 val url = apiUrl() - .addQueryParameter("max-results", (maxResults + 1).toString()) + .addQueryParameter("max-results", (maxMangaResults + 1).toString()) .addQueryParameter("start-index", startIndex.toString()) if (query.isNotBlank()) { url.addQueryParameter("q", query) - return GET(url.build(), headers) + val searchUrl = url.build().toString().replaceLast("q=", "q=label:$mangaCategory+") + return GET(searchUrl, headers) } filters.forEach { filter -> @@ -221,28 +123,190 @@ abstract class ZeistManga( return GET(url.build(), headers) } - open fun apiUrl(feed: String = "Series"): HttpUrl.Builder { + override fun searchMangaParse(response: Response) = latestUpdatesParse(response) + + protected open val statusSelectorList = listOf( + "Status", + "Estado", + ) + + protected open val authorSelectorList = listOf( + "Author", + "Autor", + "الكاتب", + "Yazar", + ) + + protected open val artisSelectorList = listOf( + "Artist", + "Artista", + "الرسام", + "Çizer", + ) + + protected open val mangaDetailsSelector = ".grid.gtc-235fr" + protected open val mangaDetailsSelectorDescription = "#synopsis" + protected open val mangaDetailsSelectorGenres = "div.mt-15 > a[rel=tag]" + protected open val mangaDetailsSelectorInfo = ".y6x11p" + protected open val mangaDetailsSelectorInfoTitle = "strong" + protected open val mangaDetailsSelectorInfoDescription = "span.dt" + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val profileManga = document.selectFirst(mangaDetailsSelector)!! + return SManga.create().apply { + thumbnail_url = profileManga.selectFirst("img")!!.attr("abs:src") + description = profileManga.select(mangaDetailsSelectorDescription).text() + genre = profileManga.select(mangaDetailsSelectorGenres) + .joinToString { it.text() } + + val infoElement = profileManga.select(mangaDetailsSelectorInfo) + infoElement.forEach { element -> + val infoText = element.ownText().trim().ifEmpty { element.selectFirst(mangaDetailsSelectorInfoTitle)?.text()?.trim() ?: "" } + val descText = element.select(mangaDetailsSelectorInfoDescription).text().trim() + when { + statusSelectorList.any { infoText.contains(it) } -> { + status = parseStatus(descText) + } + + authorSelectorList.any { infoText.contains(it) } -> { + author = descText + } + + artisSelectorList.any { infoText.contains(it) } -> { + artist = descText + } + } + } + } + } + + protected open val chapterCategory: String = "Chapter" + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + val url = getChapterFeedUrl(document) + + val req = GET(url, headers) + val res = client.newCall(req).execute() + + val jsonString = res.body.string() + val result = json.decodeFromString(jsonString) + + return result.feed?.entry?.filter { it.category.orEmpty().any { category -> category.term == chapterCategory } } + ?.map { it.toSChapter(baseUrl) } + ?: throw Exception("Failed to parse from chapter API") + } + + protected open val useNewChapterFeed = false + protected open val useOldChapterFeed = false + + private val chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex() + private val scriptSelector = "#clwd > script" + + open fun getChapterFeedUrl(doc: Document): String { + if (useNewChapterFeed) return newChapterFeedUrl(doc) + if (useOldChapterFeed) return oldChapterFeedUrl(doc) + + val script = doc.selectFirst(scriptSelector) + ?: return runCatching { oldChapterFeedUrl(doc) } + .getOrElse { newChapterFeedUrl(doc) } + + val feed = chapterFeedRegex + .find(script.html()) + ?.groupValues?.get(1) + ?: throw Exception("Failed to find chapter feed") + + return apiUrl(chapterCategory) + .addPathSegments(feed) + .addQueryParameter("max-results", maxChapterResults.toString()) + .build().toString() + } + + private val oldChapterFeedRegex = """([^']+)\?""".toRegex() + private val oldScriptSelector = "#myUL > script" + + open fun oldChapterFeedUrl(doc: Document): String { + val script = doc.selectFirst(oldScriptSelector)!!.attr("src") + val feed = oldChapterFeedRegex + .find(script) + ?.groupValues?.get(1) + ?: throw Exception("Failed to find chapter feed") + + return "$baseUrl$feed?alt=json&start-index=1&max-results=$maxChapterResults" + } + + private val newChapterFeedRegex = """label\s*=\s*'([^']+)'""".toRegex() + private val newScriptSelector = "#latest > script" + + private fun newChapterFeedUrl(doc: Document): String { + var chapterRegex = chapterFeedRegex + var script = doc.selectFirst(scriptSelector) + + if (script == null) { + script = doc.selectFirst(newScriptSelector)!! + chapterRegex = newChapterFeedRegex + } + + val feed = chapterRegex + .find(script.html()) + ?.groupValues?.get(1) + ?: throw Exception("Failed to find chapter feed") + + val url = apiUrl(feed) + .addQueryParameter("start-index", "1") + .addQueryParameter("max-results", "999999") + .build() + + return url.toString() + } + + protected open val pageListSelector = "div.check-box div.separator" + + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + val images = document.select(pageListSelector) + return images.select("img[src]").mapIndexed { i, img -> + Page(i, "", img.attr("abs:src")) + } + } + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.") + + protected open val mangaCategory: String = "Series" + + open fun apiUrl(feed: String = mangaCategory): HttpUrl.Builder { return "$baseUrl/feeds/posts/default/-/".toHttpUrl().newBuilder() .addPathSegment(feed) .addQueryParameter("alt", "json") } + protected open val hasFilters = false + + protected open val hasStatusFilter = true + protected open val hasTypeFilter = true + protected open val hasLanguageFilter = true + protected open val hasGenreFilter = true + override fun getFilterList(): FilterList { + val filterList = mutableListOf>() + if (!hasFilters) { return FilterList(emptyList()) } - return FilterList( - Filter.Header(intl.filterWarning), - Filter.Separator(), - StatusList(intl.statusFilterTitle, getStatusList()), - TypeList(intl.typeFilterTitle, getTypeList()), - LanguageList(intl.languageFilterTitle, getLanguageList()), - GenreList(intl.genreFilterTitle, getGenreList()), - ) + filterList.add(Filter.Header(intl.filterWarning)) + filterList.add(Filter.Separator()) + + if (hasStatusFilter) filterList.add(StatusList(intl.statusFilterTitle, getStatusList())) + if (hasTypeFilter) filterList.add(TypeList(intl.typeFilterTitle, getTypeList())) + if (hasLanguageFilter) filterList.add(LanguageList(intl.languageFilterTitle, getLanguageList())) + if (hasGenreFilter) filterList.add(GenreList(intl.genreFilterTitle, getGenreList())) + + return FilterList(filterList) } - // Theme Default Status protected open fun getStatusList(): List = listOf( Status(intl.all, ""), Status(intl.statusOngoing, "Ongoing"), @@ -253,7 +317,6 @@ abstract class ZeistManga( Status(intl.statusCancelled, "Cancelled"), ) - // Theme Default Types protected open fun getTypeList(): List = listOf( Type(intl.all, ""), Type(intl.typeManga, "Manga"), @@ -266,7 +329,6 @@ abstract class ZeistManga( Type(intl.typeDoujinshi, "Doujinshi"), ) - // Theme Default Genres protected open fun getGenreList(): List = listOf( Genre("Action", "Action"), Genre("Adventurer", "Adventurer"), @@ -308,14 +370,56 @@ abstract class ZeistManga( Genre("Yuri", "Yuri"), ) - // Theme Default Languages protected open fun getLanguageList(): List = listOf( Language(intl.all, ""), Language("Indonesian", "Indonesian"), Language("English", "English"), ) + protected open val statusOnGoingList = listOf( + "ongoing", + "en curso", + "ativo", + "lançando", + "مستمر", + ) + + protected open val statusCompletedList = listOf( + "completed", + "completo", + ) + + protected open val statusHiatusList = listOf( + "hiatus", + ) + + protected open val statusCancelledList = listOf( + "cancelled", + "dropped", + "dropado", + "abandonado", + "cancelado", + ) + + protected open fun parseStatus(element: String): Int = when (element.lowercase().trim()) { + in statusOnGoingList -> SManga.ONGOING + in statusCompletedList -> SManga.COMPLETED + in statusHiatusList -> SManga.ON_HIATUS + in statusCancelledList -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + + private fun String.replaceLast(oldValue: String, newValue: String): String { + val lastIndexOf = lastIndexOf(oldValue) + return if (lastIndexOf == -1) { + this + } else { + substring(0, lastIndexOf) + newValue + substring(lastIndexOf + oldValue.length) + } + } + companion object { - private const val maxResults = 20 + private const val maxMangaResults = 20 + private const val maxChapterResults = 999999 } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt index 575d77ea2c..ae23ae7918 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zeistmanga/ZeistMangaGenerator.kt @@ -9,18 +9,26 @@ class ZeistMangaGenerator : ThemeSourceGenerator { override val themeClass = "ZeistManga" - override val baseVersionCode: Int = 6 + override val baseVersionCode: Int = 8 override val sources = listOf( + SingleLang("AnimeXNovel", "https://www.animexnovel.com", "pt-BR"), SingleLang("Asupan Komik", "https://www.asupankomik.my.id", "id", overrideVersionCode = 1), - SingleLang("DatGarScanlation", "https://datgarscanlation.blogspot.com", "es"), + SingleLang("Eleven Scanlator", "https://elevenscanlator.blogspot.com", "pt-BR"), + SingleLang("Guilda Tier Draw", "https://www.guildatierdraw.com", "pt-BR", isNsfw = true), SingleLang("Hijala", "https://hijala.blogspot.com", "ar"), SingleLang("KLManhua", "https://klmanhua.blogspot.com", "id", isNsfw = true), + SingleLang("KomikRealm", "https://www.komikrealm.my.id", "id"), + SingleLang("Loner Translations", "https://loner-tl.blogspot.com", "ar"), SingleLang("Manga Ai Land", "https://manga-ai-land.blogspot.com", "ar"), - SingleLang("Muslos No Sekai", "https://muslosnosekai.blogspot.com", "es"), - SingleLang("Noromax", "https://www.noromax.xyz", "id"), + SingleLang("Manga Soul", "https://www.manga-soul.com", "ar", isNsfw = true), + SingleLang("MikoRoku", "https://www.mikoroku.web.id", "id", isNsfw = true), + SingleLang("Mikrokosmos Fansub", "https://mikrokosmosfb.blogspot.com", "tr", isNsfw = true), SingleLang("ShiyuraSub", "https://shiyurasub.blogspot.com", "id"), + SingleLang("SobatManKu", "https://www.sobatmanku19.site", "id"), SingleLang("Tooncubus", "https://www.tooncubus.top", "id", isNsfw = true), + SingleLang("Tyrant Scans", "https://www.tyrantscans.com", "pt-BR"), + SingleLang("Yokai", "https://yokai-team.blogspot.com", "ar"), ) companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zmanga/ZMangaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zmanga/ZMangaGenerator.kt index c5d3b5a583..c092c35551 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zmanga/ZMangaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/zmanga/ZMangaGenerator.kt @@ -12,7 +12,6 @@ class ZMangaGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 1 override val sources = listOf( - SingleLang("Alceascan", "https://alceascan.my.id", "id"), SingleLang("KomikGan", "https://komikgan.com", "id"), SingleLang("Hensekai", "https://hensekai.com", "id", isNsfw = true, className = "Hensekai"), SingleLang("KomikIndo.info", "http://komikindo.info", "id", isNsfw = true, className = "KomikIndoInfo"), diff --git a/multisrc/src/main/java/generator/ThemeSourceGenerator.kt b/multisrc/src/main/java/generator/ThemeSourceGenerator.kt index ba19d359ae..d768cade5b 100644 --- a/multisrc/src/main/java/generator/ThemeSourceGenerator.kt +++ b/multisrc/src/main/java/generator/ThemeSourceGenerator.kt @@ -108,7 +108,7 @@ interface ThemeSourceGenerator { """ | | - | + | """.trimMargin(), ) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a65ef43f7..87406ffe6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,10 @@ include(":core") -listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor", "synchrony").forEach { - include(":lib-$it") - project(":lib-$it").projectDir = File("lib/$it") +// Load all modules under /lib +File(rootDir, "lib").eachDir { + val libName = it.name + include(":lib-$libName") + project(":lib-$libName").projectDir = File("lib/$libName") } if (System.getenv("CI") == null || System.getenv("CI_MODULE_GEN") == "true") { @@ -11,57 +13,14 @@ if (System.getenv("CI") == null || System.getenv("CI_MODULE_GEN") == "true") { include(":multisrc") project(":multisrc").projectDir = File("multisrc") - // Loads all extensions - File(rootDir, "src").eachDir { dir -> - dir.eachDir { subdir -> - - //ANTIGO - val name = ":extensions:individual:${dir.name}:${subdir.name}" - include(name) - project(name).projectDir = File("src/${dir.name}/${subdir.name}") - - - /* NOVO - val lang = "pt" - val projectName = ":extensions:individual:$lang:${subdir.name}" - println(projectName) - include(projectName) - project(projectName).projectDir = File("src/${lang}/${subdir.name}") - */ - } - } - // Loads all generated extensions from multisrc - File(rootDir, "generated-src").eachDir { dir -> - dir.eachDir { subdir -> - - //ANTIGO - val name = ":extensions:multisrc:${dir.name}:${subdir.name}" - include(name) - project(name).projectDir = File("generated-src/${dir.name}/${subdir.name}") - - - /* NOVO - val lang = "pt" - val projectName = ":extensions:multisrc:$lang:${subdir.name}" - println(projectName) - include(projectName) - project(projectName).projectDir = File("generated-src/${lang}/${subdir.name}") - */ - } - } - /** - * If you're developing locally and only want to work with a single module, - * comment out the parts above and uncomment below. + * Add or remove modules to load as needed for local development here. + * To generate multisrc extensions first, run the `:multisrc:generateExtensions` task first. */ -// 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}") - + loadAllIndividualExtensions("pt") + loadAllGeneratedMultisrcExtensions("pt") + // loadIndividualExtension("all", "mangadex") + // loadGeneratedMultisrcExtension("en", "guya") } else { // Running in CI (GitHub Actions) @@ -111,6 +70,35 @@ if (System.getenv("CI") == null || System.getenv("CI_MODULE_GEN") == "true") { } } +fun loadAllIndividualExtensions() { + File(rootDir, "src").eachDir { dir -> + dir.eachDir { subdir -> + val name = ":extensions:individual:${dir.name}:${subdir.name}" + include(name) + project(name).projectDir = File("src/${dir.name}/${subdir.name}") + } + } +} +fun loadAllGeneratedMultisrcExtensions() { + File(rootDir, "generated-src").eachDir { dir -> + dir.eachDir { subdir -> + val name = ":extensions:multisrc:${dir.name}:${subdir.name}" + include(name) + project(name).projectDir = File("generated-src/${dir.name}/${subdir.name}") + } + } +} +fun loadIndividualExtension(lang: String, name: String) { + val projectName = ":extensions:individual:$lang:$name" + include(projectName) + project(projectName).projectDir = File("src/${lang}/${name}") +} +fun loadGeneratedMultisrcExtension(lang: String, name: String) { + val projectName = ":extensions:multisrc:$lang:$name" + include(projectName) + project(projectName).projectDir = File("generated-src/${lang}/${name}") +} + fun File.getChunk(chunk: Int, chunkSize: Int): List? { return listFiles() // Lang folder diff --git a/src/all/akuma/AndroidManifest.xml b/src/all/akuma/AndroidManifest.xml index 3d6f16b294..329ef70335 100644 --- a/src/all/akuma/AndroidManifest.xml +++ b/src/all/akuma/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - \ No newline at end of file + diff --git a/src/all/batoto/AndroidManifest.xml b/src/all/batoto/AndroidManifest.xml index 5817ba8e3b..c4ca310dd6 100644 --- a/src/all/batoto/AndroidManifest.xml +++ b/src/all/batoto/AndroidManifest.xml @@ -1,7 +1,5 @@ - - + - \ No newline at end of file + diff --git a/src/ca/fansubscat/AndroidManifest.xml b/src/all/comicfury/AndroidManifest.xml similarity index 100% rename from src/ca/fansubscat/AndroidManifest.xml rename to src/all/comicfury/AndroidManifest.xml diff --git a/src/all/comicfury/build.gradle b/src/all/comicfury/build.gradle new file mode 100644 index 0000000000..9cf5be3144 --- /dev/null +++ b/src/all/comicfury/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Comic Fury' + pkgNameSuffix = 'all.comicfury' + extClass = '.ComicFuryFactory' + extVersionCode = 2 + isNsfw = true +} + +dependencies { + implementation(project(':lib-textinterceptor')) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/comicfury/res/mipmap-hdpi/ic_launcher.png b/src/all/comicfury/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..865ccdc5f5 Binary files /dev/null and b/src/all/comicfury/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/comicfury/res/mipmap-mdpi/ic_launcher.png b/src/all/comicfury/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..436075be25 Binary files /dev/null and b/src/all/comicfury/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/comicfury/res/mipmap-xhdpi/ic_launcher.png b/src/all/comicfury/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2e7393ab74 Binary files /dev/null and b/src/all/comicfury/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/comicfury/res/mipmap-xxhdpi/ic_launcher.png b/src/all/comicfury/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a5c5732167 Binary files /dev/null and b/src/all/comicfury/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/comicfury/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/comicfury/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..1188a64c5c Binary files /dev/null and b/src/all/comicfury/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/comicfury/res/web_hi_res_512.png b/src/all/comicfury/res/web_hi_res_512.png new file mode 100644 index 0000000000..9f721a498c Binary files /dev/null and b/src/all/comicfury/res/web_hi_res_512.png differ diff --git a/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFury.kt b/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFury.kt new file mode 100644 index 0000000000..268487b6b0 --- /dev/null +++ b/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFury.kt @@ -0,0 +1,291 @@ +package eu.kanade.tachiyomi.extension.all.comicfury + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor +import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.text.SimpleDateFormat +import java.util.Locale + +class ComicFury( + override val lang: String, + private val siteLang: String = lang, // override lang string used in MangaSearch + private val extraName: String = "", +) : HttpSource(), ConfigurableSource { + override val baseUrl: String = "https://comicfury.com" + override val name: String = "Comic Fury$extraName" //Used for No Text + override val supportsLatest: Boolean = true + private val dateFormat = SimpleDateFormat("dd MMM yyyy hh:mm aa", Locale.US) + private val dateFormatSlim = SimpleDateFormat("dd MMM yyyy", Locale.US) + + override val client = super.client.newBuilder().addInterceptor(TextInterceptor()).build() + + /** + * Archive is on a separate page from manga info + */ + override fun chapterListRequest(manga: SManga): Request = + GET("$baseUrl/read/${manga.url.substringAfter("?url=")}/archive") + + /** + * Open Archive Url instead of the details page + * Helps with getting past the nfsw pages + */ + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl/read/" + manga.url.substringAfter("?url=") + "/archive" + } + + /** + * There are two different ways chapters are setup + * First Way if (true) + * Manga -> Chapter -> Comic -> Pages + * The Second Way if (false) + * Manga -> Comic -> Pages + * + * Importantly the Chapter And Comic Pages can be easy distinguished + * by the name of the list elements in this case archive-chapter/archive-comic + * + * For Manga that doesn't have "chapters" skip the loop. Including All Sub-Comics of Chapters + * + * Put the chapter name into scanlator so read can know what chapter it is. + * + * Chapter Number is handled as Chapter dot Comic. Ex. Chapter 6, Comic 4: chapter_number = 6.4 + * + */ + override fun chapterListParse(response: Response): List { + val jsp = response.asJsoup() + if (jsp.selectFirst("div.archive-chapter") != null) { + val chapters: MutableList = arrayListOf() + for (chapter in jsp.select("div.archive-chapter").parents().reversed()) { + val name = chapter.text() + chapters.addAll( + client.newCall( + GET("$baseUrl${chapter.attr("href")}"), + ).execute() + .use { chapterListParse(it) } + .mapIndexed { i, it -> + it.apply { + scanlator = name + chapter_number += i + } + }, + ) + } + return chapters + } else { + return jsp.select("div.archive-comic").mapIndexed { i, it -> + SChapter.create().apply { + url = it.parent()!!.attr("href") + name = it.child(0).ownText() + date_upload = it.child(1).ownText().toDate() + chapter_number = "0.$i".toFloat() + } + }.toList().reversed() + } + } + + override fun pageListParse(response: Response): List { + val jsp = response.asJsoup() + val pages: MutableList = arrayListOf() + val comic = jsp.selectFirst("div.is--comic-page") + for (child in comic!!.select("div.is--image-segment div img")) { + pages.add( + Page( + pages.size, + response.request.url.toString(), + child.attr("src"), + ), + ) + } + if (showAuthorsNotesPref()) { + for (child in comic.select("div.is--author-notes div.is--comment-box").withIndex()) { + pages.add( + Page( + pages.size, + response.request.url.toString(), + TextInterceptorHelper.createUrl( + jsp.selectFirst("a.is--comment-author")?.ownText() + ?: "Error No Author For Comment Found", + jsp.selectFirst("div.is--comment-content")?.html() + ?: "Error No Comment Content Found", + ), + ), + ) + } + } + return pages + } + + /** + * Author name joining maybe redundant. + * + * Manga Status is available but not currently implemented. + */ + override fun mangaDetailsParse(response: Response): SManga { + val jsp = response.asJsoup() + val desDiv = jsp.selectFirst("div.description-tags") + return SManga.create().apply { + setUrlWithoutDomain(response.request.url.toString()) + description = desDiv?.parent()?.ownText() + genre = desDiv?.children()?.eachText()?.joinToString(", ") + author = jsp.select("a.authorname").eachText().joinToString(", ") + initialized = true + } + } + + override fun searchMangaParse(response: Response): MangasPage { + val jsp = response.asJsoup() + val list: MutableList = arrayListOf() + for (result in jsp.select("div.webcomic-result")) { + list.add( + SManga.create().apply { + url = result.selectFirst("div.webcomic-result-avatar a")!!.attr("href") + title = result.selectFirst("div.webcomic-result-title")!!.attr("title") + thumbnail_url = result.selectFirst("div.webcomic-result-avatar a img")!!.absUrl("src") + }, + ) + } + return MangasPage(list, (jsp.selectFirst("div.search-next-page") != null)) + } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val req: HttpUrl.Builder = "$baseUrl/search.php".toHttpUrl().newBuilder() + req.addQueryParameter("query", query) + req.addQueryParameter("page", page.toString()) + req.addQueryParameter("language", siteLang) + filters.forEach { + when (it) { + is TagsFilter -> req.addEncodedQueryParameter( + "tags", + it.state.replace(", ", ","), + ) + is SortFilter -> req.addQueryParameter("sort", it.state.toString()) + is CompletedComicFilter -> req.addQueryParameter( + "completed", + it.state.toInt().toString(), + ) + is LastUpdatedFilter -> req.addQueryParameter( + "lastupdate", + it.state.toString(), + ) + is ViolenceFilter -> req.addQueryParameter("fv", it.state.toString()) + is NudityFilter -> req.addQueryParameter("fn", it.state.toString()) + is StrongLangFilter -> req.addQueryParameter("fl", it.state.toString()) + is SexualFilter -> req.addQueryParameter("fs", it.state.toString()) + else -> {} + } + } + + return Request.Builder().url(req.build()).build() + } + + // START OF AUTHOR NOTES // + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + companion object { + private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes" + } + private fun showAuthorsNotesPref() = + preferences.getBoolean(SHOW_AUTHORS_NOTES_KEY, false) + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val authorsNotesPref = SwitchPreferenceCompat(screen.context).apply { + key = SHOW_AUTHORS_NOTES_KEY; title = "Show author's notes" + summary = "Enable to see the author's notes at the end of chapters (if they're there)." + setDefaultValue(false) + } + screen.addPreference(authorsNotesPref) + } + // END OF AUTHOR NOTES // + + // START OF FILTERS // + override fun getFilterList(): FilterList = getFilterList(0) + private fun getFilterList(sortIndex: Int): FilterList = FilterList( + TagsFilter(), + Filter.Separator(), + SortFilter(sortIndex), + Filter.Separator(), + LastUpdatedFilter(), + CompletedComicFilter(), + Filter.Separator(), + Filter.Header("Flags"), + ViolenceFilter(), + NudityFilter(), + StrongLangFilter(), + SexualFilter(), + ) + + internal class SortFilter(index: Int) : Filter.Select( + "Sort By", + arrayOf("Relevance", "Popularity", "Last Update"), + index, + ) + internal class CompletedComicFilter : Filter.CheckBox("Comic Completed", false) + internal class LastUpdatedFilter : Filter.Select( + "Last Updated", + arrayOf("All Time", "This Week", "This Month", "This Year", "Completed Only"), + 0, + ) + internal class ViolenceFilter : Filter.Select( + "Violence", + arrayOf("None / Minimal", "Violent Content", "Gore / Graphic"), + 2, + ) + internal class NudityFilter : Filter.Select( + "Frontal Nudity", + arrayOf("None", "Occasional", "Frequent"), + 2, + ) + internal class StrongLangFilter : Filter.Select( + "Strong Language", + arrayOf("None", "Occasional", "Frequent"), + 2, + ) + internal class SexualFilter : Filter.Select( + "Sexual Content", + arrayOf("No Sexual Content", "Sexual Situations", "Strong Sexual Themes"), + 2, + ) + internal class TagsFilter : Filter.Text("Tags") + + // END OF FILTERS // + + override fun popularMangaRequest(page: Int): Request = + searchMangaRequest(page, "", getFilterList(1)) + override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) + + override fun latestUpdatesRequest(page: Int): Request = + searchMangaRequest(page, "", getFilterList(2)) + override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) + + override fun imageUrlParse(response: Response): String = + throw UnsupportedOperationException("Not Used") + + private fun String.toDate(): Long { + val ret = this.replace("st", "") + .replace("nd", "") + .replace("rd", "") + .replace("th", "") + .replace(",", "") + return dateFormat.parse(ret)?.time ?: dateFormatSlim.parse(ret)!!.time + } + + private fun Boolean.toInt(): Int = if (this) { 0 } else { 1 } +} diff --git a/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFuryFactory.kt b/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFuryFactory.kt new file mode 100644 index 0000000000..cb18b496d0 --- /dev/null +++ b/src/all/comicfury/src/eu/kanade/tachiyomi/extension/all/comicfury/ComicFuryFactory.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.extension.all.comicfury + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class ComicFuryFactory : SourceFactory { + override fun createSources(): List = listOf( + ComicFury("all"), + ComicFury("en"), + ComicFury("es"), + ComicFury("pt-BR", "pt"), + ComicFury("de"), + ComicFury("fr"), + ComicFury("it"), + ComicFury("pl"), + ComicFury("ja"), + ComicFury("zh"), + ComicFury("ru"), + ComicFury("fi"), + ComicFury("other"), + ComicFury("other", "notext", " (No Text)"), + ) +} diff --git a/src/all/comickfun/AndroidManifest.xml b/src/all/comickfun/AndroidManifest.xml index 1411452060..ab949d68e0 100644 --- a/src/all/comickfun/AndroidManifest.xml +++ b/src/all/comickfun/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - - + + + + + + diff --git a/src/all/comickfun/assets/i18n/messages_en.properties b/src/all/comickfun/assets/i18n/messages_en.properties new file mode 100644 index 0000000000..55487eaacd --- /dev/null +++ b/src/all/comickfun/assets/i18n/messages_en.properties @@ -0,0 +1,13 @@ +ignored_groups_title=Ignored Groups +ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive) +include_tags_title=Include Tags +include_tags_on=More specific, but might contain spoilers! +include_tags_off=Only the broader genres +update_cover_title=Update Covers +update_cover_on=Keep cover updated +update_cover_off=Prefer first cover +score_position_title=Score Position in the Description +score_position_top=Top +score_position_middle=Middle +score_position_bottom=Bottom +score_position_none=Hide Score diff --git a/src/all/comickfun/assets/i18n/messages_pt_br.properties b/src/all/comickfun/assets/i18n/messages_pt_br.properties new file mode 100644 index 0000000000..745ed5b153 --- /dev/null +++ b/src/all/comickfun/assets/i18n/messages_pt_br.properties @@ -0,0 +1,13 @@ +ignored_groups_title=Grupos Ignorados +ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha +include_tags_title=Incluir Tags +include_tags_on=Mais detalhadas, mas podem conter spoilers +include_tags_off=Apenas os gêneros básicos +update_cover_title=Atualizar Capas +update_cover_on=Manter capas atualizadas +update_cover_off=Usar apenas a primeira capa +score_position_title=Posição da Nota na Descrição +score_position_top=Topo +score_position_middle=Meio +score_position_bottom=Final +score_position_none=Sem Nota diff --git a/src/all/comickfun/build.gradle b/src/all/comickfun/build.gradle index 644d588cca..f07d392840 100644 --- a/src/all/comickfun/build.gradle +++ b/src/all/comickfun/build.gradle @@ -6,8 +6,12 @@ ext { extName = 'Comick' pkgNameSuffix = 'all.comickfun' extClass = '.ComickFunFactory' - extVersionCode = 29 + extVersionCode = 40 isNsfw = true } +dependencies { + implementation(project(":lib-i18n")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt index 51a7a8dd3e..07fb8dcf3c 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt @@ -1,8 +1,16 @@ package eu.kanade.tachiyomi.extension.all.comickfun +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page @@ -16,6 +24,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone @@ -24,11 +34,11 @@ import kotlin.math.min abstract class ComickFun( override val lang: String, private val comickFunLang: String, -) : HttpSource() { +) : ConfigurableSource, HttpSource() { override val name = "Comick" - override val baseUrl = "https://comick.app" + override val baseUrl = "https://comick.ink" private val apiUrl = "https://api.comick.fun" @@ -43,6 +53,108 @@ abstract class ComickFun( private lateinit var searchResponse: List + private val intl by lazy { + Intl( + language = lang, + baseLanguage = "en", + availableLanguages = setOf("en", "pt-BR"), + classLoader = this::class.java.classLoader!!, + ) + } + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + EditTextPreference(screen.context).apply { + key = IGNORED_GROUPS_PREF + title = intl["ignored_groups_title"] + summary = intl["ignored_groups_summary"] + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit() + .putString(IGNORED_GROUPS_PREF, newValue.toString()) + .commit() + } + }.also(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = INCLUDE_MU_TAGS_PREF + title = intl["include_tags_title"] + summaryOn = intl["include_tags_on"] + summaryOff = intl["include_tags_off"] + setDefaultValue(INCLUDE_MU_TAGS_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit() + .putBoolean(INCLUDE_MU_TAGS_PREF, newValue as Boolean) + .commit() + } + }.also(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = FIRST_COVER_PREF + title = intl["update_cover_title"] + summaryOff = intl["update_cover_off"] + summaryOn = intl["update_cover_on"] + setDefaultValue(FIRST_COVER_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit() + .putBoolean(FIRST_COVER_PREF, newValue as Boolean) + .commit() + } + }.also(screen::addPreference) + + ListPreference(screen.context).apply { + key = SCORE_POSITION_PREF + title = intl["score_position_title"] + summary = "%s" + entries = arrayOf( + intl["score_position_top"], + intl["score_position_middle"], + intl["score_position_bottom"], + intl["score_position_none"], + ) + entryValues = arrayOf(SCORE_POSITION_DEFAULT, "middle", "bottom", "none") + setDefaultValue(SCORE_POSITION_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + val index = findIndexOfValue(selected) + val entry = entryValues[index] as String + + preferences.edit() + .putString(SCORE_POSITION_PREF, entry) + .commit() + } + }.also(screen::addPreference) + } + + private val SharedPreferences.ignoredGroups: Set + get() = getString(IGNORED_GROUPS_PREF, "") + ?.lowercase() + ?.split("\n") + ?.map(String::trim) + ?.filter(String::isNotEmpty) + ?.sorted() + .orEmpty() + .toSet() + + private val SharedPreferences.includeMuTags: Boolean + get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT) + + private val SharedPreferences.updateCover: Boolean + get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT) + + private val SharedPreferences.scorePosition: String + get() = getString(SCORE_POSITION_PREF, SCORE_POSITION_DEFAULT) ?: SCORE_POSITION_DEFAULT + + init { + preferences.newLineIgnoredGroups() + } + override fun headersBuilder() = Headers.Builder().apply { add("Referer", "$baseUrl/") add("User-Agent", "Tachiyomi ${System.getProperty("http.agent")}") @@ -76,7 +188,11 @@ abstract class ComickFun( override fun latestUpdatesParse(response: Response) = popularMangaParse(response) /** Manga Search **/ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { return if (query.startsWith(SLUG_SEARCH_PREFIX)) { // url deep link val slugOrHid = query.substringAfter(SLUG_SEARCH_PREFIX) @@ -132,60 +248,63 @@ abstract class ComickFun( addQueryParameter("completed", "true") } } + is GenreFilter -> { - it.state.filter { (it as TriState).isIncluded() }.forEach { - addQueryParameter( - "genres", - (it as TriState).value, - ) + it.state.filter { it.isIncluded() }.forEach { + addQueryParameter("genres", it.value) } - it.state.filter { (it as TriState).isExcluded() }.forEach { - addQueryParameter( - "excludes", - (it as TriState).value, - ) + it.state.filter { it.isExcluded() }.forEach { + addQueryParameter("excludes", it.value) } } + is DemographicFilter -> { - it.state.filter { (it as CheckBox).state }.forEach { - addQueryParameter( - "demographic", - (it as CheckBox).value, - ) + it.state.filter { it.isIncluded() }.forEach { + addQueryParameter("demographic", it.value) } } + is TypeFilter -> { - it.state.filter { (it as CheckBox).state }.forEach { - addQueryParameter( - "country", - (it as CheckBox).value, - ) + it.state.filter { it.state }.forEach { + addQueryParameter("country", it.value) } } + is SortFilter -> { addQueryParameter("sort", it.getValue()) } + + is StatusFilter -> { + if (it.state > 0) { + addQueryParameter("status", it.getValue()) + } + } + is CreatedAtFilter -> { if (it.state > 0) { addQueryParameter("time", it.getValue()) } } + is MinimumFilter -> { if (it.state.isNotEmpty()) { addQueryParameter("minimum", it.state) } } + is FromYearFilter -> { if (it.state.isNotEmpty()) { addQueryParameter("from", it.state) } } + is ToYearFilter -> { if (it.state.isNotEmpty()) { addQueryParameter("to", it.state) } } + is TagFilter -> { if (it.state.isNotEmpty()) { it.state.split(",").forEach { @@ -193,6 +312,7 @@ abstract class ComickFun( } } } + else -> {} } } @@ -217,9 +337,43 @@ abstract class ComickFun( return GET("$apiUrl$mangaUrl?tachiyomi=true", headers) } - override fun mangaDetailsParse(response: Response): SManga { + override fun fetchMangaDetails(manga: SManga): Observable { + return client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response, manga).apply { initialized = true } + } + } + + override fun mangaDetailsParse(response: Response): SManga = + mangaDetailsParse(response, SManga.create()) + + private fun mangaDetailsParse(response: Response, manga: SManga): SManga { val mangaData = response.parseAs() - return mangaData.toSManga() + if (!preferences.updateCover && manga.thumbnail_url != mangaData.comic.cover) { + if (manga.thumbnail_url.toString().endsWith("#1")) { + return mangaData.toSManga( + includeMuTags = preferences.includeMuTags, + scorePosition = preferences.scorePosition, + covers = listOf( + MDcovers( + b2key = manga.thumbnail_url?.substringBeforeLast("#") + ?.substringAfterLast("/"), + vol = "1", + ), + ), + ) + } + val coversUrl = + "$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true" + val covers = client.newCall(GET(coversUrl)).execute() + .parseAs().md_covers.reversed() + return mangaData.toSManga( + includeMuTags = preferences.includeMuTags, + covers = if (covers.any { it.vol == "1" }) covers.filter { it.vol == "1" } else covers, + ) + } + return mangaData.toSManga(includeMuTags = preferences.includeMuTags) } override fun getMangaUrl(manga: SManga): String { @@ -269,7 +423,11 @@ abstract class ComickFun( page += 1 } - return chapterListResponse.chapters.map { it.toSChapter(mangaUrl) } + return chapterListResponse.chapters + .filter { + it.groups.map { g -> g.lowercase() }.intersect(preferences.ignoredGroups).isEmpty() + } + .map { it.toSChapter(mangaUrl) } } override fun getChapterUrl(chapter: SChapter): String { @@ -299,16 +457,41 @@ abstract class ComickFun( override fun getFilterList() = getFilters() + private fun SharedPreferences.newLineIgnoredGroups() { + if (getBoolean(MIGRATED_IGNORED_GROUPS, false)) return + val ignoredGroups = getString(IGNORED_GROUPS_PREF, "").orEmpty() + + edit() + .putString( + IGNORED_GROUPS_PREF, + ignoredGroups + .split(",") + .map(String::trim) + .filter(String::isNotEmpty) + .joinToString("\n"), + ) + .putBoolean(MIGRATED_IGNORED_GROUPS, true) + .apply() + } + companion object { const val SLUG_SEARCH_PREFIX = "id:" + private const val IGNORED_GROUPS_PREF = "IgnoredGroups" + private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags" + private const val INCLUDE_MU_TAGS_DEFAULT = false + private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups" + private const val FIRST_COVER_PREF = "DefaultCover" + private const val FIRST_COVER_DEFAULT = true + private const val SCORE_POSITION_PREF = "ScorePosition" + private const val SCORE_POSITION_DEFAULT = "top" private const val limit = 20 val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).apply { timeZone = TimeZone.getTimeZone("UTC") } } - val markdownLinksRegex = "\\[([^]]+)\\]\\(([^)]+)\\)".toRegex() - val markdownItalicBoldRegex = "\\*+\\s*([^\\*]*)\\s*\\*+".toRegex() + val markdownLinksRegex = "\\[([^]]+)]\\(([^)]+)\\)".toRegex() + val markdownItalicBoldRegex = "\\*+\\s*([^*]*)\\s*\\*+".toRegex() val markdownItalicRegex = "_+\\s*([^_]*)\\s*_+".toRegex() } } diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt index 51e1742fa3..405a6e37e8 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt @@ -4,91 +4,159 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import java.math.BigDecimal +import java.math.RoundingMode @Serializable data class SearchManga( val hid: String, val title: String, - val md_covers: List, - val cover_url: String? = null, - + @SerialName("md_covers") val mdCovers: List = emptyList(), + @SerialName("cover_url") val cover: String? = null, ) { fun toSManga() = SManga.create().apply { - // appennding # at end as part of migration from slug to hid + // appending # at end as part of migration from slug to hid url = "/comic/$hid#" title = this@SearchManga.title - thumbnail_url = parseCover(cover_url, md_covers) + thumbnail_url = parseCover(cover, mdCovers) } } @Serializable data class Manga( val comic: Comic, - val artists: List = emptyList(), - val authors: List = emptyList(), - val genres: List = emptyList(), + val artists: List = emptyList(), + val authors: List = emptyList(), + val genres: List = emptyList(), + val demographic: String? = null, ) { - fun toSManga() = SManga.create().apply { - // appennding # at end as part of migration from slug to hid - url = "/comic/${comic.hid}#" - title = comic.title - description = comic.desc.beautifyDescription() - if (comic.altTitles.isNotEmpty()) { - if (description.isNullOrEmpty()) { - description = "Alternative Titles:\n" - } else { - description += "\n\nAlternative Titles:\n" + fun toSManga( + includeMuTags: Boolean = false, + scorePosition: String = "", + covers: List? = null, + ) = + SManga.create().apply { + // appennding # at end as part of migration from slug to hid + url = "/comic/${comic.hid}#" + title = comic.title + description = buildString { + if (scorePosition == "top") append(comic.fancyScore) + val desc = comic.desc?.beautifyDescription() + if (!desc.isNullOrEmpty()) { + if (this.isNotEmpty()) append("\n\n") + append(desc) + } + if (scorePosition == "middle") { + if (this.isNotEmpty()) append("\n\n") + append(comic.fancyScore) + } + if (comic.altTitles.isNotEmpty()) { + if (this.isNotEmpty()) append("\n\n") + append("Alternative Titles:\n") + append( + comic.altTitles.mapNotNull { title -> + title.title?.let { "• $it" } + }.joinToString("\n"), + ) + } + if (scorePosition == "bottom") { + if (this.isNotEmpty()) append("\n\n") + append(comic.fancyScore) + } } - description += comic.altTitles.mapNotNull { title -> - title.title?.let { "• $it" } - }.joinToString("\n") + status = comic.status.parseStatus(comic.translationComplete) + thumbnail_url = parseCover( + comic.cover, + covers ?: comic.mdCovers, + ) + artist = artists.joinToString { it.name.trim() } + author = authors.joinToString { it.name.trim() } + genre = buildList { + comic.origination?.let(::add) + demographic?.let { add(Name(it)) } + addAll(genres) + addAll(comic.mdGenres.mapNotNull { it.name }) + if (includeMuTags) { + comic.muGenres.categories.forEach { category -> + category?.category?.title?.let { add(Name(it)) } + } + } + } + .distinctBy { it.name } + .filter { it.name.isNotBlank() } + .joinToString { it.name.trim() } } - status = comic.status.parseStatus(comic.translation_completed) - thumbnail_url = parseCover(comic.cover_url, comic.md_covers) - artist = artists.joinToString { it.name.trim() } - author = authors.joinToString { it.name.trim() } - genre = genres.joinToString { it.name.trim() } - } } @Serializable data class Comic( val hid: String, val title: String, - @SerialName("md_titles") val altTitles: List, - val desc: String = "N/A", - val status: Int = 0, - val translation_completed: Boolean = true, - val md_covers: List, - val cover_url: String? = null, + val country: String? = null, + val slug: String? = null, + @SerialName("md_titles") val altTitles: List = emptyList(), + val desc: String? = null, + val status: Int? = 0, + @SerialName("translation_completed") val translationComplete: Boolean? = true, + @SerialName("md_covers") val mdCovers: List<MDcovers> = emptyList(), + @SerialName("cover_url") val cover: String? = null, + @SerialName("md_comic_md_genres") val mdGenres: List<MdGenres>, + @SerialName("mu_comics") val muGenres: MuComicCategories = MuComicCategories(emptyList()), + @SerialName("bayesian_rating") val score: String? = null, +) { + val origination = when (country) { + "jp" -> Name("Manga") + "kr" -> Name("Manhwa") + "cn" -> Name("Manhua") + else -> null + } + val fancyScore: String = if (score.isNullOrEmpty()) { + "" + } else { + val stars = score.toBigDecimal().div(BigDecimal(2)) + .setScale(0, RoundingMode.HALF_UP).toInt() + buildString { + append("★".repeat(stars)) + if (stars < 5) append("☆".repeat(5 - stars)) + append(" $score") + } + } +} + +@Serializable +data class MdGenres( + @SerialName("md_genres") val name: Name? = null, ) @Serializable -data class MDcovers( - val b2key: String?, +data class MuComicCategories( + @SerialName("mu_comic_categories") val categories: List<MuCategories?> = emptyList(), ) @Serializable -data class MDtitles( - val title: String?, +data class MuCategories( + @SerialName("mu_categories") val category: Title? = null, ) @Serializable -data class Artist( - val name: String, - val slug: String, +data class Covers( + val md_covers: List<MDcovers> = emptyList(), ) @Serializable -data class Author( - val name: String, - val slug: String, +data class MDcovers( + val b2key: String?, + val vol: String? = null, +) + +@Serializable +data class Title( + val title: String?, ) @Serializable -data class Genre( - val slug: String, +data class Name( val name: String, ) @@ -101,18 +169,18 @@ data class ChapterList( @Serializable data class Chapter( val hid: String, - val lang: String, + val lang: String = "", val title: String = "", - val created_at: String = "", + @SerialName("created_at") val createdAt: String = "", val chap: String = "", val vol: String = "", - val group_name: List<String> = emptyList(), + @SerialName("group_name") val groups: List<String> = emptyList(), ) { fun toSChapter(mangaUrl: String) = SChapter.create().apply { url = "$mangaUrl/$hid-chapter-$chap-$lang" name = beautifyChapterName(vol, chap, title) - date_upload = created_at.parseDate() - scanlator = group_name.joinToString().takeUnless { it.isBlank() } ?: "Unknown" + date_upload = createdAt.parseDate() + scanlator = groups.joinToString().takeUnless { it.isBlank() } ?: "Unknown" } } diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt index 321f8173f3..7d8d0a33d8 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt @@ -5,13 +5,14 @@ import eu.kanade.tachiyomi.source.model.FilterList fun getFilters(): FilterList { return FilterList( - Filter.Header(name = "NOTE: Everything below is ignored if using text search"), - CompletedFilter("Completed translation"), + Filter.Header(name = "The filter is ignored when using text search."), GenreFilter("Genre", getGenresList), DemographicFilter("Demographic", getDemographicList), TypeFilter("Type", getTypeList), SortFilter("Sort", getSortsList), - CreatedAtFilter("Created At", getCreatedAtList), + StatusFilter("Status", getStatusList), + CompletedFilter("Completely Scanlated?"), + CreatedAtFilter("Created at", getCreatedAtList), MinimumFilter("Minimum Chapters"), Filter.Header("From Year, ex: 2010"), FromYearFilter("From"), @@ -19,162 +20,173 @@ fun getFilters(): FilterList { ToYearFilter("To"), Filter.Header("Separate tags with commas"), TagFilter("Tags"), - ) } /** Filters **/ -internal class GenreFilter(name: String, genreList: List<TriState>) : Group(name, genreList) +internal class GenreFilter(name: String, genreList: List<Pair<String, String>>) : + Filter.Group<TriFilter>(name, genreList.map { TriFilter(it.first, it.second) }) -internal class TagFilter(name: String) : Text(name) +internal class TagFilter(name: String) : TextFilter(name) -internal class DemographicFilter(name: String, demographicList: List<CheckBox>) : - Group(name, demographicList) +internal class DemographicFilter(name: String, demographicList: List<Pair<String, String>>) : + Filter.Group<TriFilter>(name, demographicList.map { TriFilter(it.first, it.second) }) -internal class TypeFilter(name: String, typeList: List<CheckBox>) : - Group(name, typeList) +internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) : + Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) }) -internal class CompletedFilter(name: String) : CheckBox(name) +internal class CompletedFilter(name: String) : CheckBoxFilter(name) -internal class CreatedAtFilter(name: String, createdAtList: Array<Pair<String, String>>) : - Select(name, createdAtList) +internal class CreatedAtFilter(name: String, createdAtList: List<Pair<String, String>>) : + SelectFilter(name, createdAtList) -internal class MinimumFilter(name: String) : Text(name) +internal class MinimumFilter(name: String) : TextFilter(name) -internal class FromYearFilter(name: String) : Text(name) +internal class FromYearFilter(name: String) : TextFilter(name) -internal class ToYearFilter(name: String) : Text(name) +internal class ToYearFilter(name: String) : TextFilter(name) -internal class SortFilter(name: String, sortList: Array<Pair<String, String>>, state: Int = 0) : - Select(name, sortList, state) +internal class SortFilter(name: String, sortList: List<Pair<String, String>>, state: Int = 0) : + SelectFilter(name, sortList, state) -/** Generics **/ -internal open class Group(name: String, values: List<Any>) : - Filter.Group<Any>(name, values) +internal class StatusFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) : + SelectFilter(name, statusList, state) -internal open class TriState(name: String, val value: String) : Filter.TriState(name) +/** Generics **/ +internal open class TriFilter(name: String, val value: String) : Filter.TriState(name) -internal open class Text(name: String) : Filter.Text(name) +internal open class TextFilter(name: String) : Filter.Text(name) -internal open class CheckBox(name: String, val value: String = "") : Filter.CheckBox(name) +internal open class CheckBoxFilter(name: String, val value: String = "") : Filter.CheckBox(name) -internal open class Select(name: String, private val vals: Array<Pair<String, String>>, state: Int = 0) : +internal open class SelectFilter(name: String, private val vals: List<Pair<String, String>>, state: Int = 0) : Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) { fun getValue() = vals[state].second } /** Filters Data **/ -private val getGenresList: List<TriState> = listOf( - TriState("4-Koma", "4-koma"), - TriState("Action", "action"), - TriState("Adaptation", "adaptation"), - TriState("Adult", "adult"), - TriState("Adventure", "adventure"), - TriState("Aliens", "aliens"), - TriState("Animals", "animals"), - TriState("Anthology", "anthology"), - TriState("Award Winning", "award-winning"), - TriState("Comedy", "comedy"), - TriState("Cooking", "cooking"), - TriState("Crime", "crime"), - TriState("Crossdressing", "crossdressing"), - TriState("Delinquents", "delinquents"), - TriState("Demons", "demons"), - TriState("Doujinshi", "doujinshi"), - TriState("Drama", "drama"), - TriState("Ecchi", "ecchi"), - TriState("Fan Colored", "fan-colored"), - TriState("Fantasy", "fantasy"), - TriState("Full Color", "full-color"), - TriState("Gender Bender", "gender-bender"), - TriState("Genderswap", "genderswap"), - TriState("Ghosts", "ghosts"), - TriState("Gore", "gore"), - TriState("Gyaru", "gyaru"), - TriState("Harem", "harem"), - TriState("Historical", "historical"), - TriState("Horror", "horror"), - TriState("Incest", "incest"), - TriState("Isekai", "isekai"), - TriState("Loli", "loli"), - TriState("Long Strip", "long-strip"), - TriState("Mafia", "mafia"), - TriState("Magic", "magic"), - TriState("Magical Girls", "magical-girls"), - TriState("Martial Arts", "martial-arts"), - TriState("Mature", "mature"), - TriState("Mecha", "mecha"), - TriState("Medical", "medical"), - TriState("Military", "military"), - TriState("Monster Girls", "monster-girls"), - TriState("Monsters", "monsters"), - TriState("Music", "music"), - TriState("Mystery", "mystery"), - TriState("Ninja", "ninja"), - TriState("Office Workers", "office-workers"), - TriState("Official Colored", "official-colored"), - TriState("Oneshot", "oneshot"), - TriState("Philosophical", "philosophical"), - TriState("Police", "police"), - TriState("Post-Apocalyptic", "post-apocalyptic"), - TriState("Psychological", "psychological"), - TriState("Reincarnation", "reincarnation"), - TriState("Reverse Harem", "reverse-harem"), - TriState("Romance", "romance"), - TriState("Samurai", "samurai"), - TriState("School Life", "school-life"), - TriState("Sci-Fi", "sci-fi"), - TriState("Sexual Violence", "sexual-violence"), - TriState("Shota", "shota"), - TriState("Shoujo Ai", "shoujo-ai"), - TriState("Shounen Ai", "shounen-ai"), - TriState("Slice of Life", "slice-of-life"), - TriState("Smut", "smut"), - TriState("Sports", "sports"), - TriState("Superhero", "superhero"), - TriState("Supernatural", "supernatural"), - TriState("Survival", "survival"), - TriState("Thriller", "thriller"), - TriState("Time Travel", "time-travel"), - TriState("Traditional Games", "traditional-games"), - TriState("Tragedy", "tragedy"), - TriState("User Created", "user-created"), - TriState("Vampires", "vampires"), - TriState("Video Games", "video-games"), - TriState("Villainess", "villainess"), - TriState("Virtual Reality", "virtual-reality"), - TriState("Web Comic", "web-comic"), - TriState("Wuxia", "wuxia"), - TriState("Yaoi", "yaoi"), - TriState("Yuri", "yuri"), - TriState("Zombies", "zombies"), +private val getGenresList: List<Pair<String, String>> = listOf( + Pair("4-Koma", "4-koma"), + Pair("Action", "action"), + Pair("Adaptation", "adaptation"), + Pair("Adult", "adult"), + Pair("Adventure", "adventure"), + Pair("Aliens", "aliens"), + Pair("Animals", "animals"), + Pair("Anthology", "anthology"), + Pair("Award Winning", "award-winning"), + Pair("Comedy", "comedy"), + Pair("Cooking", "cooking"), + Pair("Crime", "crime"), + Pair("Crossdressing", "crossdressing"), + Pair("Delinquents", "delinquents"), + Pair("Demons", "demons"), + Pair("Doujinshi", "doujinshi"), + Pair("Drama", "drama"), + Pair("Ecchi", "ecchi"), + Pair("Fan Colored", "fan-colored"), + Pair("Fantasy", "fantasy"), + Pair("Full Color", "full-color"), + Pair("Gender Bender", "gender-bender"), + Pair("Genderswap", "genderswap"), + Pair("Ghosts", "ghosts"), + Pair("Gore", "gore"), + Pair("Gyaru", "gyaru"), + Pair("Harem", "harem"), + Pair("Historical", "historical"), + Pair("Horror", "horror"), + Pair("Incest", "incest"), + Pair("Isekai", "isekai"), + Pair("Loli", "loli"), + Pair("Long Strip", "long-strip"), + Pair("Mafia", "mafia"), + Pair("Magic", "magic"), + Pair("Magical Girls", "magical-girls"), + Pair("Martial Arts", "martial-arts"), + Pair("Mature", "mature"), + Pair("Mecha", "mecha"), + Pair("Medical", "medical"), + Pair("Military", "military"), + Pair("Monster Girls", "monster-girls"), + Pair("Monsters", "monsters"), + Pair("Music", "music"), + Pair("Mystery", "mystery"), + Pair("Ninja", "ninja"), + Pair("Office Workers", "office-workers"), + Pair("Official Colored", "official-colored"), + Pair("Oneshot", "oneshot"), + Pair("Philosophical", "philosophical"), + Pair("Police", "police"), + Pair("Post-Apocalyptic", "post-apocalyptic"), + Pair("Psychological", "psychological"), + Pair("Reincarnation", "reincarnation"), + Pair("Reverse Harem", "reverse-harem"), + Pair("Romance", "romance"), + Pair("Samurai", "samurai"), + Pair("School Life", "school-life"), + Pair("Sci-Fi", "sci-fi"), + Pair("Sexual Violence", "sexual-violence"), + Pair("Shota", "shota"), + Pair("Shoujo Ai", "shoujo-ai"), + Pair("Shounen Ai", "shounen-ai"), + Pair("Slice of Life", "slice-of-life"), + Pair("Smut", "smut"), + Pair("Sports", "sports"), + Pair("Superhero", "superhero"), + Pair("Supernatural", "supernatural"), + Pair("Survival", "survival"), + Pair("Thriller", "thriller"), + Pair("Time Travel", "time-travel"), + Pair("Traditional Games", "traditional-games"), + Pair("Tragedy", "tragedy"), + Pair("User Created", "user-created"), + Pair("Vampires", "vampires"), + Pair("Video Games", "video-games"), + Pair("Villainess", "villainess"), + Pair("Virtual Reality", "virtual-reality"), + Pair("Web Comic", "web-comic"), + Pair("Wuxia", "wuxia"), + Pair("Yaoi", "yaoi"), + Pair("Yuri", "yuri"), + Pair("Zombies", "zombies"), ) -private val getDemographicList: List<CheckBox> = listOf( - CheckBox("Shounen", "1"), - CheckBox("Shoujo", "2"), - CheckBox("Seinen", "3"), - CheckBox("Josei", "4"), +private val getDemographicList: List<Pair<String, String>> = listOf( + Pair("Shounen", "1"), + Pair("Shoujo", "2"), + Pair("Seinen", "3"), + Pair("Josei", "4"), ) -private val getTypeList: List<CheckBox> = listOf( - CheckBox("Manga", "jp"), - CheckBox("Manhwa", "kr"), - CheckBox("Manhua", "cn"), +private val getTypeList: List<Pair<String, String>> = listOf( + Pair("Manga", "jp"), + Pair("Manhwa", "kr"), + Pair("Manhua", "cn"), ) -private val getCreatedAtList: Array<Pair<String, String>> = arrayOf( +private val getCreatedAtList: List<Pair<String, String>> = listOf( Pair("", ""), + Pair("3 days", "3"), + Pair("7 days", "7"), Pair("30 days", "30"), Pair("3 months", "90"), Pair("6 months", "180"), Pair("1 year", "365"), ) -private val getSortsList: Array<Pair<String, String>> = arrayOf( +private val getSortsList: List<Pair<String, String>> = listOf( Pair("Most popular", "follow"), Pair("Most follows", "user_follow_count"), Pair("Most views", "view"), Pair("High rating", "rating"), Pair("Last updated", "uploaded"), + Pair("Newest", "created_at"), +) + +private val getStatusList: List<Pair<String, String>> = listOf( + Pair("All", "0"), + Pair("Ongoing", "1"), + Pair("Completed", "2"), + Pair("Cancelled", "3"), + Pair("Hiatus", "4"), ) diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunHelper.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunHelper.kt index e73fc3f62a..1d9f365a4e 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunHelper.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunHelper.kt @@ -18,11 +18,11 @@ internal fun String.beautifyDescription(): String { .trim() } -internal fun Int.parseStatus(translationComplete: Boolean): Int { +internal fun Int?.parseStatus(translationComplete: Boolean?): Int { return when (this) { 1 -> SManga.ONGOING 2 -> { - if (translationComplete) { + if (translationComplete == true) { SManga.COMPLETED } else { SManga.PUBLISHING_FINISHED @@ -34,11 +34,12 @@ internal fun Int.parseStatus(translationComplete: Boolean): Int { } } -internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String { - val b2key = runCatching { mdCovers.first().b2key } - .getOrNull() ?: "" +internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String? { + val b2key = mdCovers.firstOrNull()?.b2key + ?: return thumbnailUrl + val vol = mdCovers.firstOrNull()?.vol.orEmpty() - return "$thumbnailUrl#$b2key" + return thumbnailUrl?.replaceAfterLast("/", "$b2key#$vol") } internal fun thumbnailIntercept(chain: Interceptor.Chain): Response { diff --git a/src/all/comico/AndroidManifest.xml b/src/all/comico/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/comico/AndroidManifest.xml +++ b/src/all/comico/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/commitstrip/AndroidManifest.xml b/src/all/commitstrip/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/commitstrip/AndroidManifest.xml +++ b/src/all/commitstrip/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/cubari/AndroidManifest.xml b/src/all/cubari/AndroidManifest.xml index 3cfd493e82..4eaf291a1f 100644 --- a/src/all/cubari/AndroidManifest.xml +++ b/src/all/cubari/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -72,4 +71,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/all/cubari/CHANGELOG.md b/src/all/cubari/CHANGELOG.md deleted file mode 100644 index ae1c5e216c..0000000000 --- a/src/all/cubari/CHANGELOG.md +++ /dev/null @@ -1,67 +0,0 @@ -## 1.2.11 - -### Features - -* Add `CHANGELOG.md` & `README.md` - -## 1.2.10 - -### Features - -* Wildcard hosts in URL intent - -## 1.2.9 - -### Fix - -* release date -* empty volume - -## 1.2.8 - -### Fix - -* Upload fate - -## 1.2.7 - -### Fix - -* proxy methode - -## 1.2.6 - -### Refactor - -* replace Gson with kotlinx.serialization - -## 1.2.5 - -### Fix - -* Volume default - -## 1.2.4 - -### Fix - -* seriesSlug being the wrong type - -## 1.2.3 - -### Features - -* Volume support -* Fall back release date - -## 1.2.2 - -### Features - -* Migrate Guya proxy to Cubari - -## 1.2.1 - -### Features - -* first version diff --git a/src/all/cubari/README.md b/src/all/cubari/README.md index 56d26d07c8..d79b828ab3 100644 --- a/src/all/cubari/README.md +++ b/src/all/cubari/README.md @@ -13,7 +13,8 @@ Don't find the question you are look for go check out our general FAQs and Guide ## FAQ ### Why do I see no manga? -Cubari is a proxy for image gallery's. +Cubari is a proxy for image galleries. +If you've setup the Remote Storage via WebView the Recent tab shows your recent, unpinned entries, conversely the Popular tab shows your pinned entries. ### Where can I get more information about Cubari? You can visit the [Cubari](https://cubari.moe/) website for for more information. diff --git a/src/all/cubari/build.gradle b/src/all/cubari/build.gradle index b7f6df0db3..5624032dc8 100644 --- a/src/all/cubari/build.gradle +++ b/src/all/cubari/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Cubari' pkgNameSuffix = "all.cubari" extClass = '.CubariFactory' - extVersionCode = 19 + extVersionCode = 22 } apply from: "$rootDir/common.gradle" diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt index 2717e4885e..29465701d7 100644 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt +++ b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt @@ -4,6 +4,7 @@ import android.app.Application import android.os.Build import eu.kanade.tachiyomi.AppInfo import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -105,7 +106,7 @@ open class Cubari(override val lang: String) : HttpSource() { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() + .asObservable() .map { response -> chapterListParse(response, manga) } } @@ -285,7 +286,7 @@ open class Cubari(override val lang: String) : HttpSource() { // ------------- Helpers and whatnot --------------- - private val volumeNotSpecifiedTerms = setOf("Uncategorized", "null") + private val volumeNotSpecifiedTerms = setOf("Uncategorized", "null", "") private fun parseChapterList(payload: String, manga: SManga): List<SChapter> { val jsonObj = json.parseToJsonElement(payload).jsonObject @@ -325,12 +326,10 @@ open class Cubari(override val lang: String) : HttpSource() { seriesPrefs.getLong(chapterNum, currentTimeMillis) } - name = if (volume != null) { - // Output "Vol. 1 Ch. 1 - Chapter Name" - "Vol. $volume Ch. $chapterNum - $title" - } else { - // Output "Ch. 1 - Chapter Name" - "Ch. $chapterNum - $title" + name = buildString { + if (!volume.isNullOrBlank()) append("Vol.$volume ") + append("Ch.$chapterNum") + if (title.isNotBlank()) append(" - $title") } url = if (chapterGroups[groupNum] is JsonArray) { diff --git a/src/all/danbooru/AndroidManifest.xml b/src/all/danbooru/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/danbooru/AndroidManifest.xml +++ b/src/all/danbooru/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/dragonballmultiverse/AndroidManifest.xml b/src/all/dragonballmultiverse/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/dragonballmultiverse/AndroidManifest.xml +++ b/src/all/dragonballmultiverse/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/ehentai/AndroidManifest.xml b/src/all/ehentai/AndroidManifest.xml index c529fbbd13..c297d5bcbd 100644 --- a/src/all/ehentai/AndroidManifest.xml +++ b/src/all/ehentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/all/everiaclub/AndroidManifest.xml b/src/all/everiaclub/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/all/everiaclub/AndroidManifest.xml +++ b/src/all/everiaclub/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/all/everiaclub/build.gradle b/src/all/everiaclub/build.gradle index 9655825281..c9de6a8ccc 100644 --- a/src/all/everiaclub/build.gradle +++ b/src/all/everiaclub/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Everia.club' pkgNameSuffix = 'all.everiaclub' extClass = '.EveriaClub' - extVersionCode = 6 + extVersionCode = 9 isNsfw = true } diff --git a/src/all/everiaclub/src/eu/kanade/tachiyomi/extension/all/everiaclub/EveriaClub.kt b/src/all/everiaclub/src/eu/kanade/tachiyomi/extension/all/everiaclub/EveriaClub.kt index 566c62782c..a9ec4248e6 100644 --- a/src/all/everiaclub/src/eu/kanade/tachiyomi/extension/all/everiaclub/EveriaClub.kt +++ b/src/all/everiaclub/src/eu/kanade/tachiyomi/extension/all/everiaclub/EveriaClub.kt @@ -41,7 +41,7 @@ class EveriaClub() : ParsedHttpSource() { return GET("$baseUrl/page/$page/") } - override fun latestUpdatesSelector() = ".posts-wrapper > article" + override fun latestUpdatesSelector() = "#blog-entries > article" // Popular override fun popularMangaFromElement(element: Element): SManga { @@ -73,7 +73,7 @@ class EveriaClub() : ParsedHttpSource() { } } - override fun searchMangaSelector() = latestUpdatesSelector() + override fun searchMangaSelector() = "#content > article" // Details override fun mangaDetailsParse(document: Document): SManga { @@ -81,7 +81,7 @@ class EveriaClub() : ParsedHttpSource() { manga.title = document.select(".entry-title").text() manga.description = document.select(".entry-title").text() val genres = mutableListOf<String>() - document.select(".nv-tags-list > a").forEach { + document.select(".post-tags > a").forEach { genres.add(it.text()) } manga.genre = genres.joinToString(", ") @@ -104,7 +104,7 @@ class EveriaClub() : ParsedHttpSource() { override fun pageListParse(document: Document): List<Page> { val pages = mutableListOf<Page>() document.select("noscript").remove() - document.select("article img").forEachIndexed { i, it -> + document.select(".entry-content img").forEachIndexed { i, it -> val itUrl = it.imgSrc pages.add(Page(i, itUrl, itUrl)) } diff --git a/src/all/freleinbooks/AndroidManifest.xml b/src/all/freleinbooks/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/all/freleinbooks/AndroidManifest.xml +++ b/src/all/freleinbooks/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/all/hennojin/AndroidManifest.xml b/src/all/hennojin/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/hennojin/AndroidManifest.xml +++ b/src/all/hennojin/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/holonometria/AndroidManifest.xml b/src/all/holonometria/AndroidManifest.xml new file mode 100644 index 0000000000..cc947c5679 --- /dev/null +++ b/src/all/holonometria/AndroidManifest.xml @@ -0,0 +1 @@ +<manifest /> diff --git a/src/all/holonometria/build.gradle b/src/all/holonometria/build.gradle new file mode 100644 index 0000000000..fe9ec48fa7 --- /dev/null +++ b/src/all/holonometria/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'HOLONOMETRIA' + pkgNameSuffix = 'all.holonometria' + extClass = '.HolonometriaFactory' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/holonometria/res/mipmap-hdpi/ic_launcher.png b/src/all/holonometria/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..bef1d2b85e Binary files /dev/null and b/src/all/holonometria/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/holonometria/res/mipmap-mdpi/ic_launcher.png b/src/all/holonometria/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..caaa59fadf Binary files /dev/null and b/src/all/holonometria/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/holonometria/res/mipmap-xhdpi/ic_launcher.png b/src/all/holonometria/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..fc8894dbd5 Binary files /dev/null and b/src/all/holonometria/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/holonometria/res/mipmap-xxhdpi/ic_launcher.png b/src/all/holonometria/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5f6f59ea8b Binary files /dev/null and b/src/all/holonometria/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/holonometria/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/holonometria/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f0f185ceac Binary files /dev/null and b/src/all/holonometria/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/holonometria/res/web_hi_res_512.png b/src/all/holonometria/res/web_hi_res_512.png new file mode 100644 index 0000000000..b3bd72e7b9 Binary files /dev/null and b/src/all/holonometria/res/web_hi_res_512.png differ diff --git a/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/Holonometria.kt b/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/Holonometria.kt new file mode 100644 index 0000000000..4c90583285 --- /dev/null +++ b/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/Holonometria.kt @@ -0,0 +1,150 @@ +package eu.kanade.tachiyomi.extension.all.holonometria + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class Holonometria( + override val lang: String, + private val langPath: String = "$lang/", +) : ParsedHttpSource() { + + override val name = "HOLONOMETRIA" + + override val baseUrl = "https://alt.hololive.tv" + + override val supportsLatest = false + + override val client = network.client.newBuilder() + .readTimeout(60, TimeUnit.SECONDS) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int) = + GET("$baseUrl/holonometria/$langPath", headers) + + override fun popularMangaSelector() = "#Story article:has(a[href*=/manga/])" + override fun popularMangaNextPageSelector() = null + + override fun popularMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + title = element.select(".ttl").text() + thumbnail_url = element.selectFirst("img")?.attr("abs:src") + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + GET("$baseUrl/holonometria/$langPath#${query.trim()}", headers) + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val search = response.request.url.fragment!! + + val entries = document.select(searchMangaSelector()) + .map(::searchMangaFromElement) + .filter { it.title.contains(search, true) } + + return MangasPage(entries, false) + } + + override fun searchMangaSelector() = popularMangaSelector() + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + title = document.select(".md-ttl__pages").text() + thumbnail_url = document.select(".mangainfo img").attr("abs:src") + description = document.select(".mangainfo aside").text() + val info = document.select(".mangainfo footer").html().split("<br>") + author = info.firstOrNull { desc -> manga.any { desc.contains(it, true) } } + ?.substringAfter(":") + ?.substringAfter(":") + ?.trim() + ?.replace("&", "&") + artist = info.firstOrNull { desc -> script.any { desc.contains(it, true) } } + ?.substringAfter(":") + ?.substringAfter(":") + ?.trim() + ?.replace("&", "&") + } + + override fun chapterListRequest(manga: SManga) = + paginatedChapterListRequest(manga.url, 1) + + private fun paginatedChapterListRequest(mangaUrl: String, page: Int) = + GET("$baseUrl$mangaUrl".removeSuffix("/") + if (page == 1) "/" else "/page/$page/", headers) + + override fun chapterListParse(response: Response): List<SChapter> { + val document = response.asJsoup() + val mangaUrl = response.request.url.toString() + .substringAfter(baseUrl) + .substringBefore("page/") + + val chapters = document.select(chapterListSelector()) + .map(::chapterFromElement) + .toMutableList() + + val lastPage = document.select(".pagenation-list a").last() + ?.text()?.toIntOrNull() ?: return chapters + + for (page in 2..lastPage) { + val request = paginatedChapterListRequest(mangaUrl, page) + val newDocument = client.newCall(request).execute().asJsoup() + + val moreChapters = newDocument.select(chapterListSelector()) + .map(::chapterFromElement) + + chapters.addAll(moreChapters) + } + + return chapters + } + + override fun chapterListSelector() = "#Archive article" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + name = element.select(".ttl").text() + date_upload = element.selectFirst(".data--date")?.text().parseDate() + scanlator = element.selectFirst(".data--category")?.text() + } + + private fun String?.parseDate(): Long { + return runCatching { + dateFormat.parse(this!!)!!.time + }.getOrDefault(0L) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select("#js-mangaviewer img").mapIndexed { idx, img -> + Page(idx, "", img.attr("abs:src")) + } + } + + companion object { + private val manga = listOf("manga", "gambar", "漫画") + private val script = listOf("script", "naskah", "脚本") + + private val dateFormat by lazy { + SimpleDateFormat("yy.MM.dd", Locale.ENGLISH) + } + } + + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used") + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") + override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") +} diff --git a/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/HolonometriaFactory.kt b/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/HolonometriaFactory.kt new file mode 100644 index 0000000000..08d64ab980 --- /dev/null +++ b/src/all/holonometria/src/eu/kanade/tachiyomi/extension/all/holonometria/HolonometriaFactory.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.all.holonometria + +import eu.kanade.tachiyomi.source.SourceFactory + +class HolonometriaFactory : SourceFactory { + override fun createSources() = listOf( + Holonometria("ja", ""), + Holonometria("en"), + Holonometria("id"), + ) +} diff --git a/src/all/imhentai/AndroidManifest.xml b/src/all/imhentai/AndroidManifest.xml index c0545790ee..a8e83cde1c 100644 --- a/src/all/imhentai/AndroidManifest.xml +++ b/src/all/imhentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -21,4 +20,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/all/imhentai/build.gradle b/src/all/imhentai/build.gradle index 75553cbc4c..1905c4fe24 100644 --- a/src/all/imhentai/build.gradle +++ b/src/all/imhentai/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'IMHentai' pkgNameSuffix = 'all.imhentai' extClass = '.IMHentaiFactory' - extVersionCode = 11 + extVersionCode = 14 isNsfw = true } diff --git a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt index b7d26c217b..339d807e23 100644 --- a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt +++ b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt @@ -31,6 +31,13 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH override val client: OkHttpClient = network.cloudflareClient .newBuilder() + .addInterceptor { chain -> + val request = chain.request() + val headers = request.headers.newBuilder() + .removeAll("Accept-Encoding") + .build() + chain.proceed(request.newBuilder().headers(headers).build()) + } .addInterceptor( fun(chain): Response { val response = chain.proceed(chain.request()) @@ -103,33 +110,41 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH private fun toBinary(boolean: Boolean) = if (boolean) "1" else "0" override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("key", query) - .addQueryParameter("page", page.toString()) - .addQueryParameter(getLanguageURIByName(imhLang).uri, toBinary(true)) // main language always enabled - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is LanguageFilters -> { - filter.state.forEach { - url.addQueryParameter(it.uri, toBinary(it.state)) + if (filters.any { it is LanguageFilters && it.state.any { it.name == LANGUAGE_SPEECHLESS && it.state } }) { // edge case for language = speechless + val url = "$baseUrl/language/speechless/".toHttpUrlOrNull()!!.newBuilder() + + if ((if (filters.isEmpty()) getFilterList() else filters).filterIsInstance<SortOrderFilter>()[0].state == 0) { + url.addPathSegment("popular") + } + return GET(url.toString()) + } else { + val url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("key", query) + .addQueryParameter("page", page.toString()) + .addQueryParameter(getLanguageURIByName(imhLang).uri, toBinary(true)) // main language always enabled + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is LanguageFilters -> { + filter.state.forEach { + url.addQueryParameter(it.uri, toBinary(it.state)) + } } - } - is CategoryFilters -> { - filter.state.forEach { - url.addQueryParameter(it.uri, toBinary(it.state)) + is CategoryFilters -> { + filter.state.forEach { + url.addQueryParameter(it.uri, toBinary(it.state)) + } } - } - is SortOrderFilter -> { - getSortOrderURIs().forEachIndexed { index, pair -> - url.addQueryParameter(pair.second, toBinary(filter.state == index)) + is SortOrderFilter -> { + getSortOrderURIs().forEachIndexed { index, pair -> + url.addQueryParameter(pair.second, toBinary(filter.state == index)) + } } + else -> {} } - else -> {} } + return GET(url.toString()) } - - return GET(url.toString()) } override fun searchMangaSelector(): String = popularMangaSelector() @@ -216,7 +231,8 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH in 527144..632481 -> "m4.imhentai.xxx" in 632482..816010 -> "m5.imhentai.xxx" in 816011..970098 -> "m6.imhentai.xxx" - else -> "m7.imhentai.xxx" + in 970099..1121113 -> "m7.imhentai.xxx" + else -> "m8.imhentai.xxx" } val images = json.parseToJsonElement( @@ -256,6 +272,7 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH SortOrderFilter(getSortOrderURIs(), sortOrderState), CategoryFilters(getCategoryURIs()), LanguageFilters(getLanguageURIs().filter { it.name != imhLang }), // exclude main lang + Filter.Header("Speechless language: ignores all filters except \"Popular\" and \"Latest\" in Sorting Filter"), ) private fun getCategoryURIs() = listOf( @@ -283,6 +300,7 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH LanguageFilter(LANGUAGE_KOREAN, "kr"), LanguageFilter(LANGUAGE_GERMAN, "de"), LanguageFilter(LANGUAGE_RUSSIAN, "ru"), + LanguageFilter(LANGUAGE_SPEECHLESS, ""), ) private fun getLanguageURIByName(name: String): LanguageFilter { @@ -304,5 +322,6 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH const val LANGUAGE_KOREAN = "Korean" const val LANGUAGE_GERMAN = "German" const val LANGUAGE_RUSSIAN = "Russian" + const val LANGUAGE_SPEECHLESS = "Speechless" } } diff --git a/src/all/izneo/AndroidManifest.xml b/src/all/izneo/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/izneo/AndroidManifest.xml +++ b/src/all/izneo/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/junmeitu/AndroidManifest.xml b/src/all/junmeitu/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/all/junmeitu/AndroidManifest.xml +++ b/src/all/junmeitu/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/all/kavita/AndroidManifest.xml b/src/all/kavita/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/kavita/AndroidManifest.xml +++ b/src/all/kavita/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/komga/AndroidManifest.xml b/src/all/komga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/komga/AndroidManifest.xml +++ b/src/all/komga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/komga/build.gradle b/src/all/komga/build.gradle index f04faa5c72..c603e66997 100644 --- a/src/all/komga/build.gradle +++ b/src/all/komga/build.gradle @@ -6,11 +6,7 @@ ext { extName = 'Komga' pkgNameSuffix = 'all.komga' extClass = '.KomgaFactory' - extVersionCode = 47 -} - -dependencies { - compileOnly libs.bundles.reactivex + extVersionCode = 50 } apply from: "$rootDir/common.gradle" diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt index 3bfe5742fd..4f0adb59fe 100644 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt @@ -20,7 +20,8 @@ import eu.kanade.tachiyomi.extension.all.komga.dto.PageWrapperDto import eu.kanade.tachiyomi.extension.all.komga.dto.ReadListDto import eu.kanade.tachiyomi.extension.all.komga.dto.SeriesDto import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.model.Filter @@ -182,7 +183,7 @@ open class Komga(private val suffix: String = "") : ConfigurableSource, Unmetere override fun fetchMangaDetails(manga: SManga): Observable<SManga> { return client.newCall(GET(manga.url, headers)) - .asObservableSuccess() + .asObservable() .map { response -> mangaDetailsParse(response).apply { initialized = true } } diff --git a/src/all/lanraragi/AndroidManifest.xml b/src/all/lanraragi/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/lanraragi/AndroidManifest.xml +++ b/src/all/lanraragi/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/lanraragi/build.gradle b/src/all/lanraragi/build.gradle index 6cf8118a94..137850830f 100644 --- a/src/all/lanraragi/build.gradle +++ b/src/all/lanraragi/build.gradle @@ -6,11 +6,7 @@ ext { extName = 'LANraragi' pkgNameSuffix = 'all.lanraragi' extClass = '.LANraragiFactory' - extVersionCode = 13 -} - -dependencies { - compileOnly libs.bundles.reactivex + extVersionCode = 15 } apply from: "$rootDir/common.gradle" diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt index 2afc25ab39..cec6020206 100644 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt +++ b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt @@ -240,7 +240,7 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme override fun headersBuilder() = Headers.Builder().apply { if (apiKey.isNotEmpty()) { - val apiKey64 = Base64.encodeToString(apiKey.toByteArray(), Base64.DEFAULT).trim() + val apiKey64 = Base64.encodeToString(apiKey.toByteArray(), Base64.NO_WRAP) add("Authorization", "Bearer $apiKey64") } } diff --git a/src/all/leagueoflegends/AndroidManifest.xml b/src/all/leagueoflegends/AndroidManifest.xml index 95481c3e28..8072ee00db 100644 --- a/src/all/leagueoflegends/AndroidManifest.xml +++ b/src/all/leagueoflegends/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension"/> +<manifest /> diff --git a/src/all/littlegarden/AndroidManifest.xml b/src/all/littlegarden/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/littlegarden/AndroidManifest.xml +++ b/src/all/littlegarden/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/luscious/AndroidManifest.xml b/src/all/luscious/AndroidManifest.xml index 7f8a6d5d3c..8072ee00db 100644 --- a/src/all/luscious/AndroidManifest.xml +++ b/src/all/luscious/AndroidManifest.xml @@ -1,4 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" -package="eu.kanade.tachiyomi.extension"> -</manifest> \ No newline at end of file +<manifest /> diff --git a/src/all/mangadex/AndroidManifest.xml b/src/all/mangadex/AndroidManifest.xml index 3fb6d85193..4faeb6138d 100644 --- a/src/all/mangadex/AndroidManifest.xml +++ b/src/all/mangadex/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/all/mangadex/assets/i18n/messages_en.properties b/src/all/mangadex/assets/i18n/messages_en.properties new file mode 100644 index 0000000000..435187ebc7 --- /dev/null +++ b/src/all/mangadex/assets/i18n/messages_en.properties @@ -0,0 +1,150 @@ +alternative_titles=Alternative titles: +alternative_titles_in_description=Alternative titles in description +alternative_titles_in_description_summary=Include a manga's alternative titles at the end of its description +block_group_by_uuid=Block groups by UUID +block_group_by_uuid_summary=Chapters from blocked groups will not show up in Latest or Manga feed. Enter as a Comma-separated list of group UUIDs +block_uploader_by_uuid=Block uploader by UUID +block_uploader_by_uuid_summary=Chapters from blocked uploaders will not show up in Latest or Manga feed. Enter as a Comma-separated list of uploader UUIDs +content=Content +content_gore=Gore +content_rating=Content rating +content_rating_erotica=Erotica +content_rating_genre=Content rating: %s +content_rating_pornographic=Pornographic +content_rating_safe=Safe +content_rating_suggestive=Suggestive +content_sexual_violence=Sexual violence +cover_quality=Cover quality +cover_quality_low=Low +cover_quality_medium=Medium +cover_quality_original=Original +data_saver=Data saver +data_saver_summary=Enables smaller, more compressed images +excluded_tags_mode=Excluded tags mode +filter_original_languages=Filter original languages +filter_original_languages_summary=Only show content that was originally published in the selected languages in both latest and browse +format=Format +format_adaptation=Adaptation +format_anthology=Anthology +format_award_winning=Award Winning +format_doujinshi=Doujinshi +format_fan_colored=Fan Colored +format_full_color=Full Color +format_long_strip=Long Strip +format_official_colored=Official Colored +format_oneshot=Oneshot +format_user_created=User Created +format_web_comic=Web Comic +format_yonkoma=4-Koma +genre=Genre +genre_action=Action +genre_adventure=Adventure +genre_boys_love=Boy's Love +genre_comedy=Comedy +genre_crime=Crime +genre_drama=Drama +genre_fantasy=Fantasy +genre_girls_love=Girl's Love +genre_historical=Historical +genre_horror=Horror +genre_isekai=Isekai +genre_magical_girls=Magical Girls +genre_mecha=Mecha +genre_medical=Medical +genre_mystery=Mystery +genre_philosophical=Philosophical +genre_romance=Romance +genre_sci_fi=Sci-Fi +genre_slice_of_life=Slice of Life +genre_sports=Sports +genre_superhero=Superhero +genre_thriller=Thriller +genre_tragedy=Tragedy +genre_wuxia=Wuxia +has_available_chapters=Has available chapters +included_tags_mode=Included tags mode +invalid_author_id=Not a valid author ID +invalid_manga_id=Not a valid manga ID +invalid_group_id=Not a valid group ID +invalid_uuids=The text contains invalid UUIDs +migrate_warning=Migrate this entry from MangaDex to MangaDex to update it +mode_and=And +mode_or=Or +no_group=No Group +no_series_in_list=No series in the list +original_language=Original language +original_language_filter_chinese=%s (Manhua) +original_language_filter_japanese=%s (Manga) +original_language_filter_korean=%s (Manhwa) +publication_demographic=Publication demographic +publication_demographic_josei=Josei +publication_demographic_none=None +publication_demographic_seinen=Seinen +publication_demographic_shoujo=Shoujo +publication_demographic_shounen=Shounen +sort=Sort +sort_alphabetic=Alphabetic +sort_chapter_uploaded_at=Chapter uploaded at +sort_content_created_at=Content created at +sort_content_info_updated_at=Content info updated at +sort_number_of_follows=Number of follows +sort_rating=Rating +sort_relevance=Relevance +sort_year=Year +standard_content_rating=Default content rating +standard_content_rating_summary=Show content with the selected ratings by default +standard_https_port=Use HTTPS port 443 only +standard_https_port_summary=Enable to only request image servers that use port 443. This allows users with stricter firewall restrictions to access MangaDex images +status=Status +status_cancelled=Cancelled +status_completed=Completed +status_hiatus=Hiatus +status_ongoing=Ongoing +tags_mode=Tags mode +theme=Theme +theme_aliens=Aliens +theme_animals=Animals +theme_cooking=Cooking +theme_crossdressing=Crossdressing +theme_delinquents=Delinquents +theme_demons=Demons +theme_gender_swap=Genderswap +theme_ghosts=Ghosts +theme_gyaru=Gyaru +theme_harem=Harem +theme_incest=Incest +theme_loli=Loli +theme_mafia=Mafia +theme_magic=Magic +theme_martial_arts=Martial Arts +theme_military=Military +theme_monster_girls=Monster Girls +theme_monsters=Monsters +theme_music=Music +theme_ninja=Ninja +theme_office_workers=Office Workers +theme_police=Police +theme_post_apocalyptic=Post-Apocalyptic +theme_psychological=Psychological +theme_reincarnation=Reincarnation +theme_reverse_harem=Reverse Harem +theme_samurai=Samurai +theme_school_life=School Life +theme_shota=Shota +theme_supernatural=Supernatural +theme_survival=Survival +theme_time_travel=Time Travel +theme_traditional_games=Traditional Games +theme_vampires=Vampires +theme_video_games=Video Games +theme_villainess=Villainess +theme_virtual_reality=Virtual Reality +theme_zombies=Zombies +try_using_first_volume_cover=Attempt to use the first volume cover as cover +try_using_first_volume_cover_summary=May need to manually refresh entries already in library. Otherwise, clear database to have new covers to show up. +unable_to_process_chapter_request=Unable to process Chapter request. HTTP code: %d +uploaded_by=Uploaded by %s +set_custom_useragent=Set custom User-Agent +set_custom_useragent_summary=Keep it as default +set_custom_useragent_dialog=\n\nSpecify a custom user agent\n After each modification, the application needs to be restarted.\n\nDefault value:\n%s +set_custom_useragent_error_invalid=Invalid User-Agent: %s diff --git a/src/all/mangadex/assets/i18n/messages_es.properties b/src/all/mangadex/assets/i18n/messages_es.properties new file mode 100644 index 0000000000..fc1dc27ab3 --- /dev/null +++ b/src/all/mangadex/assets/i18n/messages_es.properties @@ -0,0 +1,108 @@ +block_group_by_uuid=Bloquear grupos por UUID +block_group_by_uuid_summary=Los capítulos de los grupos bloqueados no aparecerán en Recientes o en el Feed de mangas. Introduce una coma para separar la lista de UUIDs +block_uploader_by_uuid=Bloquear uploader por UUID +block_uploader_by_uuid_summary=Los capítulos de los uploaders bloqueados no aparecerán en Recientes o en el Feed de mangas. Introduce una coma para separar la lista de UUIDs +content=Contenido +content_rating=Clasificación de contenido +content_rating_erotica=Erótico +content_rating_genre=Clasificación: %s +content_rating_pornographic=Pornográfico +content_rating_safe=Seguro +content_rating_suggestive=Sugestivo +content_sexual_violence=Violencia sexual +cover_quality=Calidad de la portada +cover_quality_low=Bajo +cover_quality_medium=Medio +data_saver=Ahorro de datos +data_saver_summary=Utiliza imágenes más pequeñas y más comprimidas +excluded_tags_mode=Modo de etiquetas excluidas +filter_original_languages=Filtrar por lenguajes +filter_original_languages_summary=Muestra solo el contenido publicado en los idiomas seleccionados en recientes y en la búsqueda +format=Formato +format_adaptation=Adaptación +format_anthology=Antología +format_award_winning=Ganador de premio +format_fan_colored=Coloreado por fans +format_full_color=Todo a color +format_long_strip=Tira larga +format_official_colored=Coloreo oficial +format_user_created=Creado por usuario +genre=Genero +genre_action=Acción +genre_adventure=Aventura +genre_comedy=Comedia +genre_crime=Crimen +genre_fantasy=Fantasia +genre_historical=Histórico +genre_magical_girls=Chicas mágicas +genre_medical=Medico +genre_mystery=Misterio +genre_philosophical=Filosófico +genre_sci_fi=Ciencia ficción +genre_slice_of_life=Recuentos de la vida +genre_sports=Deportes +genre_superhero=Superhéroes +genre_tragedy=Tragedia +has_available_chapters=Tiene capítulos disponibles +included_tags_mode=Modo de etiquetas incluidas +invalid_author_id=ID de autor inválida +invalid_group_id=ID de grupo inválida +migrate_warning=Migre la entrada MangaDex a MangaDex para actualizarla +mode_and=Y +mode_or=O +no_group=Sin grupo +no_series_in_list=No hay series en la lista +original_language=Lenguaje original +publication_demographic=Demografía +publication_demographic_none=Ninguna +sort=Ordenar +sort_alphabetic=Alfabeticamente +sort_chapter_uploaded_at=Capítulo subido en +sort_content_created_at=Contenido creado en +sort_content_info_updated_at=Información del contenido actualizada en +sort_number_of_follows=Número de seguidores +sort_rating=Calificación +sort_relevance=Relevancia +sort_year=Año +standard_content_rating=Clasificación de contenido por defecto +standard_content_rating_summary=Muestra el contenido con la clasificación de contenido seleccionada por defecto +standard_https_port=Utilizar el puerto 443 de HTTPS +standard_https_port_summary=Habilite esta opción solicitar las imágenes a los servidores que usan el puerto 443. Esto permite a los usuarios con restricciones estrictas de firewall acceder a las imagenes en MangaDex +status=Estado +status_cancelled=Cancelado +status_completed=Completado +status_hiatus=Pausado +status_ongoing=Publicandose +tags_mode=Modo de etiquetas +theme=Tema +theme_aliens=Alienígenas +theme_animals=Animales +theme_cooking=Cocina +theme_crossdressing=Travestismo +theme_delinquents=Delincuentes +theme_demons=Demonios +theme_gender_swap=Cambio de sexo +theme_ghosts=Fantasmas +theme_incest=Incesto +theme_magic=Magia +theme_martial_arts=Artes marciales +theme_military=Militar +theme_monster_girls=Chicas monstruo +theme_monsters=Monstruos +theme_music=Musica +theme_office_workers=Oficinistas +theme_police=Policial +theme_post_apocalyptic=Post-apocalíptico +theme_psychological=Psicológico +theme_reincarnation=Reencarnación +theme_reverse_harem=Harem inverso +theme_school_life=Vida escolar +theme_supernatural=Sobrenatural +theme_survival=Supervivencia +theme_time_travel=Viaje en el tiempo +theme_traditional_games=Juegos tradicionales +theme_vampires=Vampiros +theme_villainess=Villana +theme_virtual_reality=Realidad virtual +unable_to_process_chapter_request=No se ha podido procesar la solicitud del capítulo. Código HTTP: %d +uploaded_by=Subido por %s \ No newline at end of file diff --git a/src/all/mangadex/assets/i18n/messages_pt_br.properties b/src/all/mangadex/assets/i18n/messages_pt_br.properties new file mode 100644 index 0000000000..ecd4ca7be2 --- /dev/null +++ b/src/all/mangadex/assets/i18n/messages_pt_br.properties @@ -0,0 +1,119 @@ +alternative_titles=Títulos alternativos: +alternative_titles_in_description=Títulos alternativos na descrição +alternative_titles_in_description_summary=Inclui os títulos alternativos das séries no final de cada descrição +block_group_by_uuid=Bloquear grupos por UUID +block_group_by_uuid_summary=Capítulos de grupos bloqueados não irão aparecer no feed de Recentes ou Mangás. Digite uma lista de UUIDs dos grupos separados por vírgulas +block_uploader_by_uuid=Bloquear uploaders por UUID +block_uploader_by_uuid_summary=Capítulos de usuários bloqueados não irão aparecer no feed de Recentes ou Mangás. Digite uma lista de UUIDs dos usuários separados por vírgulas +content=Conteúdo +content_rating=Classificação de conteúdo +content_rating_erotica=Erótico +content_rating_genre=Classificação: %s +content_rating_pornographic=Pornográfico +content_rating_safe=Seguro +content_rating_suggestive=Sugestivo +content_sexual_violence=Violência sexual +cover_quality=Qualidade da capa +cover_quality_low=Baixa +cover_quality_medium=Média +data_saver=Economia de dados +data_saver_summary=Utiliza imagens menores e mais compactadas +excluded_tags_mode=Modo de exclusão de tags +filter_original_languages=Filtrar os idiomas originais +filter_original_languages_summary=Mostra somente conteúdos que foram publicados originalmente nos idiomas selecionados nas seções de recentes e navegar +format=Formato +format_adaptation=Adaptação +format_anthology=Antologia +format_award_winning=Premiado +format_fan_colored=Colorizado por fãs +format_full_color=Colorido +format_long_strip=Vertical +format_official_colored=Colorizado oficialmente +format_user_created=Criado por usuários +genre=Gênero +genre_action=Ação +genre_adventure=Aventura +genre_comedy=Comédia +genre_crime=Crime +genre_fantasy=Fantasia +genre_historical=Histórico +genre_magical_girls=Garotas mágicas +genre_medical=Médico +genre_mystery=Mistério +genre_philosophical=Filosófico +genre_sci_fi=Ficção científica +genre_slice_of_life=Cotidiano +genre_sports=Esportes +genre_superhero=Super-heroi +genre_tragedy=Tragédia +has_available_chapters=Há capítulos disponíveis +included_tags_mode=Modo de inclusão de tags +invalid_author_id=ID do autor inválido +invalid_manga_id=ID do mangá inválido +invalid_group_id=ID do grupo inválido +invalid_uuids=O texto contém UUIDs inválidos +migrate_warning=Migre esta entrada do MangaDex para o MangaDex para atualizar +mode_and=E +mode_or=Ou +no_group=Sem grupo +no_series_in_list=Sem séries na lista +original_language=Idioma original +original_language_filter_japanese=%s (Mangá) +publication_demographic=Demografia da publicação +publication_demographic_none=Nenhuma +sort=Ordenar +sort_alphabetic=Alfabeticamente +sort_chapter_uploaded_at=Upload do capítulo +sort_content_created_at=Criação do conteúdo +sort_content_info_updated_at=Atualização das informações +sort_number_of_follows=Número de seguidores +sort_rating=Nota +sort_relevance=Relevância +sort_year=Ano de lançamento +standard_content_rating=Classificação de conteúdo padrão +standard_content_rating_summary=Mostra os conteúdos com as classificações selecionadas por padrão +standard_https_port=Utilizar somente a porta 443 do HTTPS +standard_https_port_summary=Ative para fazer requisições em somente servidores de imagem que usem a porta 443. Isso permite com que usuários com regras mais restritas de firewall possam acessar as imagens do MangaDex. +status=Estado +status_cancelled=Cancelado +status_completed=Completo +status_hiatus=Hiato +status_ongoing=Em andamento +tags_mode=Modo das tags +theme=Tema +theme_aliens=Alienígenas +theme_animals=Animais +theme_cooking=Culinária +theme_delinquents=Delinquentes +theme_demons=Demônios +theme_gender_swap=Troca de gêneros +theme_ghosts=Fantasmas +theme_harem=Harém +theme_incest=Incesto +theme_mafia=Máfia +theme_magic=Magia +theme_martial_arts=Artes marciais +theme_military=Militar +theme_monster_girls=Garotas monstro +theme_monsters=Monstros +theme_music=Musical +theme_office_workers=Funcionários de escritório +theme_police=Policial +theme_post_apocalyptic=Pós-apocalíptico +theme_psychological=Psicológico +theme_reincarnation=Reencarnação +theme_reverse_harem=Harém reverso +theme_school_life=Vida escolar +theme_supernatural=Sobrenatural +theme_survival=Sobrevivência +theme_time_travel=Viagem no tempo +theme_traditional_games=Jogos tradicionais +theme_vampires=Vampiros +theme_video_games=Videojuegos +theme_villainess=Villainess +theme_virtual_reality=Realidade virtual +theme_zombies=Zumbis +try_using_first_volume_cover=Tentar usar a capa do primeiro volume como capa +try_using_first_volume_cover_summary=Pode ser necessário atualizar os itens já adicionados na biblioteca. Alternativamente, limpe o banco de dados para as novas capas aparecerem. +unable_to_process_chapter_request=Não foi possível processar a requisição do capítulo. Código HTTP: %d +uploaded_by=Enviado por %s \ No newline at end of file diff --git a/src/all/mangadex/assets/i18n/messages_ru.properties b/src/all/mangadex/assets/i18n/messages_ru.properties new file mode 100644 index 0000000000..fbc49b49f0 --- /dev/null +++ b/src/all/mangadex/assets/i18n/messages_ru.properties @@ -0,0 +1,138 @@ +block_group_by_uuid=Заблокировать группы по UUID +block_group_by_uuid_summary=Главы от заблокированных групп не будут отображаться в последних обновлениях и в списке глав тайтла. Введите через запятую список UUID групп. +block_uploader_by_uuid=Заблокировать загрузчика по UUID +block_uploader_by_uuid_summary=Главы от заблокированных загрузчиков не будут отображаться в последних обновлениях и в списке глав тайтла. Введите через запятую список UUID загрузчиков. +content=Неприемлемый контент +content_gore=Жестокость +content_rating=Рейтинг контента +content_rating_erotica=Эротический +content_rating_genre=Рейтинг контента: %s +content_rating_pornographic=Порнографический +content_rating_safe=Безопасный +content_rating_suggestive=Намекающий +content_sexual_violence=Сексуальное насилие +cover_quality=Качество обложки +cover_quality_low=Низкое +cover_quality_medium=Среднее +cover_quality_original=Оригинальное +data_saver=Экономия трафика +data_saver_summary=Использует меньшие по размеру, сжатые изображения +excluded_tags_mode=Исключая +filter_original_languages=Фильтр по языку оригинала +filter_original_languages_summary=Показывать тайтлы которые изначально были выпущены только в выбранных языках в последних обновлениях и при поиске +format=Формат +format_adaptation=Адаптация +format_anthology=Антология +format_award_winning=Отмеченный наградами +format_doujinshi=Додзинси +format_fan_colored=Раскрашенная фанатами +format_full_color=В цвете +format_long_strip=Веб +format_official_colored=Официально раскрашенная +format_oneshot=Сингл +format_user_created=Созданная пользователями +format_web_comic=Веб-комикс +format_yonkoma=Ёнкома +genre=Жанр +genre_action=Боевик +genre_adventure=Приключения +genre_boys_love=BL +genre_comedy=Комедия +genre_crime=Криминал +genre_drama=Драма +genre_fantasy=Фэнтези +genre_girls_love=GL +genre_historical=История +genre_horror=Ужасы +genre_isekai=Исекай +genre_magical_girls=Махо-сёдзё +genre_mecha=Меха +genre_medical=Медицина +genre_mystery=Мистика +genre_philosophical=Философия +genre_romance=Романтика +genre_sci_fi=Научная фантастика +genre_slice_of_life=Повседневность +genre_sports=Спорт +genre_superhero=Супергерои +genre_thriller=Триллер +genre_tragedy=Трагедия +genre_wuxia=Культивация +has_available_chapters=Есть главы +included_tags_mode=Включая +invalid_author_id=Недействительный ID автора +invalid_group_id=Недействительный ID группы +mode_and=И +mode_or=Или +no_group=Нет группы +no_series_in_list=Лист пуст +original_language=Язык оригинала +original_language_filter_chinese=%s (Манхуа) +original_language_filter_japanese=%s (Манга) +original_language_filter_korean=%s (Манхва) +publication_demographic=Целевая аудитория +publication_demographic_josei=Дзёсэй +publication_demographic_none=Нет +publication_demographic_seinen=Сэйнэн +publication_demographic_shoujo=Сёдзё +publication_demographic_shounen=Сёнэн +sort=Сортировать по +sort_alphabetic=Алфавиту +sort_chapter_uploaded_at=Загруженной главе +sort_content_created_at=По дате создания +sort_content_info_updated_at=По дате обновления +sort_number_of_follows=Количеству фолловеров +sort_rating=Популярности +sort_relevance=Лучшему соответствию +sort_year=Год +standard_content_rating=Рейтинг контента по умолчанию +standard_content_rating_summary=Показывать контент с выбранным рейтингом по умолчанию +standard_https_port=Использовать только HTTPS порт 443 +standard_https_port_summary=Запрашивает изображения только с серверов которые используют порт 443. Это позволяет пользователям со строгими правилами брандмауэра загружать изображения с MangaDex. +status=Статус +status_cancelled=Отменён +status_completed=Завершён +status_hiatus=Приостановлен +status_ongoing=Онгоинг +tags_mode=Режим поиска +theme=Теги +theme_aliens=Инопланетяне +theme_animals=Животные +theme_cooking=Животные +theme_crossdressing=Кроссдрессинг +theme_delinquents=Хулиганы +theme_demons=Демоны +theme_gender_swap=Смена гендера +theme_ghosts=Призраки +theme_gyaru=Гяру +theme_harem=Гарем +theme_incest=Инцест +theme_loli=Лоли +theme_mafia=Мафия +theme_magic=Магия +theme_martial_arts=Боевые исскуства +theme_military=Военные +theme_monster_girls=Монстродевушки +theme_monsters=Монстры +theme_music=Музыка +theme_ninja=Ниндзя +theme_office_workers=Офисные работники +theme_police=Полиция +theme_post_apocalyptic=Постапокалиптика +theme_psychological=Психология +theme_reincarnation=Реинкарнация +theme_reverse_harem=Обратный гарем +theme_samurai=Самураи +theme_school_life=Школа +theme_shota=Шота +theme_supernatural=Сверхъестественное +theme_survival=Выживание +theme_time_travel=Путешествие во времени +theme_traditional_games=Путешествие во времени +theme_vampires=Вампиры +theme_video_games=Видеоигры +theme_villainess=Злодейка +theme_virtual_reality=Виртуальная реальность +theme_zombies=Зомби +unable_to_process_chapter_request=Не удалось обработать ссылку на главу. Ошибка: %d +uploaded_by=Загрузил %s \ No newline at end of file diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle index af4c1d2e27..101966ce69 100644 --- a/src/all/mangadex/build.gradle +++ b/src/all/mangadex/build.gradle @@ -6,8 +6,12 @@ ext { extName = 'MangaDex' pkgNameSuffix = 'all.mangadex' extClass = '.MangaDexFactory' - extVersionCode = 183 + extVersionCode = 192 isNsfw = true } +dependencies { + implementation(project(":lib-i18n")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt index b7f8eaa11a..8fc471311b 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MDConstants.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.extension.all.mangadex +import eu.kanade.tachiyomi.lib.i18n.Intl import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone +import kotlin.time.Duration.Companion.minutes object MDConstants { @@ -31,7 +33,7 @@ object MDConstants { const val atHomePostUrl = "https://api.mangadex.network/report" val whitespaceRegex = "\\s".toRegex() - const val mdAtHomeTokenLifespan = 5 * 60 * 1000 + val mdAtHomeTokenLifespan = 5.minutes.inWholeMilliseconds val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+SSS", Locale.US) .apply { timeZone = TimeZone.getTimeZone("UTC") } @@ -49,8 +51,8 @@ object MDConstants { return "${coverQualityPref}_$dexLang" } - fun getCoverQualityPreferenceEntries(intl: MangaDexIntl) = - arrayOf(intl.coverQualityOriginal, intl.coverQualityMedium, intl.coverQualityLow) + fun getCoverQualityPreferenceEntries(intl: Intl) = + arrayOf(intl["cover_quality_original"], intl["cover_quality_medium"], intl["cover_quality_low"]) fun getCoverQualityPreferenceEntryValues() = arrayOf("", ".512.jpg", ".256.jpg") @@ -74,6 +76,12 @@ object MDConstants { const val contentRatingPrefValErotica = "erotica" const val contentRatingPrefValPornographic = "pornographic" val contentRatingPrefDefaults = setOf(contentRatingPrefValSafe, contentRatingPrefValSuggestive) + val allContentRatings = setOf( + contentRatingPrefValSafe, + contentRatingPrefValSuggestive, + contentRatingPrefValErotica, + contentRatingPrefValPornographic, + ) fun getContentRatingPrefKey(dexLang: String): String { return "${contentRatingPref}_$dexLang" @@ -130,6 +138,13 @@ object MDConstants { return "${altTitlesInDescPref}_$dexLang" } + private const val customUserAgentPref = "customUserAgent" + fun getCustomUserAgentPrefKey(dexLang: String): String { + return "${customUserAgentPref}_$dexLang" + } + + val defaultUserAgent = "Tachiyomi " + System.getProperty("http.agent") + private const val tagGroupContent = "content" private const val tagGroupFormat = "format" private const val tagGroupGenre = "genre" diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt index 2bb9a1495e..06b72b1d03 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt @@ -2,11 +2,14 @@ package eu.kanade.tachiyomi.extension.all.mangadex import android.app.Application import android.content.SharedPreferences +import android.os.Build +import android.widget.Toast import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.AppInfo import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.AggregateVolume import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto @@ -34,7 +37,6 @@ import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Request import okhttp3.Response import rx.Observable @@ -42,9 +44,8 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date -abstract class MangaDex(final override val lang: String, private val dexLang: String) : - ConfigurableSource, - HttpSource() { +abstract class MangaDex(final override val lang: String, private val dexLang: String = lang) : + ConfigurableSource, HttpSource() { override val name = MangaDexIntl.MANGADEX_NAME @@ -58,13 +59,23 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St private val helper = MangaDexHelper(lang) - final override fun headersBuilder() = Headers.Builder() - .add("Referer", "$baseUrl/") - .add("User-Agent", "Tachiyomi " + System.getProperty("http.agent")) + final override fun headersBuilder(): Headers.Builder { + val extraHeader = "Android/${Build.VERSION.RELEASE} " + + "Tachiyomi/${AppInfo.getVersionName()} " + + "MangaDex/1.4.190" + + val builder = super.headersBuilder().apply { + set("Referer", "$baseUrl/") + set("Extra", extraHeader) + } + + return builder + } override val client = network.client.newBuilder() .rateLimit(3) .addInterceptor(MdAtHomeReportInterceptor(network.client, headers)) + .addInterceptor(MdUserAgentInterceptor(preferences, dexLang)) .build() init { @@ -82,12 +93,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("includes[]", MDConstants.coverArt) .addQueryParameter("contentRating[]", preferences.contentRating) .addQueryParameter("originalLanguage[]", preferences.originalLanguages) + .build() - return GET( - url = url.build().toString(), - headers = headers, - cache = CacheControl.FORCE_NETWORK, - ) + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun popularMangaParse(response: Response): MangasPage { @@ -96,30 +104,50 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St } val mangaListDto = response.parseAs<MangaListDto>() - val hasMoreResults = mangaListDto.limit + mangaListDto.offset < mangaListDto.total val coverSuffix = preferences.coverQuality val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty() val mangaList = mangaListDto.data.map { mangaDataDto -> - val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships - .filterIsInstance<CoverArtDto>() - .firstOrNull() - ?.attributes?.fileName + val fileName = firstVolumeCovers.getOrElse(mangaDataDto.id) { + mangaDataDto.relationships + .firstInstanceOrNull<CoverArtDto>() + ?.attributes?.fileName + } helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) } - return MangasPage(mangaList, hasMoreResults) + return MangasPage(mangaList, mangaListDto.hasNextPage) } // Latest manga section + override fun latestUpdatesRequest(page: Int): Request { + val url = MDConstants.apiChapterUrl.toHttpUrl().newBuilder() + .addQueryParameter("offset", helper.getLatestChapterOffset(page)) + .addQueryParameter("limit", MDConstants.latestChapterLimit.toString()) + .addQueryParameter("translatedLanguage[]", dexLang) + .addQueryParameter("order[publishAt]", "desc") + .addQueryParameter("includeFutureUpdates", "0") + .addQueryParameter("originalLanguage[]", preferences.originalLanguages) + .addQueryParameter("contentRating[]", preferences.contentRating) + .addQueryParameter( + "excludedGroups[]", + MDConstants.defaultBlockedGroups + preferences.blockedGroups, + ) + .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) + .addQueryParameter("includeFuturePublishAt", "0") + .addQueryParameter("includeEmptyPages", "0") + .build() + + return GET(url, headers, CacheControl.FORCE_NETWORK) + } + /** * The API endpoint can't sort by date yet, so not implemented. */ override fun latestUpdatesParse(response: Response): MangasPage { val chapterListDto = response.parseAs<ChapterListDto>() - val hasMoreResults = chapterListDto.limit + chapterListDto.offset < chapterListDto.total val mangaIds = chapterListDto.data .flatMap { it.relationships } @@ -128,13 +156,14 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .distinct() .toSet() - val mangaUrl = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder() + val mangaApiUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() .addQueryParameter("includes[]", MDConstants.coverArt) .addQueryParameter("limit", mangaIds.size.toString()) .addQueryParameter("contentRating[]", preferences.contentRating) .addQueryParameter("ids[]", mangaIds) + .build() - val mangaRequest = GET(mangaUrl.build().toString(), headers, CacheControl.FORCE_NETWORK) + val mangaRequest = GET(mangaApiUrl, headers, CacheControl.FORCE_NETWORK) val mangaResponse = client.newCall(mangaRequest).execute() val mangaListDto = mangaResponse.parseAs<MangaListDto>() val firstVolumeCovers = fetchFirstVolumeCovers(mangaListDto.data).orEmpty() @@ -144,34 +173,15 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val coverSuffix = preferences.coverQuality val mangaList = mangaIds.mapNotNull { mangaDtoMap[it] }.map { mangaDataDto -> - val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships - .filterIsInstance<CoverArtDto>() - .firstOrNull() - ?.attributes?.fileName + val fileName = firstVolumeCovers.getOrElse(mangaDataDto.id) { + mangaDataDto.relationships + .firstInstanceOrNull<CoverArtDto>() + ?.attributes?.fileName + } helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) } - return MangasPage(mangaList, hasMoreResults) - } - - override fun latestUpdatesRequest(page: Int): Request { - val url = MDConstants.apiChapterUrl.toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("offset", helper.getLatestChapterOffset(page)) - .addQueryParameter("limit", MDConstants.latestChapterLimit.toString()) - .addQueryParameter("translatedLanguage[]", dexLang) - .addQueryParameter("order[publishAt]", "desc") - .addQueryParameter("includeFutureUpdates", "0") - .addQueryParameter("originalLanguage[]", preferences.originalLanguages) - .addQueryParameter("contentRating[]", preferences.contentRating) - .addQueryParameter( - "excludedGroups[]", - MDConstants.defaultBlockedGroups + preferences.blockedGroups, - ) - .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) - .addQueryParameter("includeFuturePublishAt", "0") - .addQueryParameter("includeEmptyPages", "0") - - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return MangasPage(mangaList, chapterListDto.hasNextPage) } // Search manga section @@ -207,7 +217,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St ), ) .asObservableSuccess() - .map { searchMangaListParse(it, page) } + .map { searchMangaListParse(it, page, filters) } else -> super.fetchSearchManga(page, query.trim(), filters) } @@ -218,38 +228,42 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .asObservable() .map { response -> if (response.isSuccessful.not()) { - throw Exception(helper.intl.unableToProcessChapterRequest(response.code)) + throw Exception(helper.intl.format("unable_to_process_chapter_request", response.code)) } response.parseAs<ChapterDto>().data!!.relationships - .filterIsInstance<MangaDataDto>() - .firstOrNull()!!.id + .firstInstanceOrNull<MangaDataDto>()!!.id } } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.startsWith(MDConstants.prefixIdSearch)) { + val mangaId = query.removePrefix(MDConstants.prefixIdSearch) + + if (!helper.containsUuid(mangaId)) { + throw Exception(helper.intl["invalid_manga_id"]) + } + + val url = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() + .addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch)) + .addQueryParameter("includes[]", MDConstants.coverArt) + .addQueryParameter("contentRating[]", MDConstants.allContentRatings) + .build() + + return GET(url, headers, CacheControl.FORCE_NETWORK) + } + val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() .addQueryParameter("limit", MDConstants.mangaLimit.toString()) .addQueryParameter("offset", helper.getMangaListOffset(page)) .addQueryParameter("includes[]", MDConstants.coverArt) when { - query.startsWith(MDConstants.prefixIdSearch) -> { - val url = MDConstants.apiMangaUrl.toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("ids[]", query.removePrefix(MDConstants.prefixIdSearch)) - .addQueryParameter("includes[]", MDConstants.coverArt) - .addQueryParameter("contentRating[]", "safe") - .addQueryParameter("contentRating[]", "suggestive") - .addQueryParameter("contentRating[]", "erotica") - .addQueryParameter("contentRating[]", "pornographic") - - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) - } - query.startsWith(MDConstants.prefixGrpSearch) -> { val groupId = query.removePrefix(MDConstants.prefixGrpSearch) + if (!helper.containsUuid(groupId)) { - throw Exception(helper.intl.invalidGroupId) + throw Exception(helper.intl["invalid_group_id"]) } tempUrl.addQueryParameter("group", groupId) @@ -257,8 +271,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St query.startsWith(MDConstants.prefixAuthSearch) -> { val authorId = query.removePrefix(MDConstants.prefixAuthSearch) + if (!helper.containsUuid(authorId)) { - throw Exception(helper.intl.invalidAuthorId) + throw Exception(helper.intl["invalid_author_id"]) } tempUrl.addQueryParameter("authorOrArtist", authorId) @@ -288,18 +303,18 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St return GET("${MDConstants.apiListUrl}/$list", headers, CacheControl.FORCE_NETWORK) } - private fun searchMangaListParse(response: Response, page: Int): MangasPage { + private fun searchMangaListParse(response: Response, page: Int, filters: FilterList): MangasPage { val listDto = response.parseAs<ListDto>() val listDtoFiltered = listDto.data!!.relationships.filterIsInstance<MangaDataDto>() val amount = listDtoFiltered.count() if (amount < 1) { - throw Exception(helper.intl.noSeriesInList) + throw Exception(helper.intl["no_series_in_list"]) } val minIndex = (page - 1) * MDConstants.mangaLimit - val url = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() + val tempUrl = MDConstants.apiMangaUrl.toHttpUrl().newBuilder() .addQueryParameter("limit", MDConstants.mangaLimit.toString()) .addQueryParameter("offset", "0") .addQueryParameter("includes[]", MDConstants.coverArt) @@ -309,13 +324,20 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .map(MangaDataDto::id) .toSet() - url.addQueryParameter("ids[]", ids) + tempUrl.addQueryParameter("ids[]", ids) - val mangaRequest = GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + val finalUrl = helper.mdFilters.addFiltersToUrl( + url = tempUrl, + filters = filters.ifEmpty { getFilterList() }, + dexLang = dexLang, + ) + + val mangaRequest = GET(finalUrl, headers, CacheControl.FORCE_NETWORK) val mangaResponse = client.newCall(mangaRequest).execute() val mangaList = searchMangaListParse(mangaResponse) - val hasNextPage = amount.toFloat() / MDConstants.mangaLimit - (page.toFloat() - 1) > 1 + val hasNextPage = amount.toFloat() / MDConstants.mangaLimit - (page.toFloat() - 1) > 1 && + ids.size == MDConstants.mangaLimit return MangasPage(mangaList, hasNextPage) } @@ -334,10 +356,11 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val coverSuffix = preferences.coverQuality val mangaList = mangaListDto.data.map { mangaDataDto -> - val fileName = firstVolumeCovers[mangaDataDto.id] ?: mangaDataDto.relationships - .filterIsInstance<CoverArtDto>() - .firstOrNull() - ?.attributes?.fileName + val fileName = firstVolumeCovers.getOrElse(mangaDataDto.id) { + mangaDataDto.relationships + .firstInstanceOrNull<CoverArtDto>() + ?.attributes?.fileName + } helper.createBasicManga(mangaDataDto, fileName, coverSuffix, dexLang) } @@ -345,7 +368,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St } private fun searchMangaUploaderRequest(page: Int, uploader: String): Request { - val url = MDConstants.apiChapterUrl.toHttpUrlOrNull()!!.newBuilder() + val url = MDConstants.apiChapterUrl.toHttpUrl().newBuilder() .addQueryParameter("offset", helper.getLatestChapterOffset(page)) .addQueryParameter("limit", MDConstants.latestChapterLimit.toString()) .addQueryParameter("translatedLanguage[]", dexLang) @@ -361,18 +384,15 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St MDConstants.defaultBlockedGroups + preferences.blockedGroups, ) .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } // Manga Details section override fun getMangaUrl(manga: SManga): String { - // TODO: Remove once redirect for /manga is fixed. - val title = manga.title - val url = "${baseUrl}${manga.url.replace("manga", "title")}" - - return "$url/" + helper.titleToSlug(title) + return baseUrl + manga.url + "/" + helper.titleToSlug(manga.title) } /** @@ -382,15 +402,16 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St */ override fun mangaDetailsRequest(manga: SManga): Request { if (!helper.containsUuid(manga.url.trim())) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } val url = (MDConstants.apiUrl + manga.url).toHttpUrl().newBuilder() .addQueryParameter("includes[]", MDConstants.coverArt) .addQueryParameter("includes[]", MDConstants.author) .addQueryParameter("includes[]", MDConstants.artist) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun mangaDetailsParse(response: Response): SManga { @@ -442,9 +463,9 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St return null } - val mangaMap = mangaList.associate { it.id to it.attributes!! } - .filterValues { !it.originalLanguage.isNullOrEmpty() } - val locales = mangaList.mapNotNull { it.attributes!!.originalLanguage }.distinct() + val safeMangaList = mangaList.filterNot { it.attributes?.originalLanguage.isNullOrEmpty() } + val mangaMap = safeMangaList.associate { it.id to it.attributes!! } + val locales = safeMangaList.mapNotNull { it.attributes!!.originalLanguage }.distinct() val limit = (mangaMap.size * locales.size).coerceAtMost(100) val apiUrl = "${MDConstants.apiUrl}/cover".toHttpUrl().newBuilder() @@ -453,7 +474,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .addQueryParameter("locales[]", locales.toSet()) .addQueryParameter("limit", limit.toString()) .addQueryParameter("offset", "0") - .toString() + .build() val result = runCatching { client.newCall(GET(apiUrl, headers)).execute().parseAs<CoverArtListDto>().data @@ -462,10 +483,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val covers = result.getOrNull() ?: return null return covers - .groupBy { - it.relationships.filterIsInstance<MangaDataDto>() - .firstOrNull()!!.id - } + .groupBy { it.relationships.firstInstanceOrNull<MangaDataDto>()!!.id } .mapValues { it.value.find { c -> c.attributes?.locale == mangaMap[it.key]?.originalLanguage } } @@ -482,7 +500,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St */ override fun chapterListRequest(manga: SManga): Request { if (!helper.containsUuid(manga.url)) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } return paginatedChapterListRequest(helper.getUUIDFromUrl(manga.url), 0) @@ -493,14 +511,12 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St */ private fun paginatedChapterListRequest(mangaId: String, offset: Int): Request { val url = helper.getChapterEndpoint(mangaId, offset, dexLang).toHttpUrl().newBuilder() - .addQueryParameter("contentRating[]", "safe") - .addQueryParameter("contentRating[]", "suggestive") - .addQueryParameter("contentRating[]", "erotica") - .addQueryParameter("contentRating[]", "pornographic") + .addQueryParameter("contentRating[]", MDConstants.allContentRatings) .addQueryParameter("excludedGroups[]", preferences.blockedGroups) .addQueryParameter("excludedUploaders[]", preferences.blockedUploaders) + .build() - return GET(url.build().toString(), headers, CacheControl.FORCE_NETWORK) + return GET(url, headers, CacheControl.FORCE_NETWORK) } override fun chapterListParse(response: Response): List<SChapter> { @@ -516,21 +532,20 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St .substringBefore("/feed") .substringAfter("${MDConstants.apiMangaUrl}/") - val limit = chapterListResponse.limit - var offset = chapterListResponse.offset - - var hasMoreResults = (limit + offset) < chapterListResponse.total + var hasNextPage = chapterListResponse.hasNextPage // Max results that can be returned is 500 so need to make more API - // calls if limit + offset > total chapters - while (hasMoreResults) { - offset += limit + // calls if the chapter list response has a next page. + while (hasNextPage) { + offset += chapterListResponse.limit + val newRequest = paginatedChapterListRequest(mangaId, offset) val newResponse = client.newCall(newRequest).execute() val newChapterList = newResponse.parseAs<ChapterListDto>() chapterListResults.addAll(newChapterList.data) - hasMoreResults = (limit + offset) < newChapterList.total + + hasNextPage = newChapterList.hasNextPage } return chapterListResults @@ -542,7 +557,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St override fun pageListRequest(chapter: SChapter): Request { if (!helper.containsUuid(chapter.url)) { - throw Exception(helper.intl.migrateWarning) + throw Exception(helper.intl["migrate_warning"]) } val chapterId = chapter.url.substringAfter("/chapter/") @@ -586,7 +601,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St override fun setupPreferenceScreen(screen: PreferenceScreen) { val coverQualityPref = ListPreference(screen.context).apply { key = MDConstants.getCoverQualityPreferenceKey(dexLang) - title = helper.intl.coverQuality + title = helper.intl["cover_quality"] entries = MDConstants.getCoverQualityPreferenceEntries(helper.intl) entryValues = MDConstants.getCoverQualityPreferenceEntryValues() setDefaultValue(MDConstants.getCoverQualityPreferenceDefaultValue()) @@ -605,8 +620,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val tryUsingFirstVolumeCoverPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getTryUsingFirstVolumeCoverPrefKey(dexLang) - title = helper.intl.tryUsingFirstVolumeCover - summary = helper.intl.tryUsingFirstVolumeCoverSummary + title = helper.intl["try_using_first_volume_cover"] + summary = helper.intl["try_using_first_volume_cover_summary"] setDefaultValue(MDConstants.tryUsingFirstVolumeCoverDefault) setOnPreferenceChangeListener { _, newValue -> @@ -620,8 +635,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val dataSaverPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getDataSaverPreferenceKey(dexLang) - title = helper.intl.dataSaver - summary = helper.intl.dataSaverSummary + title = helper.intl["data_saver"] + summary = helper.intl["data_saver_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> @@ -635,8 +650,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val standardHttpsPortPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getStandardHttpsPreferenceKey(dexLang) - title = helper.intl.standardHttpsPort - summary = helper.intl.standardHttpsPortSummary + title = helper.intl["standard_https_port"] + summary = helper.intl["standard_https_port_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> @@ -650,13 +665,13 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val contentRatingPref = MultiSelectListPreference(screen.context).apply { key = MDConstants.getContentRatingPrefKey(dexLang) - title = helper.intl.standardContentRating - summary = helper.intl.standardContentRatingSummary + title = helper.intl["standard_content_rating"] + summary = helper.intl["standard_content_rating_summary"] entries = arrayOf( - helper.intl.contentRatingSafe, - helper.intl.contentRatingSuggestive, - helper.intl.contentRatingErotica, - helper.intl.contentRatingPornographic, + helper.intl["content_rating_safe"], + helper.intl["content_rating_suggestive"], + helper.intl["content_rating_erotica"], + helper.intl["content_rating_pornographic"], ) entryValues = arrayOf( MDConstants.contentRatingPrefValSafe, @@ -677,8 +692,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val originalLanguagePref = MultiSelectListPreference(screen.context).apply { key = MDConstants.getOriginalLanguagePrefKey(dexLang) - title = helper.intl.filterOriginalLanguages - summary = helper.intl.filterOriginalLanguagesSummary + title = helper.intl["filter_original_languages"] + summary = helper.intl["filter_original_languages_summary"] entries = arrayOf( helper.intl.languageDisplayName(MangaDexIntl.JAPANESE), helper.intl.languageDisplayName(MangaDexIntl.CHINESE), @@ -702,8 +717,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val blockedGroupsPref = EditTextPreference(screen.context).apply { key = MDConstants.getBlockedGroupsPrefKey(dexLang) - title = helper.intl.blockGroupByUuid - summary = helper.intl.blockGroupByUuidSummary + title = helper.intl["block_group_by_uuid"] + summary = helper.intl["block_group_by_uuid_summary"] setOnBindEditTextListener(helper::setupEditTextUuidValidator) @@ -716,8 +731,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val blockedUploaderPref = EditTextPreference(screen.context).apply { key = MDConstants.getBlockedUploaderPrefKey(dexLang) - title = helper.intl.blockUploaderByUuid - summary = helper.intl.blockUploaderByUuidSummary + title = helper.intl["block_uploader_by_uuid"] + summary = helper.intl["block_uploader_by_uuid_summary"] setOnBindEditTextListener(helper::setupEditTextUuidValidator) @@ -730,8 +745,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St val altTitlesInDescPref = SwitchPreferenceCompat(screen.context).apply { key = MDConstants.getAltTitlesInDescPrefKey(dexLang) - title = helper.intl.altTitlesInDesc - summary = helper.intl.altTitlesInDescSummary + title = helper.intl["alternative_titles_in_description"] + summary = helper.intl["alternative_titles_in_description_summary"] setDefaultValue(false) setOnPreferenceChangeListener { _, newValue -> @@ -743,6 +758,30 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St } } + val userAgentPref = EditTextPreference(screen.context).apply { + key = MDConstants.getCustomUserAgentPrefKey(dexLang) + title = helper.intl["set_custom_useragent"] + summary = helper.intl["set_custom_useragent_summary"] + dialogMessage = helper.intl.format( + "set_custom_useragent_dialog", + MDConstants.defaultUserAgent, + ) + + setDefaultValue(MDConstants.defaultUserAgent) + + setOnPreferenceChangeListener { _, newValue -> + try { + Headers.Builder().add("User-Agent", newValue as String) + summary = newValue + true + } catch (e: Throwable) { + val errorMessage = helper.intl.format("set_custom_useragent_error_invalid", e.message) + Toast.makeText(screen.context, errorMessage, Toast.LENGTH_LONG).show() + false + } + } + } + screen.addPreference(coverQualityPref) screen.addPreference(tryUsingFirstVolumeCoverPref) screen.addPreference(dataSaverPref) @@ -752,19 +791,23 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St screen.addPreference(originalLanguagePref) screen.addPreference(blockedGroupsPref) screen.addPreference(blockedUploaderPref) + screen.addPreference(userAgentPref) } override fun getFilterList(): FilterList = helper.mdFilters.getMDFilterList(preferences, dexLang, helper.intl) - private fun HttpUrl.Builder.addQueryParameter(name: String, value: Set<String>?): HttpUrl.Builder { - return apply { value?.forEach { addQueryParameter(name, it) } } + private fun HttpUrl.Builder.addQueryParameter(name: String, value: Set<String>?) = apply { + value?.forEach { addQueryParameter(name, it) } } private inline fun <reified T> Response.parseAs(): T = use { helper.json.decodeFromString(body.string()) } + private inline fun <reified T> List<*>.firstInstanceOrNull(): T? = + firstOrNull { it is T } as? T? + private val SharedPreferences.contentRating get() = getStringSet( MDConstants.getContentRatingPrefKey(dexLang), @@ -823,6 +866,12 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St private val SharedPreferences.altTitlesInDesc get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false) + private val SharedPreferences.customUserAgent + get() = getString( + MDConstants.getCustomUserAgentPrefKey(dexLang), + MDConstants.defaultUserAgent, + ) + /** * Previous versions of the extension allowed invalid UUID values to be stored in the * preferences. This method clear invalid UUIDs in case the user have updated from diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt index cd5ab1a458..10a39bc6ca 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt @@ -6,19 +6,25 @@ import eu.kanade.tachiyomi.source.SourceFactory class MangaDexFactory : SourceFactory { override fun createSources(): List<Source> = listOf( MangaDexEnglish(), + MangaDexAlbanian(), MangaDexArabic(), + MangaDexAzerbaijani(), MangaDexBengali(), MangaDexBulgarian(), MangaDexBurmese(), MangaDexCatalan(), MangaDexChineseSimplified(), MangaDexChineseTraditional(), + MangaDexCroatian(), MangaDexCzech(), MangaDexDanish(), MangaDexDutch(), + MangaDexEsperanto(), + MangaDexEstonian(), MangaDexFilipino(), MangaDexFinnish(), MangaDexFrench(), + MangaDexGeorgian(), MangaDexGerman(), MangaDexGreek(), MangaDexHebrew(), @@ -41,11 +47,13 @@ class MangaDexFactory : SourceFactory { MangaDexPortuguesePortugal(), MangaDexRomanian(), MangaDexRussian(), - MangaDexSerboCroatian(), + MangaDexSerbian(), + MangaDexSlovak(), MangaDexSpanishLatinAmerica(), MangaDexSpanishSpain(), MangaDexSwedish(), MangaDexTamil(), + MangaDexTelugu(), MangaDexThai(), MangaDexTurkish(), MangaDexUkrainian(), @@ -53,48 +61,56 @@ class MangaDexFactory : SourceFactory { ) } -class MangaDexArabic : MangaDex("ar", "ar") -class MangaDexBengali : MangaDex("bn", "bn") -class MangaDexBulgarian : MangaDex("bg", "bg") -class MangaDexBurmese : MangaDex("my", "my") -class MangaDexCatalan : MangaDex("ca", "ca") +class MangaDexAlbanian : MangaDex("sq") +class MangaDexArabic : MangaDex("ar") +class MangaDexAzerbaijani : MangaDex("az") +class MangaDexBengali : MangaDex("bn") +class MangaDexBulgarian : MangaDex("bg") +class MangaDexBurmese : MangaDex("my") +class MangaDexCatalan : MangaDex("ca") class MangaDexChineseSimplified : MangaDex("zh-Hans", "zh") class MangaDexChineseTraditional : MangaDex("zh-Hant", "zh-hk") -class MangaDexCzech : MangaDex("cs", "cs") -class MangaDexDanish : MangaDex("da", "da") -class MangaDexDutch : MangaDex("nl", "nl") -class MangaDexEnglish : MangaDex("en", "en") +class MangaDexCroatian : MangaDex("hr") +class MangaDexCzech : MangaDex("cs") +class MangaDexDanish : MangaDex("da") +class MangaDexDutch : MangaDex("nl") +class MangaDexEnglish : MangaDex("en") +class MangaDexEsperanto : MangaDex("eo") +class MangaDexEstonian : MangaDex("et") class MangaDexFilipino : MangaDex("fil", "tl") -class MangaDexFinnish : MangaDex("fi", "fi") -class MangaDexFrench : MangaDex("fr", "fr") -class MangaDexGerman : MangaDex("de", "de") -class MangaDexGreek : MangaDex("el", "el") -class MangaDexHebrew : MangaDex("he", "he") -class MangaDexHindi : MangaDex("hi", "hi") -class MangaDexHungarian : MangaDex("hu", "hu") -class MangaDexIndonesian : MangaDex("id", "id") -class MangaDexItalian : MangaDex("it", "it") -class MangaDexJapanese : MangaDex("ja", "ja") -class MangaDexKazakh : MangaDex("kk", "kk") -class MangaDexKorean : MangaDex("ko", "ko") -class MangaDexLatin : MangaDex("la", "la") -class MangaDexLithuanian : MangaDex("lt", "lt") -class MangaDexMalay : MangaDex("ms", "ms") -class MangaDexMongolian : MangaDex("mn", "mn") -class MangaDexNepali : MangaDex("ne", "ne") -class MangaDexNorwegian : MangaDex("no", "no") -class MangaDexPersian : MangaDex("fa", "fa") -class MangaDexPolish : MangaDex("pl", "pl") +class MangaDexFinnish : MangaDex("fi") +class MangaDexFrench : MangaDex("fr") +class MangaDexGeorgian : MangaDex("ka") +class MangaDexGerman : MangaDex("de") +class MangaDexGreek : MangaDex("el") +class MangaDexHebrew : MangaDex("he") +class MangaDexHindi : MangaDex("hi") +class MangaDexHungarian : MangaDex("hu") +class MangaDexIndonesian : MangaDex("id") +class MangaDexItalian : MangaDex("it") +class MangaDexJapanese : MangaDex("ja") +class MangaDexKazakh : MangaDex("kk") +class MangaDexKorean : MangaDex("ko") +class MangaDexLatin : MangaDex("la") +class MangaDexLithuanian : MangaDex("lt") +class MangaDexMalay : MangaDex("ms") +class MangaDexMongolian : MangaDex("mn") +class MangaDexNepali : MangaDex("ne") +class MangaDexNorwegian : MangaDex("no") +class MangaDexPersian : MangaDex("fa") +class MangaDexPolish : MangaDex("pl") class MangaDexPortugueseBrazil : MangaDex("pt-BR", "pt-br") -class MangaDexPortuguesePortugal : MangaDex("pt", "pt") -class MangaDexRomanian : MangaDex("ro", "ro") -class MangaDexRussian : MangaDex("ru", "ru") -class MangaDexSerboCroatian : MangaDex("sh", "sh") +class MangaDexPortuguesePortugal : MangaDex("pt") +class MangaDexRomanian : MangaDex("ro") +class MangaDexRussian : MangaDex("ru") +class MangaDexSerbian : MangaDex("sr") +class MangaDexSlovak : MangaDex("sk") class MangaDexSpanishLatinAmerica : MangaDex("es-419", "es-la") -class MangaDexSpanishSpain : MangaDex("es", "es") -class MangaDexSwedish : MangaDex("sv", "sv") -class MangaDexTamil : MangaDex("ta", "ta") -class MangaDexThai : MangaDex("th", "th") -class MangaDexTurkish : MangaDex("tr", "tr") -class MangaDexUkrainian : MangaDex("uk", "uk") -class MangaDexVietnamese : MangaDex("vi", "vi") +class MangaDexSpanishSpain : MangaDex("es") +class MangaDexSwedish : MangaDex("sv") +class MangaDexTamil : MangaDex("ta") +class MangaDexTelugu : MangaDex("te") +class MangaDexThai : MangaDex("th") +class MangaDexTurkish : MangaDex("tr") +class MangaDexUkrainian : MangaDex("uk") +class MangaDexVietnamese : MangaDex("vi") diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt index d6e8f49590..05b452a01b 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFilters.kt @@ -4,6 +4,7 @@ import android.content.SharedPreferences import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.PublicationDemographicDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.StatusDto +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import okhttp3.HttpUrl @@ -13,7 +14,7 @@ class MangaDexFilters { internal fun getMDFilterList( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): FilterList = FilterList( HasAvailableChaptersFilter(intl), OriginalLanguageList(intl, getOriginalLanguage(preferences, dexLang, intl)), @@ -22,18 +23,18 @@ class MangaDexFilters { StatusList(intl, getStatus(intl)), SortFilter(intl, getSortables(intl)), TagsFilter(intl, getTagFilters(intl)), - TagList(intl.content, getContents(intl)), - TagList(intl.format, getFormats(intl)), - TagList(intl.genre, getGenres(intl)), - TagList(intl.theme, getThemes(intl)), + TagList(intl["content"], getContents(intl)), + TagList(intl["format"], getFormats(intl)), + TagList(intl["genre"], getGenres(intl)), + TagList(intl["theme"], getThemes(intl)), ) private interface UrlQueryFilter { fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) } - private class HasAvailableChaptersFilter(intl: MangaDexIntl) : - Filter.CheckBox(intl.hasAvailableChapters), + private class HasAvailableChaptersFilter(intl: Intl) : + Filter.CheckBox(intl["has_available_chapters"]), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -44,14 +45,18 @@ class MangaDexFilters { } } - private class OriginalLanguage(name: String, val isoCode: String) : Filter.CheckBox(name) - private class OriginalLanguageList(intl: MangaDexIntl, originalLanguage: List<OriginalLanguage>) : - Filter.Group<OriginalLanguage>(intl.originalLanguage, originalLanguage), + private class OriginalLanguage( + name: String, + val isoCode: String, + state: Boolean = false, + ) : Filter.CheckBox(name, state) + private class OriginalLanguageList(intl: Intl, originalLanguage: List<OriginalLanguage>) : + Filter.Group<OriginalLanguage>(intl["original_language"], originalLanguage), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { - state.forEach { lang -> - if (lang.state) { + state.filter(OriginalLanguage::state) + .forEach { lang -> // dex has zh and zh-hk for chinese manhua if (lang.isoCode == MDConstants.originalLanguagePrefValChinese) { url.addQueryParameter( @@ -62,14 +67,13 @@ class MangaDexFilters { url.addQueryParameter("originalLanguage[]", lang.isoCode) } - } } } private fun getOriginalLanguage( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): List<OriginalLanguage> { val originalLanguages = preferences.getStringSet( MDConstants.getOriginalLanguagePrefKey(dexLang), @@ -77,33 +81,48 @@ class MangaDexFilters { )!! return listOf( - OriginalLanguage(intl.originalLanguageFilterJapanese, MDConstants.originalLanguagePrefValJapanese) - .apply { state = MDConstants.originalLanguagePrefValJapanese in originalLanguages }, - OriginalLanguage(intl.originalLanguageFilterChinese, MDConstants.originalLanguagePrefValChinese) - .apply { state = MDConstants.originalLanguagePrefValChinese in originalLanguages }, - OriginalLanguage(intl.originalLanguageFilterKorean, MDConstants.originalLanguagePrefValKorean) - .apply { state = MDConstants.originalLanguagePrefValKorean in originalLanguages }, + OriginalLanguage( + name = intl.format( + "original_language_filter_japanese", + intl.languageDisplayName(MangaDexIntl.JAPANESE), + ), + isoCode = MDConstants.originalLanguagePrefValJapanese, + state = MDConstants.originalLanguagePrefValJapanese in originalLanguages, + ), + OriginalLanguage( + name = intl.format( + "original_language_filter_chinese", + intl.languageDisplayName(MangaDexIntl.CHINESE), + ), + isoCode = MDConstants.originalLanguagePrefValChinese, + state = MDConstants.originalLanguagePrefValChinese in originalLanguages, + ), + OriginalLanguage( + name = intl.format( + "original_language_filter_korean", + intl.languageDisplayName(MangaDexIntl.KOREAN), + ), + isoCode = MDConstants.originalLanguagePrefValKorean, + state = MDConstants.originalLanguagePrefValKorean in originalLanguages, + ), ) } private class ContentRating(name: String, val value: String) : Filter.CheckBox(name) - private class ContentRatingList(intl: MangaDexIntl, contentRating: List<ContentRating>) : - Filter.Group<ContentRating>(intl.contentRating, contentRating), + private class ContentRatingList(intl: Intl, contentRating: List<ContentRating>) : + Filter.Group<ContentRating>(intl["content_rating"], contentRating), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { - state.forEach { rating -> - if (rating.state) { - url.addQueryParameter("contentRating[]", rating.value) - } - } + state.filter(ContentRating::state) + .forEach { url.addQueryParameter("contentRating[]", it.value) } } } private fun getContentRating( preferences: SharedPreferences, dexLang: String, - intl: MangaDexIntl, + intl: Intl, ): List<ContentRating> { val contentRatings = preferences.getStringSet( MDConstants.getContentRatingPrefKey(dexLang), @@ -111,82 +130,76 @@ class MangaDexFilters { ) return listOf( - ContentRating(intl.contentRatingSafe, ContentRatingDto.SAFE.value).apply { + ContentRating(intl["content_rating_safe"], ContentRatingDto.SAFE.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValSafe) ?: true }, - ContentRating(intl.contentRatingSuggestive, ContentRatingDto.SUGGESTIVE.value).apply { + ContentRating(intl["content_rating_suggestive"], ContentRatingDto.SUGGESTIVE.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValSuggestive) ?: true }, - ContentRating(intl.contentRatingErotica, ContentRatingDto.EROTICA.value).apply { + ContentRating(intl["content_rating_erotica"], ContentRatingDto.EROTICA.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValErotica) ?: false }, - ContentRating(intl.contentRatingPornographic, ContentRatingDto.PORNOGRAPHIC.value).apply { + ContentRating(intl["content_rating_pornographic"], ContentRatingDto.PORNOGRAPHIC.value).apply { state = contentRatings?.contains(MDConstants.contentRatingPrefValPornographic) ?: false }, ) } private class Demographic(name: String, val value: String) : Filter.CheckBox(name) - private class DemographicList(intl: MangaDexIntl, demographics: List<Demographic>) : - Filter.Group<Demographic>(intl.publicationDemographic, demographics), + private class DemographicList(intl: Intl, demographics: List<Demographic>) : + Filter.Group<Demographic>(intl["publication_demographic"], demographics), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { - state.forEach { demographic -> - if (demographic.state) { - url.addQueryParameter("publicationDemographic[]", demographic.value) - } - } + state.filter(Demographic::state) + .forEach { url.addQueryParameter("publicationDemographic[]", it.value) } } } - private fun getDemographics(intl: MangaDexIntl) = listOf( - Demographic(intl.publicationDemographicNone, PublicationDemographicDto.NONE.value), - Demographic(intl.publicationDemographicShounen, PublicationDemographicDto.SHOUNEN.value), - Demographic(intl.publicationDemographicShoujo, PublicationDemographicDto.SHOUJO.value), - Demographic(intl.publicationDemographicSeinen, PublicationDemographicDto.SEINEN.value), - Demographic(intl.publicationDemographicJosei, PublicationDemographicDto.JOSEI.value), + private fun getDemographics(intl: Intl) = listOf( + Demographic(intl["publication_demographic_none"], PublicationDemographicDto.NONE.value), + Demographic(intl["publication_demographic_shounen"], PublicationDemographicDto.SHOUNEN.value), + Demographic(intl["publication_demographic_shoujo"], PublicationDemographicDto.SHOUJO.value), + Demographic(intl["publication_demographic_seinen"], PublicationDemographicDto.SEINEN.value), + Demographic(intl["publication_demographic_josei"], PublicationDemographicDto.JOSEI.value), ) private class Status(name: String, val value: String) : Filter.CheckBox(name) - private class StatusList(intl: MangaDexIntl, status: List<Status>) : - Filter.Group<Status>(intl.status, status), + private class StatusList(intl: Intl, status: List<Status>) : + Filter.Group<Status>(intl["status"], status), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { - state.forEach { status -> - if (status.state) { - url.addQueryParameter("status[]", status.value) - } - } + state.filter(Status::state) + .forEach { url.addQueryParameter("status[]", it.value) } } } - private fun getStatus(intl: MangaDexIntl) = listOf( - Status(intl.statusOngoing, StatusDto.ONGOING.value), - Status(intl.statusCompleted, StatusDto.COMPLETED.value), - Status(intl.statusHiatus, StatusDto.HIATUS.value), - Status(intl.statusCancelled, StatusDto.CANCELLED.value), + private fun getStatus(intl: Intl) = listOf( + Status(intl["status_ongoing"], StatusDto.ONGOING.value), + Status(intl["status_completed"], StatusDto.COMPLETED.value), + Status(intl["status_hiatus"], StatusDto.HIATUS.value), + Status(intl["status_cancelled"], StatusDto.CANCELLED.value), ) data class Sortable(val title: String, val value: String) { override fun toString(): String = title } - private fun getSortables(intl: MangaDexIntl) = arrayOf( - Sortable(intl.sortAlphabetic, "title"), - Sortable(intl.sortChapterUploadedAt, "latestUploadedChapter"), - Sortable(intl.sortNumberOfFollows, "followedCount"), - Sortable(intl.sortContentCreatedAt, "createdAt"), - Sortable(intl.sortContentInfoUpdatedAt, "updatedAt"), - Sortable(intl.sortRelevance, "relevance"), - Sortable(intl.sortYear, "year"), - Sortable(intl.sortRating, "rating"), + private fun getSortables(intl: Intl) = arrayOf( + Sortable(intl["sort_alphabetic"], "title"), + Sortable(intl["sort_chapter_uploaded_at"], "latestUploadedChapter"), + Sortable(intl["sort_number_of_follows"], "followedCount"), + Sortable(intl["sort_content_created_at"], "createdAt"), + Sortable(intl["sort_content_info_updated_at"], "updatedAt"), + Sortable(intl["sort_relevance"], "relevance"), + Sortable(intl["sort_year"], "year"), + Sortable(intl["sort_rating"], "rating"), ) - class SortFilter(intl: MangaDexIntl, private val sortables: Array<Sortable>) : + class SortFilter(intl: Intl, private val sortables: Array<Sortable>) : Filter.Sort( - intl.sort, + intl["sort"], sortables.map(Sortable::title).toTypedArray(), Selection(5, false), ), @@ -195,10 +208,7 @@ class MangaDexFilters { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { if (state != null) { val query = sortables[state!!.index].value - val value = when (state!!.ascending) { - true -> "asc" - false -> "desc" - } + val value = if (state!!.ascending) "asc" else "desc" url.addQueryParameter("order[$query]", value) } @@ -222,112 +232,112 @@ class MangaDexFilters { } } - private fun getContents(intl: MangaDexIntl): List<Tag> { + private fun getContents(intl: Intl): List<Tag> { val tags = listOf( - Tag("b29d6a3d-1569-4e7a-8caf-7557bc92cd5d", intl.contentGore), - Tag("97893a4c-12af-4dac-b6be-0dffb353568e", intl.contentSexualViolence), + Tag("b29d6a3d-1569-4e7a-8caf-7557bc92cd5d", intl["content_gore"]), + Tag("97893a4c-12af-4dac-b6be-0dffb353568e", intl["content_sexual_violence"]), ) return tags.sortIfTranslated(intl) } - private fun getFormats(intl: MangaDexIntl): List<Tag> { + private fun getFormats(intl: Intl): List<Tag> { val tags = listOf( - Tag("b11fda93-8f1d-4bef-b2ed-8803d3733170", intl.formatFourKoma), - Tag("f4122d1c-3b44-44d0-9936-ff7502c39ad3", intl.formatAdaptation), - Tag("51d83883-4103-437c-b4b1-731cb73d786c", intl.formatAnthology), - Tag("0a39b5a1-b235-4886-a747-1d05d216532d", intl.formatAwardWinning), - Tag("b13b2a48-c720-44a9-9c77-39c9979373fb", intl.formatDoujinshi), - Tag("7b2ce280-79ef-4c09-9b58-12b7c23a9b78", intl.formatFanColored), - Tag("f5ba408b-0e7a-484d-8d49-4e9125ac96de", intl.formatFullColor), - Tag("3e2b8dae-350e-4ab8-a8ce-016e844b9f0d", intl.formatLongStrip), - Tag("320831a8-4026-470b-94f6-8353740e6f04", intl.formatOfficialColored), - Tag("0234a31e-a729-4e28-9d6a-3f87c4966b9e", intl.formatOneshot), - Tag("891cf039-b895-47f0-9229-bef4c96eccd4", intl.formatUserCreated), - Tag("e197df38-d0e7-43b5-9b09-2842d0c326dd", intl.formatWebComic), + Tag("b11fda93-8f1d-4bef-b2ed-8803d3733170", intl["format_yonkoma"]), + Tag("f4122d1c-3b44-44d0-9936-ff7502c39ad3", intl["format_adaptation"]), + Tag("51d83883-4103-437c-b4b1-731cb73d786c", intl["format_anthology"]), + Tag("0a39b5a1-b235-4886-a747-1d05d216532d", intl["format_award_winning"]), + Tag("b13b2a48-c720-44a9-9c77-39c9979373fb", intl["format_doujinshi"]), + Tag("7b2ce280-79ef-4c09-9b58-12b7c23a9b78", intl["format_fan_colored"]), + Tag("f5ba408b-0e7a-484d-8d49-4e9125ac96de", intl["format_full_color"]), + Tag("3e2b8dae-350e-4ab8-a8ce-016e844b9f0d", intl["format_long_strip"]), + Tag("320831a8-4026-470b-94f6-8353740e6f04", intl["format_official_colored"]), + Tag("0234a31e-a729-4e28-9d6a-3f87c4966b9e", intl["format_oneshot"]), + Tag("891cf039-b895-47f0-9229-bef4c96eccd4", intl["format_user_created"]), + Tag("e197df38-d0e7-43b5-9b09-2842d0c326dd", intl["format_web_comic"]), ) return tags.sortIfTranslated(intl) } - private fun getGenres(intl: MangaDexIntl): List<Tag> { + private fun getGenres(intl: Intl): List<Tag> { val tags = listOf( - Tag("391b0423-d847-456f-aff0-8b0cfc03066b", intl.genreAction), - Tag("87cc87cd-a395-47af-b27a-93258283bbc6", intl.genreAdventure), - Tag("5920b825-4181-4a17-beeb-9918b0ff7a30", intl.genreBoysLove), - Tag("4d32cc48-9f00-4cca-9b5a-a839f0764984", intl.genreComedy), - Tag("5ca48985-9a9d-4bd8-be29-80dc0303db72", intl.genreCrime), - Tag("b9af3a63-f058-46de-a9a0-e0c13906197a", intl.genreDrama), - Tag("cdc58593-87dd-415e-bbc0-2ec27bf404cc", intl.genreFantasy), - Tag("a3c67850-4684-404e-9b7f-c69850ee5da6", intl.genreGirlsLove), - Tag("33771934-028e-4cb3-8744-691e866a923e", intl.genreHistorical), - Tag("cdad7e68-1419-41dd-bdce-27753074a640", intl.genreHorror), - Tag("ace04997-f6bd-436e-b261-779182193d3d", intl.genreIsekai), - Tag("81c836c9-914a-4eca-981a-560dad663e73", intl.genreMagicalGirls), - Tag("50880a9d-5440-4732-9afb-8f457127e836", intl.genreMecha), - Tag("c8cbe35b-1b2b-4a3f-9c37-db84c4514856", intl.genreMedical), - Tag("ee968100-4191-4968-93d3-f82d72be7e46", intl.genreMystery), - Tag("b1e97889-25b4-4258-b28b-cd7f4d28ea9b", intl.genrePhilosophical), - Tag("423e2eae-a7a2-4a8b-ac03-a8351462d71d", intl.genreRomance), - Tag("256c8bd9-4904-4360-bf4f-508a76d67183", intl.genreSciFi), - Tag("e5301a23-ebd9-49dd-a0cb-2add944c7fe9", intl.genreSliceOfLife), - Tag("69964a64-2f90-4d33-beeb-f3ed2875eb4c", intl.genreSports), - Tag("7064a261-a137-4d3a-8848-2d385de3a99c", intl.genreSuperhero), - Tag("07251805-a27e-4d59-b488-f0bfbec15168", intl.genreThriller), - Tag("f8f62932-27da-4fe4-8ee1-6779a8c5edba", intl.genreTragedy), - Tag("acc803a4-c95a-4c22-86fc-eb6b582d82a2", intl.genreWuxia), + Tag("391b0423-d847-456f-aff0-8b0cfc03066b", intl["genre_action"]), + Tag("87cc87cd-a395-47af-b27a-93258283bbc6", intl["genre_adventure"]), + Tag("5920b825-4181-4a17-beeb-9918b0ff7a30", intl["genre_boys_love"]), + Tag("4d32cc48-9f00-4cca-9b5a-a839f0764984", intl["genre_comedy"]), + Tag("5ca48985-9a9d-4bd8-be29-80dc0303db72", intl["genre_crime"]), + Tag("b9af3a63-f058-46de-a9a0-e0c13906197a", intl["genre_drama"]), + Tag("cdc58593-87dd-415e-bbc0-2ec27bf404cc", intl["genre_fantasy"]), + Tag("a3c67850-4684-404e-9b7f-c69850ee5da6", intl["genre_girls_love"]), + Tag("33771934-028e-4cb3-8744-691e866a923e", intl["genre_historical"]), + Tag("cdad7e68-1419-41dd-bdce-27753074a640", intl["genre_horror"]), + Tag("ace04997-f6bd-436e-b261-779182193d3d", intl["genre_isekai"]), + Tag("81c836c9-914a-4eca-981a-560dad663e73", intl["genre_magical_girls"]), + Tag("50880a9d-5440-4732-9afb-8f457127e836", intl["genre_mecha"]), + Tag("c8cbe35b-1b2b-4a3f-9c37-db84c4514856", intl["genre_medical"]), + Tag("ee968100-4191-4968-93d3-f82d72be7e46", intl["genre_mystery"]), + Tag("b1e97889-25b4-4258-b28b-cd7f4d28ea9b", intl["genre_philosophical"]), + Tag("423e2eae-a7a2-4a8b-ac03-a8351462d71d", intl["genre_romance"]), + Tag("256c8bd9-4904-4360-bf4f-508a76d67183", intl["genre_sci_fi"]), + Tag("e5301a23-ebd9-49dd-a0cb-2add944c7fe9", intl["genre_slice_of_life"]), + Tag("69964a64-2f90-4d33-beeb-f3ed2875eb4c", intl["genre_sports"]), + Tag("7064a261-a137-4d3a-8848-2d385de3a99c", intl["genre_superhero"]), + Tag("07251805-a27e-4d59-b488-f0bfbec15168", intl["genre_thriller"]), + Tag("f8f62932-27da-4fe4-8ee1-6779a8c5edba", intl["genre_tragedy"]), + Tag("acc803a4-c95a-4c22-86fc-eb6b582d82a2", intl["genre_wuxia"]), ) return tags.sortIfTranslated(intl) } - private fun getThemes(intl: MangaDexIntl): List<Tag> { + private fun getThemes(intl: Intl): List<Tag> { val tags = listOf( - Tag("e64f6742-c834-471d-8d72-dd51fc02b835", intl.themeAliens), - Tag("3de8c75d-8ee3-48ff-98ee-e20a65c86451", intl.themeAnimals), - Tag("ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869", intl.themeCooking), - Tag("9ab53f92-3eed-4e9b-903a-917c86035ee3", intl.themeCrossdressing), - Tag("da2d50ca-3018-4cc0-ac7a-6b7d472a29ea", intl.themeDelinquents), - Tag("39730448-9a5f-48a2-85b0-a70db87b1233", intl.themeDemons), - Tag("2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a", intl.themeGenderSwap), - Tag("3bb26d85-09d5-4d2e-880c-c34b974339e9", intl.themeGhosts), - Tag("fad12b5e-68ba-460e-b933-9ae8318f5b65", intl.themeGyaru), - Tag("aafb99c1-7f60-43fa-b75f-fc9502ce29c7", intl.themeHarem), - Tag("5bd0e105-4481-44ca-b6e7-7544da56b1a3", intl.themeIncest), - Tag("2d1f5d56-a1e5-4d0d-a961-2193588b08ec", intl.themeLoli), - Tag("85daba54-a71c-4554-8a28-9901a8b0afad", intl.themeMafia), - Tag("a1f53773-c69a-4ce5-8cab-fffcd90b1565", intl.themeMagic), - Tag("799c202e-7daa-44eb-9cf7-8a3c0441531e", intl.themeMartialArts), - Tag("ac72833b-c4e9-4878-b9db-6c8a4a99444a", intl.themeMilitary), - Tag("dd1f77c5-dea9-4e2b-97ae-224af09caf99", intl.themeMonsterGirls), - Tag("36fd93ea-e8b8-445e-b836-358f02b3d33d", intl.themeMonsters), - Tag("f42fbf9e-188a-447b-9fdc-f19dc1e4d685", intl.themeMusic), - Tag("489dd859-9b61-4c37-af75-5b18e88daafc", intl.themeNinja), - Tag("92d6d951-ca5e-429c-ac78-451071cbf064", intl.themeOfficeWorkers), - Tag("df33b754-73a3-4c54-80e6-1a74a8058539", intl.themePolice), - Tag("9467335a-1b83-4497-9231-765337a00b96", intl.themePostApocalyptic), - Tag("3b60b75c-a2d7-4860-ab56-05f391bb889c", intl.themePsychological), - Tag("0bc90acb-ccc1-44ca-a34a-b9f3a73259d0", intl.themeReincarnation), - Tag("65761a2a-415e-47f3-bef2-a9dababba7a6", intl.themeReverseHarem), - Tag("81183756-1453-4c81-aa9e-f6e1b63be016", intl.themeSamurai), - Tag("caaa44eb-cd40-4177-b930-79d3ef2afe87", intl.themeSchoolLife), - Tag("ddefd648-5140-4e5f-ba18-4eca4071d19b", intl.themeShota), - Tag("eabc5b4c-6aff-42f3-b657-3e90cbd00b75", intl.themeSupernatural), - Tag("5fff9cde-849c-4d78-aab0-0d52b2ee1d25", intl.themeSurvival), - Tag("292e862b-2d17-4062-90a2-0356caa4ae27", intl.themeTimeTravel), - Tag("31932a7e-5b8e-49a6-9f12-2afa39dc544c", intl.themeTraditionalGames), - Tag("d7d1730f-6eb0-4ba6-9437-602cac38664c", intl.themeVampires), - Tag("9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8", intl.themeVideoGames), - Tag("d14322ac-4d6f-4e9b-afd9-629d5f4d8a41", intl.themeVillainess), - Tag("8c86611e-fab7-4986-9dec-d1a2f44acdd5", intl.themeVirtualReality), - Tag("631ef465-9aba-4afb-b0fc-ea10efe274a8", intl.themeZombies), + Tag("e64f6742-c834-471d-8d72-dd51fc02b835", intl["theme_aliens"]), + Tag("3de8c75d-8ee3-48ff-98ee-e20a65c86451", intl["theme_animals"]), + Tag("ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869", intl["theme_cooking"]), + Tag("9ab53f92-3eed-4e9b-903a-917c86035ee3", intl["theme_crossdressing"]), + Tag("da2d50ca-3018-4cc0-ac7a-6b7d472a29ea", intl["theme_delinquents"]), + Tag("39730448-9a5f-48a2-85b0-a70db87b1233", intl["theme_demons"]), + Tag("2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a", intl["theme_gender_swap"]), + Tag("3bb26d85-09d5-4d2e-880c-c34b974339e9", intl["theme_ghosts"]), + Tag("fad12b5e-68ba-460e-b933-9ae8318f5b65", intl["theme_gyaru"]), + Tag("aafb99c1-7f60-43fa-b75f-fc9502ce29c7", intl["theme_harem"]), + Tag("5bd0e105-4481-44ca-b6e7-7544da56b1a3", intl["theme_incest"]), + Tag("2d1f5d56-a1e5-4d0d-a961-2193588b08ec", intl["theme_loli"]), + Tag("85daba54-a71c-4554-8a28-9901a8b0afad", intl["theme_mafia"]), + Tag("a1f53773-c69a-4ce5-8cab-fffcd90b1565", intl["theme_magic"]), + Tag("799c202e-7daa-44eb-9cf7-8a3c0441531e", intl["theme_martial_arts"]), + Tag("ac72833b-c4e9-4878-b9db-6c8a4a99444a", intl["theme_military"]), + Tag("dd1f77c5-dea9-4e2b-97ae-224af09caf99", intl["theme_monster_girls"]), + Tag("36fd93ea-e8b8-445e-b836-358f02b3d33d", intl["theme_monsters"]), + Tag("f42fbf9e-188a-447b-9fdc-f19dc1e4d685", intl["theme_music"]), + Tag("489dd859-9b61-4c37-af75-5b18e88daafc", intl["theme_ninja"]), + Tag("92d6d951-ca5e-429c-ac78-451071cbf064", intl["theme_office_workers"]), + Tag("df33b754-73a3-4c54-80e6-1a74a8058539", intl["theme_police"]), + Tag("9467335a-1b83-4497-9231-765337a00b96", intl["theme_post_apocalyptic"]), + Tag("3b60b75c-a2d7-4860-ab56-05f391bb889c", intl["theme_psychological"]), + Tag("0bc90acb-ccc1-44ca-a34a-b9f3a73259d0", intl["theme_reincarnation"]), + Tag("65761a2a-415e-47f3-bef2-a9dababba7a6", intl["theme_reverse_harem"]), + Tag("81183756-1453-4c81-aa9e-f6e1b63be016", intl["theme_samurai"]), + Tag("caaa44eb-cd40-4177-b930-79d3ef2afe87", intl["theme_school_life"]), + Tag("ddefd648-5140-4e5f-ba18-4eca4071d19b", intl["theme_shota"]), + Tag("eabc5b4c-6aff-42f3-b657-3e90cbd00b75", intl["theme_supernatural"]), + Tag("5fff9cde-849c-4d78-aab0-0d52b2ee1d25", intl["theme_survival"]), + Tag("292e862b-2d17-4062-90a2-0356caa4ae27", intl["theme_time_travel"]), + Tag("31932a7e-5b8e-49a6-9f12-2afa39dc544c", intl["theme_traditional_games"]), + Tag("d7d1730f-6eb0-4ba6-9437-602cac38664c", intl["theme_vampires"]), + Tag("9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8", intl["theme_video_games"]), + Tag("d14322ac-4d6f-4e9b-afd9-629d5f4d8a41", intl["theme_villainess"]), + Tag("8c86611e-fab7-4986-9dec-d1a2f44acdd5", intl["theme_virtual_reality"]), + Tag("631ef465-9aba-4afb-b0fc-ea10efe274a8", intl["theme_zombies"]), ) return tags.sortIfTranslated(intl) } // to get all tags from dex https://api.mangadex.org/manga/tag - internal fun getTags(intl: MangaDexIntl): List<Tag> { + internal fun getTags(intl: Intl): List<Tag> { return getContents(intl) + getFormats(intl) + getGenres(intl) + getThemes(intl) } @@ -335,13 +345,13 @@ class MangaDexFilters { override fun toString(): String = title } - private fun getTagModes(intl: MangaDexIntl) = arrayOf( - TagMode(intl.modeAnd, "AND"), - TagMode(intl.modeOr, "OR"), + private fun getTagModes(intl: Intl) = arrayOf( + TagMode(intl["mode_and"], "AND"), + TagMode(intl["mode_or"], "OR"), ) - private class TagInclusionMode(intl: MangaDexIntl, modes: Array<TagMode>) : - Filter.Select<TagMode>(intl.includedTagsMode, modes, 0), + private class TagInclusionMode(intl: Intl, modes: Array<TagMode>) : + Filter.Select<TagMode>(intl["included_tags_mode"], modes, 0), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -349,8 +359,8 @@ class MangaDexFilters { } } - private class TagExclusionMode(intl: MangaDexIntl, modes: Array<TagMode>) : - Filter.Select<TagMode>(intl.excludedTagsMode, modes, 1), + private class TagExclusionMode(intl: Intl, modes: Array<TagMode>) : + Filter.Select<TagMode>(intl["excluded_tags_mode"], modes, 1), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -358,8 +368,8 @@ class MangaDexFilters { } } - private class TagsFilter(intl: MangaDexIntl, innerFilters: FilterList) : - Filter.Group<Filter<*>>(intl.tags, innerFilters), + private class TagsFilter(intl: Intl, innerFilters: FilterList) : + Filter.Group<Filter<*>>(intl["tags_mode"], innerFilters), UrlQueryFilter { override fun addQueryParameter(url: HttpUrl.Builder, dexLang: String) { @@ -368,20 +378,20 @@ class MangaDexFilters { } } - private fun getTagFilters(intl: MangaDexIntl): FilterList = FilterList( + private fun getTagFilters(intl: Intl): FilterList = FilterList( TagInclusionMode(intl, getTagModes(intl)), TagExclusionMode(intl, getTagModes(intl)), ) - internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): String { + internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList, dexLang: String): HttpUrl { filters.filterIsInstance<UrlQueryFilter>() .forEach { filter -> filter.addQueryParameter(url, dexLang) } - return url.toString() + return url.build() } - private fun List<Tag>.sortIfTranslated(intl: MangaDexIntl): List<Tag> = apply { - if (intl.availableLang == MangaDexIntl.ENGLISH) { + private fun List<Tag>.sortIfTranslated(intl: Intl): List<Tag> = apply { + if (intl.chosenLanguage == MangaDexIntl.ENGLISH) { return this } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt index 60234e8aea..1a72db3bb9 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexHelper.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.extension.all.mangadex.dto.TagDto import eu.kanade.tachiyomi.extension.all.mangadex.dto.UnknownEntity import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserAttributes import eu.kanade.tachiyomi.extension.all.mangadex.dto.UserDto +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter @@ -85,7 +86,19 @@ class MangaDexHelper(lang: String) { } } - val intl = MangaDexIntl(lang) + val intl = Intl( + language = lang, + baseLanguage = MangaDexIntl.ENGLISH, + availableLanguages = MangaDexIntl.AVAILABLE_LANGS, + classLoader = this::class.java.classLoader!!, + createMessageFileName = { lang -> + when (lang) { + MangaDexIntl.SPANISH_LATAM -> Intl.createDefaultMessageFileName(MangaDexIntl.SPANISH) + MangaDexIntl.PORTUGUESE -> Intl.createDefaultMessageFileName(MangaDexIntl.BRAZILIAN_PORTUGUESE) + else -> Intl.createDefaultMessageFileName(lang) + } + }, + ) /** * Gets the UUID from the url @@ -130,12 +143,19 @@ class MangaDexHelper(lang: String) { (MDConstants.latestChapterLimit * (page - 1)).toString() /** - * Remove any HTML characters in description or chapter name to actual - * characters. For example ♥ will show ♥. It also removes - * Markdown syntax for links, italic and bold. + * Remove any HTML characters in manga or chapter name to actual + * characters. For example ♥ will show ♥. */ - private fun String.removeEntitiesAndMarkdown(): String { + private fun String.removeEntities(): String { return Parser.unescapeEntities(this, false) + } + + /** + * Remove any HTML characters in description to actual characters. + * It also removes Markdown syntax for links, italic and bold. + */ + private fun String.removeEntitiesAndMarkdown(): String { + return removeEntities() .substringBefore("---") .replace(markdownLinksRegex, "$1") .replace(markdownItalicBoldRegex, "$1") @@ -199,20 +219,18 @@ class MangaDexHelper(lang: String) { * Check the token map to see if the MD@Home host is still valid. */ fun getValidImageUrlForPage(page: Page, headers: Headers, client: OkHttpClient): Request { - val data = page.url.split(",") + val (host, tokenRequestUrl, time) = page.url.split(",") val mdAtHomeServerUrl = - when (Date().time - data[2].toLong() > MDConstants.mdAtHomeTokenLifespan) { - false -> data[0] + when (Date().time - time.toLong() > MDConstants.mdAtHomeTokenLifespan) { + false -> host true -> { - val tokenRequestUrl = data[1] val tokenLifespan = Date().time - (tokenTracker[tokenRequestUrl] ?: 0) - val cacheControl = - if (tokenLifespan > MDConstants.mdAtHomeTokenLifespan) { - CacheControl.FORCE_NETWORK - } else { - USE_CACHE - } + val cacheControl = if (tokenLifespan > MDConstants.mdAtHomeTokenLifespan) { + CacheControl.FORCE_NETWORK + } else { + USE_CACHE + } getMdAtHomeUrl(tokenRequestUrl, client, headers, cacheControl) } } @@ -265,22 +283,20 @@ class MangaDexHelper(lang: String) { coverFileName: String?, coverSuffix: String?, lang: String, - ): SManga { - return SManga.create().apply { - url = "/manga/${mangaDataDto.id}" - val titleMap = mangaDataDto.attributes!!.title - val dirtyTitle = - titleMap.values.firstOrNull() // use literally anything from title as first resort - ?: mangaDataDto.attributes.altTitles - .find { (it[lang] ?: it["en"]) !== null } - ?.values?.singleOrNull() // find something else from alt titles - title = (dirtyTitle ?: "").removeEntitiesAndMarkdown() - - coverFileName?.let { - thumbnail_url = when (!coverSuffix.isNullOrEmpty()) { - true -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName$coverSuffix" - else -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName" - } + ): SManga = SManga.create().apply { + url = "/manga/${mangaDataDto.id}" + val titleMap = mangaDataDto.attributes!!.title + val dirtyTitle = + titleMap.values.firstOrNull() // use literally anything from title as first resort + ?: mangaDataDto.attributes.altTitles + .find { (it[lang] ?: it["en"]) !== null } + ?.values?.singleOrNull() // find something else from alt titles + title = dirtyTitle?.removeEntities().orEmpty() + + coverFileName?.let { + thumbnail_url = when (!coverSuffix.isNullOrEmpty()) { + true -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName$coverSuffix" + else -> "${MDConstants.cdnUrl}/covers/${mangaDataDto.id}/$coverFileName" } } } @@ -302,10 +318,11 @@ class MangaDexHelper(lang: String) { val dexLocale = Locale.forLanguageTag(lang) val nonGenres = listOfNotNull( - attr.publicationDemographic?.let { intl.publicationDemographic(it) }, + attr.publicationDemographic + ?.let { intl["publication_demographic_${it.name.lowercase()}"] }, attr.contentRating .takeIf { it != ContentRatingDto.SAFE } - ?.let { intl.contentRatingGenre(it) }, + ?.let { intl.format("content_rating_genre", intl["content_rating_${it.name.lowercase()}"]) }, attr.originalLanguage ?.let { Locale.forLanguageTag(it) } ?.getDisplayName(dexLocale) @@ -335,10 +352,12 @@ class MangaDexHelper(lang: String) { val genreList = MDConstants.tagGroupsOrder.flatMap { genresMap[it].orEmpty() } + nonGenres - var desc = (attr.description[lang] ?: attr.description["en"])?.removeEntitiesAndMarkdown() ?: "" + var desc = (attr.description[lang] ?: attr.description["en"]) + ?.removeEntitiesAndMarkdown() + .orEmpty() if (altTitlesInDesc) { - val romanizedOriginalLang = MDConstants.romanizedLangCodes[attr.originalLanguage] ?: "" + val romanizedOriginalLang = MDConstants.romanizedLangCodes[attr.originalLanguage].orEmpty() val altTitles = attr.altTitles .filter { it.containsKey(lang) || it.containsKey(romanizedOriginalLang) } .mapNotNull { it.values.singleOrNull() } @@ -346,19 +365,19 @@ class MangaDexHelper(lang: String) { if (altTitles.isNotEmpty()) { val altTitlesDesc = altTitles - .joinToString("\n", "${intl.altTitleText}\n") { "• $it" } - desc += (if (desc.isNullOrBlank()) "" else "\n\n") + altTitlesDesc.removeEntitiesAndMarkdown() + .joinToString("\n", "${intl["alternative_titles"]}\n") { "• $it" } + desc += (if (desc.isBlank()) "" else "\n\n") + altTitlesDesc.removeEntities() } } return createBasicManga(mangaDataDto, coverFileName, coverSuffix, lang).apply { description = desc - author = authors.joinToString(", ") - artist = artists.joinToString(", ") + author = authors.joinToString() + artist = artists.joinToString() status = getPublicationStatus(attr, chapters) genre = genreList .filter(String::isNotEmpty) - .joinToString(", ") + .joinToString() } } @@ -378,9 +397,9 @@ class MangaDexHelper(lang: String) { val users = chapterDataDto.relationships .filterIsInstance<UserDto>() .mapNotNull { it.attributes?.username } - if (users.isNotEmpty()) intl.uploadedBy(users) else "" + if (users.isNotEmpty()) intl.format("uploaded_by", users.joinToString(" & ")) else "" } - .ifEmpty { intl.noGroup } // "No Group" as final resort + .ifEmpty { intl["no_group"] } // "No Group" as final resort val chapterName = mutableListOf<String>() // Build chapter name @@ -415,7 +434,7 @@ class MangaDexHelper(lang: String) { return SChapter.create().apply { url = "/chapter/${chapterDataDto.id}" - name = chapterName.joinToString(" ").removeEntitiesAndMarkdown() + name = chapterName.joinToString(" ").removeEntities() date_upload = parseDate(attr.publishAt) scanlator = groups } @@ -447,13 +466,9 @@ class MangaDexHelper(lang: String) { editText.addTextChangedListener( object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // Do nothing. - } + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // Do nothing. - } + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit override fun afterTextChanged(editable: Editable?) { requireNotNull(editable) @@ -465,7 +480,7 @@ class MangaDexHelper(lang: String) { .map(String::trim) .all(::isUuid) - editText.error = if (!isValid) intl.invalidUuids else null + editText.error = if (!isValid) intl["invalid_uuids"] else null editText.rootView.findViewById<Button>(android.R.id.button1) ?.isEnabled = editText.error == null } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexIntl.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexIntl.kt index 4a91a3b686..7abb152aea 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexIntl.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexIntl.kt @@ -1,1049 +1,24 @@ package eu.kanade.tachiyomi.extension.all.mangadex -import eu.kanade.tachiyomi.extension.all.mangadex.dto.ContentRatingDto -import eu.kanade.tachiyomi.extension.all.mangadex.dto.PublicationDemographicDto -import java.text.Collator -import java.util.Locale - -class MangaDexIntl(lang: String) { - - val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH - - private val locale: Locale = Locale.forLanguageTag(availableLang) - - val collator: Collator = Collator.getInstance(locale) - - val invalidUuids: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "O texto contém UUIDs inválidos" - else -> "The text contains invalid UUIDs" - } - - val invalidGroupId: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do grupo inválido" - SPANISH_LATAM, SPANISH -> "ID de grupo inválida" - RUSSIAN -> "Недействительный ID группы" - else -> "Not a valid group ID" - } - - val invalidAuthorId: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do autor inválido" - SPANISH_LATAM, SPANISH -> "ID de autor inválida" - RUSSIAN -> "Недействительный ID автора" - else -> "Not a valid author ID" - } - - val noSeriesInList: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Sem séries na lista" - SPANISH_LATAM, SPANISH -> "No hay series en la lista" - RUSSIAN -> "Лист пуст" - else -> "No series in the list" - } - - val migrateWarning: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Migre esta entrada do $MANGADEX_NAME para o $MANGADEX_NAME para atualizar" - SPANISH_LATAM, SPANISH -> - "Migre la entrada $MANGADEX_NAME a $MANGADEX_NAME para actualizarla" - else -> "Migrate this entry from $MANGADEX_NAME to $MANGADEX_NAME to update it" - } - - val coverQuality: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Qualidade da capa" - SPANISH_LATAM, SPANISH -> "Calidad de la portada" - RUSSIAN -> "Качество обложки" - else -> "Cover quality" - } - - val coverQualityOriginal: String = when (availableLang) { - RUSSIAN -> "Оригинальное" - else -> "Original" - } - - val coverQualityMedium: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Média" - SPANISH_LATAM, SPANISH -> "Medio" - RUSSIAN -> "Среднее" - else -> "Medium" - } - - val coverQualityLow: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Baixa" - SPANISH_LATAM, SPANISH -> "Bajo" - RUSSIAN -> "Низкое" - else -> "Low" - } - - val dataSaver: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Economia de dados" - SPANISH_LATAM, SPANISH -> "Ahorro de datos" - RUSSIAN -> "Экономия трафика" - else -> "Data saver" - } - - val dataSaverSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Utiliza imagens menores e mais compactadas" - SPANISH_LATAM, SPANISH -> "Utiliza imágenes más pequeñas y más comprimidas" - RUSSIAN -> "Использует меньшие по размеру, сжатые изображения" - else -> "Enables smaller, more compressed images" - } - - val standardHttpsPort: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Utilizar somente a porta 443 do HTTPS" - SPANISH_LATAM, SPANISH -> "Utilizar el puerto 443 de HTTPS" - RUSSIAN -> "Использовать только HTTPS порт 443" - else -> "Use HTTPS port 443 only" - } - - val standardHttpsPortSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Ative para fazer requisições em somente servidores de imagem que usem a porta 443. " + - "Isso permite com que usuários com regras mais restritas de firewall possam acessar " + - "as imagens do $MANGADEX_NAME." - SPANISH_LATAM, SPANISH -> - "Habilite esta opción solicitar las imágenes a los servidores que usan el puerto 443. " + - "Esto permite a los usuarios con restricciones estrictas de firewall acceder " + - "a las imagenes en $MANGADEX_NAME" - RUSSIAN -> - "Запрашивает изображения только с серверов которые используют порт 443. " + - "Это позволяет пользователям со строгими правилами брандмауэра загружать " + - "изображения с $MANGADEX_NAME." - else -> - "Enable to only request image servers that use port 443. This allows users with " + - "stricter firewall restrictions to access $MANGADEX_NAME images" - } - - val contentRating: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação de conteúdo" - SPANISH_LATAM, SPANISH -> "Clasificación de contenido" - RUSSIAN -> "Рейтинг контента" - else -> "Content rating" - } - - val standardContentRating: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação de conteúdo padrão" - SPANISH_LATAM, SPANISH -> "Clasificación de contenido por defecto" - RUSSIAN -> "Рейтинг контента по умолчанию" - else -> "Default content rating" - } - - val standardContentRatingSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Mostra os conteúdos com as classificações selecionadas por padrão" - SPANISH_LATAM, SPANISH -> - "Muestra el contenido con la clasificación de contenido seleccionada por defecto" - RUSSIAN -> - "Показывать контент с выбранным рейтингом по умолчанию" - else -> "Show content with the selected ratings by default" - } - - val contentRatingSafe: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Seguro" - SPANISH_LATAM, SPANISH -> "Seguro" - RUSSIAN -> "Безопасный" - else -> "Safe" - } - - val contentRatingSuggestive: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Sugestivo" - SPANISH_LATAM, SPANISH -> "Sugestivo" - RUSSIAN -> "Намекающий" - else -> "Suggestive" - } - - val contentRatingErotica: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Erótico" - SPANISH_LATAM, SPANISH -> "Erótico" - RUSSIAN -> "Эротический" - else -> "Erotica" - } - - val contentRatingPornographic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Pornográfico" - SPANISH_LATAM, SPANISH -> "Pornográfico" - RUSSIAN -> "Порнографический" - else -> "Pornographic" - } - - private val contentRatingMap: Map<ContentRatingDto, String> = mapOf( - ContentRatingDto.SAFE to contentRatingSafe, - ContentRatingDto.SUGGESTIVE to contentRatingSuggestive, - ContentRatingDto.EROTICA to contentRatingErotica, - ContentRatingDto.PORNOGRAPHIC to contentRatingPornographic, +object MangaDexIntl { + const val BRAZILIAN_PORTUGUESE = "pt-BR" + const val CHINESE = "zh" + const val ENGLISH = "en" + const val JAPANESE = "ja" + const val KOREAN = "ko" + const val PORTUGUESE = "pt" + const val SPANISH_LATAM = "es-419" + const val SPANISH = "es" + const val RUSSIAN = "ru" + + val AVAILABLE_LANGS = setOf( + ENGLISH, + BRAZILIAN_PORTUGUESE, + PORTUGUESE, + SPANISH, + SPANISH_LATAM, + RUSSIAN, ) - fun contentRatingGenre(contentRating: ContentRatingDto): String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Classificação: ${contentRatingMap[contentRating]}" - SPANISH_LATAM, SPANISH -> "Clasificación: ${contentRatingMap[contentRating]}" - RUSSIAN -> "Рейтинг контента: ${contentRatingMap[contentRating]}" - else -> "${this.contentRating}: ${contentRatingMap[contentRating]}" - } - - val originalLanguage: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Idioma original" - SPANISH_LATAM, SPANISH -> "Lenguaje original" - RUSSIAN -> "Язык оригинала" - else -> "Original language" - } - - val originalLanguageFilterJapanese: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "${languageDisplayName(JAPANESE)} (Mangá)" - RUSSIAN -> "${languageDisplayName(JAPANESE)} (Манга)" - else -> "${languageDisplayName(JAPANESE)} (Manga)" - } - - val originalLanguageFilterChinese: String = when (availableLang) { - RUSSIAN -> "${languageDisplayName(CHINESE)} (Манхуа)" - else -> "${languageDisplayName(CHINESE)} (Manhua)" - } - - val originalLanguageFilterKorean: String = when (availableLang) { - RUSSIAN -> "${languageDisplayName(KOREAN)} (Манхва)" - else -> "${languageDisplayName(KOREAN)} (Manhwa)" - } - - val filterOriginalLanguages: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Filtrar os idiomas originais" - SPANISH_LATAM, SPANISH -> "Filtrar por lenguajes" - RUSSIAN -> "Фильтр по языку оригинала" - else -> "Filter original languages" - } - - val filterOriginalLanguagesSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Mostra somente conteúdos que foram publicados originalmente nos idiomas " + - "selecionados nas seções de recentes e navegar" - SPANISH_LATAM, SPANISH -> - "Muestra solo el contenido publicado en los idiomas " + - "seleccionados en recientes y en la búsqueda" - RUSSIAN -> - "Показывать тайтлы которые изначально были выпущены только в выбранных" + - " языках в последних обновлениях и при поиске" - else -> - "Only show content that was originally published in the selected languages in " + - "both latest and browse" - } - - val blockGroupByUuid: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Bloquear grupos por UUID" - SPANISH_LATAM, SPANISH -> "Bloquear grupos por UUID" - RUSSIAN -> "Заблокировать группы по UUID" - else -> "Block groups by UUID" - } - - val blockGroupByUuidSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Capítulos de grupos bloqueados não irão aparecer no feed de Recentes ou Mangás. " + - "Digite uma lista de UUIDs dos grupos separados por vírgulas" - SPANISH_LATAM, SPANISH -> - "Los capítulos de los grupos bloqueados no aparecerán en Recientes o en el Feed" + - " de mangas. Introduce una coma para separar la lista de UUIDs" - RUSSIAN -> - "Главы от заблокированных групп не будут отображаться в последних обновлениях" + - " и в списке глав тайтла. Введите через запятую список UUID групп." - else -> - "Chapters from blocked groups will not show up in Latest or Manga feed. " + - "Enter as a Comma-separated list of group UUIDs" - } - - val blockUploaderByUuid: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Bloquear uploaders por UUID" - SPANISH_LATAM, SPANISH -> "Bloquear uploader por UUID" - RUSSIAN -> "Заблокировать загрузчика по UUID" - else -> "Block uploader by UUID" - } - - val blockUploaderByUuidSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Capítulos de usuários bloqueados não irão aparecer no feed de Recentes ou Mangás. " + - "Digite uma lista de UUIDs dos usuários separados por vírgulas" - SPANISH_LATAM, SPANISH -> - "Los capítulos de los uploaders bloqueados no aparecerán en Recientes o en el Feed" + - " de mangas. Introduce una coma para separar la lista de UUIDs" - RUSSIAN -> - "Главы от заблокированных загрузчиков не будут отображаться в последних обновлениях" + - " и в списке глав тайтла. Введите через запятую список UUID загрузчиков." - else -> - "Chapters from blocked uploaders will not show up in Latest or Manga feed. " + - "Enter as a Comma-separated list of uploader UUIDs" - } - - val altTitlesInDesc: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Títulos alternativos na descrição" - else -> "Alternative titles in description" - } - - val altTitlesInDescSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Inclui os títulos alternativos das séries no final de cada descrição" - else -> "Include a manga's alternative titles at the end of its description" - } - - val tryUsingFirstVolumeCover: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Tentar usar a capa do primeiro volume como capa" - else -> "Attempt to use the first volume cover as cover" - } - - val tryUsingFirstVolumeCoverSummary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Pode ser necessário atualizar os itens já adicionados na biblioteca. " + - "Alternativamente, limpe o banco de dados para as novas capas aparecerem." - else -> - "May need to manually refresh entries already in library. " + - "Otherwise, clear database to have new covers to show up." - } - - val publicationDemographic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Demografia da publicação" - SPANISH_LATAM, SPANISH -> "Demografía" - RUSSIAN -> "Целевая аудитория" - else -> "Publication demographic" - } - - val publicationDemographicNone: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Nenhuma" - SPANISH_LATAM, SPANISH -> "Ninguna" - RUSSIAN -> "Нет" - else -> "None" - } - - val publicationDemographicShounen: String = when (availableLang) { - RUSSIAN -> "Сёнэн" - else -> "Shounen" - } - - val publicationDemographicShoujo: String = when (availableLang) { - RUSSIAN -> "Сёдзё" - else -> "Shoujo" - } - - val publicationDemographicSeinen: String = when (availableLang) { - RUSSIAN -> "Сэйнэн" - else -> "Seinen" - } - - val publicationDemographicJosei: String = when (availableLang) { - RUSSIAN -> "Дзёсэй" - else -> "Josei" - } - - fun publicationDemographic(demographic: PublicationDemographicDto): String = when (demographic) { - PublicationDemographicDto.NONE -> publicationDemographicNone - PublicationDemographicDto.SHOUNEN -> publicationDemographicShounen - PublicationDemographicDto.SHOUJO -> publicationDemographicShoujo - PublicationDemographicDto.SEINEN -> publicationDemographicSeinen - PublicationDemographicDto.JOSEI -> publicationDemographicJosei - } - - val status: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Estado" - SPANISH_LATAM, SPANISH -> "Estado" - RUSSIAN -> "Статус" - else -> "Status" - } - - val statusOngoing: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Em andamento" - SPANISH_LATAM, SPANISH -> "Publicandose" - RUSSIAN -> "Онгоинг" - else -> "Ongoing" - } - - val statusCompleted: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Completo" - SPANISH_LATAM, SPANISH -> "Completado" - RUSSIAN -> "Завершён" - else -> "Completed" - } - - val statusHiatus: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Hiato" - SPANISH_LATAM, SPANISH -> "Pausado" - RUSSIAN -> "Приостановлен" - else -> "Hiatus" - } - - val statusCancelled: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Cancelado" - SPANISH_LATAM, SPANISH -> "Cancelado" - RUSSIAN -> "Отменён" - else -> "Cancelled" - } - - val content: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Conteúdo" - SPANISH_LATAM, SPANISH -> "Contenido" - RUSSIAN -> "Неприемлемый контент" - else -> "Content" - } - - val contentGore: String = when (availableLang) { - RUSSIAN -> "Жестокость" - else -> "Gore" - } - - val contentSexualViolence: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Violência sexual" - SPANISH_LATAM, SPANISH -> "Violencia Sexual" - RUSSIAN -> "Сексуальное насилие" - else -> "Sexual Violence" - } - - val format: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Formato" - SPANISH_LATAM, SPANISH -> "Formato" - RUSSIAN -> "Формат" - else -> "Format" - } - - val formatAdaptation: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Adaptação" - SPANISH_LATAM, SPANISH -> "Adaptación" - RUSSIAN -> "Адаптация" - else -> "Adaptation" - } - - val formatAnthology: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Antologia" - SPANISH_LATAM, SPANISH -> "Antología" - RUSSIAN -> "Антология" - else -> "Anthology" - } - - val formatAwardWinning: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Premiado" - SPANISH_LATAM, SPANISH -> "Ganador de premio" - RUSSIAN -> "Отмеченный наградами" - else -> "Award Winning" - } - - val formatDoujinshi: String = when (availableLang) { - RUSSIAN -> "Додзинси" - else -> "Doujinshi" - } - - val formatFanColored: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Colorizado por fãs" - SPANISH_LATAM, SPANISH -> "Coloreado por fans" - RUSSIAN -> "Раскрашенная фанатами" - else -> "Fan Colored" - } - - val formatFourKoma: String = when (availableLang) { - RUSSIAN -> "Ёнкома" - else -> "4-Koma" - } - - val formatFullColor: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Colorido" - SPANISH_LATAM, SPANISH -> "Todo a color" - RUSSIAN -> "В цвете" - else -> "Full Color" - } - - val formatLongStrip: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Vertical" - SPANISH_LATAM, SPANISH -> "Tira larga" - RUSSIAN -> "Веб" - else -> "Long Strip" - } - - val formatOfficialColored: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Colorizado oficialmente" - SPANISH_LATAM, SPANISH -> "Coloreo oficial" - RUSSIAN -> "Официально раскрашенная" - else -> "Official Colored" - } - - val formatOneshot: String = when (availableLang) { - RUSSIAN -> "Сингл" - else -> "Oneshot" - } - - val formatUserCreated: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Criado por usuários" - SPANISH_LATAM, SPANISH -> "Creado por usuario" - RUSSIAN -> "Созданная пользователями" - else -> "User Created" - } - - val formatWebComic: String = when (availableLang) { - RUSSIAN -> "Веб-комикс" - else -> "Web Comic" - } - - val genre: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Gênero" - SPANISH_LATAM, SPANISH -> "Genero" - RUSSIAN -> "Жанр" - else -> "Genre" - } - - val genreAction: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Ação" - SPANISH_LATAM, SPANISH -> "Acción" - RUSSIAN -> "Боевик" - else -> "Action" - } - - val genreAdventure: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Aventura" - SPANISH_LATAM, SPANISH -> "Aventura" - RUSSIAN -> "Приключения" - else -> "Adventure" - } - - val genreBoysLove: String = when (availableLang) { - RUSSIAN -> "BL" - else -> "Boy's Love" - } - - val genreComedy: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Comédia" - SPANISH_LATAM, SPANISH -> "Comedia" - RUSSIAN -> "Комедия" - else -> "Comedy" - } - - val genreCrime: String = when (availableLang) { - SPANISH_LATAM, SPANISH -> "Crimen" - RUSSIAN -> "Криминал" - else -> "Crime" - } - - val genreDrama: String = when (availableLang) { - RUSSIAN -> "Драма" - else -> "Drama" - } - - val genreFantasy: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Fantasia" - SPANISH_LATAM, SPANISH -> "Fantasia" - RUSSIAN -> "Фэнтези" - else -> "Fantasy" - } - - val genreGirlsLove: String = when (availableLang) { - RUSSIAN -> "GL" - else -> "Girl's Love" - } - - val genreHistorical: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Histórico" - SPANISH_LATAM, SPANISH -> "Histórico" - RUSSIAN -> "История" - else -> "Historical" - } - - val genreHorror: String = when (availableLang) { - RUSSIAN -> "Ужасы" - else -> "Horror" - } - - val genreIsekai: String = when (availableLang) { - RUSSIAN -> "Исекай" - else -> "Isekai" - } - - val genreMagicalGirls: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Garotas mágicas" - SPANISH_LATAM, SPANISH -> "Chicas mágicas" - RUSSIAN -> "Махо-сёдзё" - else -> "Magical Girls" - } - - val genreMecha: String = when (availableLang) { - RUSSIAN -> "Меха" - else -> "Mecha" - } - - val genreMedical: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Médico" - SPANISH_LATAM, SPANISH -> "Medico" - RUSSIAN -> "Медицина" - else -> "Medical" - } - - val genreMystery: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Mistério" - SPANISH_LATAM, SPANISH -> "Misterio" - RUSSIAN -> "Мистика" - else -> "Mystery" - } - - val genrePhilosophical: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Filosófico" - SPANISH_LATAM, SPANISH -> "Filosófico" - RUSSIAN -> "Философия" - else -> "Philosophical" - } - - val genreRomance: String = when (availableLang) { - RUSSIAN -> "Романтика" - else -> "Romance" - } - - val genreSciFi: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Ficção científica" - SPANISH_LATAM, SPANISH -> "Ciencia ficción" - RUSSIAN -> "Научная фантастика" - else -> "Sci-Fi" - } - - val genreSliceOfLife: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Cotidiano" - SPANISH_LATAM, SPANISH -> "Recuentos de la vida" - RUSSIAN -> "Повседневность" - else -> "Slice of Life" - } - - val genreSports: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Esportes" - SPANISH_LATAM, SPANISH -> "Deportes" - RUSSIAN -> "Спорт" - else -> "Sports" - } - - val genreSuperhero: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Super-heroi" - SPANISH_LATAM, SPANISH -> "Superhéroes" - RUSSIAN -> "Супергерои" - else -> "Superhero" - } - - val genreThriller: String = when (availableLang) { - RUSSIAN -> "Триллер" - else -> "Thriller" - } - - val genreTragedy: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Tragédia" - SPANISH_LATAM, SPANISH -> "Tragedia" - RUSSIAN -> "Трагедия" - else -> "Tragedy" - } - - val genreWuxia: String = when (availableLang) { - RUSSIAN -> "Культивация" - else -> "Wuxia" - } - - val theme: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Tema" - SPANISH_LATAM, SPANISH -> "Tema" - RUSSIAN -> "Теги" - else -> "Theme" - } - - val themeAliens: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Alienígenas" - SPANISH_LATAM, SPANISH -> "Alienígenas" - RUSSIAN -> "Инопланетяне" - else -> "Aliens" - } - - val themeAnimals: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Animais" - SPANISH_LATAM, SPANISH -> "Animales" - RUSSIAN -> "Животные" - else -> "Animals" - } - - val themeCooking: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Culinária" - SPANISH_LATAM, SPANISH -> "Cocina" - RUSSIAN -> "Кулинария" - else -> "Cooking" - } - - val themeCrossdressing: String = when (availableLang) { - SPANISH_LATAM, SPANISH -> "Travestismo" - RUSSIAN -> "Кроссдрессинг" - else -> "Crossdressing" - } - - val themeDelinquents: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Delinquentes" - SPANISH_LATAM, SPANISH -> "Delincuentes" - RUSSIAN -> "Хулиганы" - else -> "Delinquents" - } - - val themeDemons: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Demônios" - SPANISH_LATAM, SPANISH -> "Demonios" - RUSSIAN -> "Демоны" - else -> "Demons" - } - - val themeGenderSwap: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Troca de gêneros" - SPANISH_LATAM, SPANISH -> "Cambio de sexo" - RUSSIAN -> "Смена гендера" - else -> "Genderswap" - } - - val themeGhosts: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Fantasmas" - SPANISH_LATAM, SPANISH -> "Fantasmas" - RUSSIAN -> "Призраки" - else -> "Ghosts" - } - - val themeGyaru: String = when (availableLang) { - RUSSIAN -> "Гяру" - else -> "Gyaru" - } - - val themeHarem: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Harém" - RUSSIAN -> "Гарем" - else -> "Harem" - } - - val themeIncest: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Incesto" - SPANISH_LATAM, SPANISH -> "Incesto" - RUSSIAN -> "Инцест" - else -> "Incest" - } - - val themeLoli: String = when (availableLang) { - RUSSIAN -> "Лоли" - else -> "Loli" - } - - val themeMafia: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Máfia" - RUSSIAN -> "Мафия" - else -> "Mafia" - } - - val themeMagic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Magia" - SPANISH_LATAM, SPANISH -> "Magia" - RUSSIAN -> "Магия" - else -> "Magic" - } - - val themeMartialArts: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Artes marciais" - SPANISH_LATAM, SPANISH -> "Artes marciales" - RUSSIAN -> "Боевые исскуства" - else -> "Martial Arts" - } - - val themeMilitary: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Militar" - SPANISH_LATAM, SPANISH -> "Militar" - RUSSIAN -> "Военные" - else -> "Military" - } - - val themeMonsterGirls: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Garotas monstro" - SPANISH_LATAM, SPANISH -> "Chicas monstruo" - RUSSIAN -> "Монстродевушки" - else -> "Monster Girls" - } - - val themeMonsters: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Monstros" - SPANISH_LATAM, SPANISH -> "Monstruos" - RUSSIAN -> "Монстры" - else -> "Monsters" - } - - val themeMusic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Musical" - SPANISH_LATAM, SPANISH -> "Musica" - RUSSIAN -> "Музыка" - else -> "Music" - } - - val themeNinja: String = when (availableLang) { - RUSSIAN -> "Ниндзя" - else -> "Ninja" - } - - val themeOfficeWorkers: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Funcionários de escritório" - SPANISH_LATAM, SPANISH -> "Oficinistas" - RUSSIAN -> "Офисные работники" - else -> "Office Workers" - } - - val themePolice: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Policial" - SPANISH_LATAM, SPANISH -> "Policial" - RUSSIAN -> "Полиция" - else -> "Police" - } - - val themePostApocalyptic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Pós-apocalíptico" - SPANISH_LATAM, SPANISH -> "Post-Apocalíptico" - RUSSIAN -> "Постапокалиптика" - else -> "Post-Apocalyptic" - } - - val themePsychological: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Psicológico" - SPANISH_LATAM, SPANISH -> "Psicológico" - RUSSIAN -> "Психология" - else -> "Psychological" - } - - val themeReincarnation: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Reencarnação" - SPANISH_LATAM, SPANISH -> "Reencarnación" - RUSSIAN -> "Реинкарнация" - else -> "Reincarnation" - } - - val themeReverseHarem: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Harém reverso" - SPANISH_LATAM, SPANISH -> "Harem Inverso" - RUSSIAN -> "Обратный гарем" - else -> "Reverse Harem" - } - - val themeSamurai: String = when (availableLang) { - RUSSIAN -> "Самураи" - else -> "Samurai" - } - - val themeSchoolLife: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Vida escolar" - SPANISH_LATAM, SPANISH -> "Vida escolar" - RUSSIAN -> "Школа" - else -> "School Life" - } - - val themeShota: String = when (availableLang) { - RUSSIAN -> "Шота" - else -> "Shota" - } - - val themeSupernatural: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Sobrenatural" - SPANISH_LATAM, SPANISH -> "Sobrenatural" - RUSSIAN -> "Сверхъестественное" - else -> "Supernatural" - } - - val themeSurvival: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Sobrevivência" - SPANISH_LATAM, SPANISH -> "Supervivencia" - RUSSIAN -> "Выживание" - else -> "Survival" - } - - val themeTimeTravel: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Viagem no tempo" - SPANISH_LATAM, SPANISH -> "Viaje en el tiempo" - RUSSIAN -> "Путешествие во времени" - else -> "Time Travel" - } - - val themeTraditionalGames: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Jogos tradicionais" - SPANISH_LATAM, SPANISH -> "Juegos tradicionales" - RUSSIAN -> "Традиционные игры" - else -> "Traditional Games" - } - - val themeVampires: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Vampiros" - SPANISH_LATAM, SPANISH -> "Vampiros" - RUSSIAN -> "Вампиры" - else -> "Vampires" - } - - val themeVideoGames: String = when (availableLang) { - SPANISH_LATAM, SPANISH -> "Videojuegos" - RUSSIAN -> "Видеоигры" - else -> "Video Games" - } - - val themeVillainess: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Vilania" - SPANISH_LATAM, SPANISH -> "Villana" - RUSSIAN -> "Злодейка" - else -> "Villainess" - } - - val themeVirtualReality: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Realidade virtual" - SPANISH_LATAM, SPANISH -> "Realidad Virtual" - RUSSIAN -> "Виртуальная реальность" - else -> "Virtual Reality" - } - - val themeZombies: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Zumbis" - RUSSIAN -> "Зомби" - else -> "Zombies" - } - - val tags: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Modo das tags" - SPANISH_LATAM, SPANISH -> "Modo de etiquetas" - RUSSIAN -> "Режим поиска" - else -> "Tags mode" - } - - val includedTagsMode: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Modo de inclusão de tags" - SPANISH_LATAM, SPANISH -> "Modo de etiquetas incluidas" - RUSSIAN -> "Включая" - else -> "Included tags mode" - } - - val excludedTagsMode: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Modo de exclusão de tags" - SPANISH_LATAM, SPANISH -> "Modo de etiquetas excluidas" - RUSSIAN -> "Исключая" - else -> "Excluded tags mode" - } - - val modeAnd: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "E" - SPANISH_LATAM, SPANISH -> "Y" - RUSSIAN -> "И" - else -> "And" - } - - val modeOr: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Ou" - SPANISH_LATAM, SPANISH -> "O" - RUSSIAN -> "Или" - else -> "Or" - } - - val sort: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Ordenar" - SPANISH_LATAM, SPANISH -> "Ordenar" - RUSSIAN -> "Сортировать по" - else -> "Sort" - } - - val sortAlphabetic: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Alfabeticamente" - SPANISH_LATAM, SPANISH -> "Alfabeticamente" - RUSSIAN -> "Алфавиту" - else -> "Alphabetic" - } - - val sortChapterUploadedAt: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Upload do capítulo" - SPANISH_LATAM, SPANISH -> "Capítulo subido en" - RUSSIAN -> "Загруженной главе" - else -> "Chapter uploaded at" - } - - val sortNumberOfFollows: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Número de seguidores" - SPANISH_LATAM, SPANISH -> "Número de seguidores" - RUSSIAN -> "Количеству фолловеров" - else -> "Number of follows" - } - - val sortContentCreatedAt: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Criação do conteúdo" - SPANISH_LATAM, SPANISH -> "Contenido creado en" - RUSSIAN -> "По дате создания" - else -> "Content created at" - } - - val sortContentInfoUpdatedAt: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Atualização das informações" - SPANISH_LATAM, SPANISH -> "Información del contenido actualizada en" - RUSSIAN -> "По дате обновления" - else -> "Content info updated at" - } - - val sortRelevance: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Relevância" - SPANISH_LATAM, SPANISH -> "Relevancia" - RUSSIAN -> "Лучшему соответствию" - else -> "Relevance" - } - - val sortYear: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Ano de lançamento" - SPANISH_LATAM, SPANISH -> "Año" - RUSSIAN -> "Год" - else -> "Year" - } - - val sortRating: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Nota" - SPANISH_LATAM, SPANISH -> "Calificación" - RUSSIAN -> "Популярности" - else -> "Rating" - } - - val hasAvailableChapters: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Há capítulos disponíveis" - SPANISH_LATAM, SPANISH -> "Tiene capítulos disponibles" - RUSSIAN -> "Есть главы" - else -> "Has available chapters" - } - - fun languageDisplayName(localeCode: String): String = - Locale.forLanguageTag(localeCode) - .getDisplayName(locale) - .replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } - - fun unableToProcessChapterRequest(code: Int): String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> - "Não foi possível processar a requisição do capítulo. Código HTTP: $code" - SPANISH_LATAM, SPANISH -> - "No se ha podido procesar la solicitud del capítulo. Código HTTP: $code" - RUSSIAN -> - "Не удалось обработать ссылку на главу. Ошибка: $code" - else -> "Unable to process Chapter request. HTTP code: $code" - } - - fun uploadedBy(users: List<String>): String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Enviado por ${users.joinToString(" & ")}" - SPANISH_LATAM, SPANISH -> "Subido por: ${users.joinToString(" & ") }" - RUSSIAN -> "Загрузил ${users.joinToString(" & ")}" - else -> "Uploaded by ${users.joinToString(" & ")}" - } - - val noGroup: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Sem grupo" - SPANISH_LATAM, SPANISH -> "Sin grupo" - RUSSIAN -> "Нет группы" - else -> "No Group" - } - - val altTitleText: String = when (availableLang) { - BRAZILIAN_PORTUGUESE, PORTUGUESE -> "Títulos alternativos:" - else -> "Alternative titles:" - } - - companion object { - const val BRAZILIAN_PORTUGUESE = "pt-BR" - const val CHINESE = "zh" - const val ENGLISH = "en" - const val JAPANESE = "ja" - const val KOREAN = "ko" - const val PORTUGUESE = "pt" - const val SPANISH_LATAM = "es-419" - const val SPANISH = "es" - const val RUSSIAN = "ru" - - val AVAILABLE_LANGS = arrayOf( - ENGLISH, - BRAZILIAN_PORTUGUESE, - PORTUGUESE, - SPANISH_LATAM, - SPANISH, - RUSSIAN, - ) - - const val MANGADEX_NAME = "MangaDex" - } + const val MANGADEX_NAME = "MangaDex" } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt index fc473bdf6c..2d0256a870 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt @@ -22,17 +22,17 @@ class MangadexUrlActivity : Activity() { super.onCreate(savedInstanceState) val pathSegments = intent?.data?.pathSegments if (pathSegments != null && pathSegments.size > 1) { - val titleid = pathSegments[1] + val titleId = pathSegments[1] val mainIntent = Intent().apply { action = "eu.kanade.tachiyomi.SEARCH" with(pathSegments[0]) { when { - equals("chapter") -> putExtra("query", "${MDConstants.prefixChSearch}$titleid") - equals("group") -> putExtra("query", "${MDConstants.prefixGrpSearch}$titleid") - equals("user") -> putExtra("query", "${MDConstants.prefixUsrSearch}$titleid") - equals("author") -> putExtra("query", "${MDConstants.prefixAuthSearch}$titleid") - equals("list") -> putExtra("query", "${MDConstants.prefixListSearch}$titleid") - else -> putExtra("query", "${MDConstants.prefixIdSearch}$titleid") + equals("chapter") -> putExtra("query", MDConstants.prefixChSearch + titleId) + equals("group") -> putExtra("query", MDConstants.prefixGrpSearch + titleId) + equals("user") -> putExtra("query", MDConstants.prefixUsrSearch + titleId) + equals("author") -> putExtra("query", MDConstants.prefixAuthSearch + titleId) + equals("list") -> putExtra("query", MDConstants.prefixListSearch + titleId) + else -> putExtra("query", MDConstants.prefixIdSearch + titleId) } } putExtra("filter", packageName) @@ -44,7 +44,7 @@ class MangadexUrlActivity : Activity() { Log.e("MangadexUrlActivity", e.toString()) } } else { - Log.e("MangadexUrlActivity", "could not parse uri from intent $intent") + Log.e("MangadexUrlActivity", "Could not parse URI from intent $intent") } finish() diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdAtHomeReportInterceptor.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdAtHomeReportInterceptor.kt index c717874203..c05de244ff 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdAtHomeReportInterceptor.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdAtHomeReportInterceptor.kt @@ -8,16 +8,17 @@ import kotlinx.serialization.json.Json import okhttp3.Call import okhttp3.Callback import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import uy.kohesive.injekt.injectLazy -import java.io.IOException /** - * Interceptor to post to md@home for MangaDex Stats + * Interceptor to post to MD@Home for MangaDex Stats */ class MdAtHomeReportInterceptor( private val client: OkHttpClient, @@ -26,53 +27,83 @@ class MdAtHomeReportInterceptor( private val json: Json by injectLazy() - private val mdAtHomeUrlRegex = - Regex("""^https://[\w\d]+\.[\w\d]+\.mangadex(\b-test\b)?\.network.*${'$'}""") - override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val response = chain.proceed(chain.request()) val url = originalRequest.url.toString() - if (!url.contains(mdAtHomeUrlRegex)) { + if (!url.contains(MD_AT_HOME_URL_REGEX)) { + return response + } + + Log.e("MangaDex", "Connecting to MD@Home node at $url") + + val reportRequest = mdAtHomeReportRequest(response) + + // Execute the report endpoint network call asynchronously to avoid blocking + // the reader from showing the image once it's fully loaded if the report call + // gets stuck, as it tend to happens sometimes. + client.newCall(reportRequest).enqueue(REPORT_CALLBACK) + + if (response.isSuccessful) { return response } + response.close() + + Log.e("MangaDex", "Error connecting to MD@Home node, fallback to uploads server") + + val imagePath = originalRequest.url.pathSegments + .dropWhile { it != "data" && it != "data-saver" } + .joinToString("/") + + val fallbackUrl = MDConstants.cdnUrl.toHttpUrl().newBuilder() + .addPathSegments(imagePath) + .build() + + val fallbackRequest = originalRequest.newBuilder() + .url(fallbackUrl) + .headers(headers) + .build() + + return chain.proceed(fallbackRequest) + } + + private fun mdAtHomeReportRequest(response: Response): Request { val result = ImageReportDto( - url, + url = response.request.url.toString(), success = response.isSuccessful, bytes = response.peekBody(Long.MAX_VALUE).bytes().size, - cached = response.header("X-Cache", "") == "HIT", + cached = response.headers["X-Cache"] == "HIT", duration = response.receivedResponseAtMillis - response.sentRequestAtMillis, ) val payload = json.encodeToString(result) - val reportRequest = POST( + return POST( url = MDConstants.atHomePostUrl, headers = headers, body = payload.toRequestBody(JSON_MEDIA_TYPE), ) - - // Execute the report endpoint network call asynchronously to avoid blocking - // the reader from showing the image once it's fully loaded if the report call - // gets stuck, as it tend to happens sometimes. - client.newCall(reportRequest).enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) { - Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}") - } - - override fun onResponse(call: Call, response: Response) { - response.close() - } - }, - ) - - return response } companion object { private val JSON_MEDIA_TYPE = "application/json".toMediaType() + private val MD_AT_HOME_URL_REGEX = + """^https://[\w\d]+\.[\w\d]+\.mangadex(\b-test\b)?\.network.*${'$'}""".toRegex() + + private val REPORT_CALLBACK = object : Callback { + override fun onFailure(call: Call, e: okio.IOException) { + Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}") + } + + override fun onResponse(call: Call, response: Response) { + if (!response.isSuccessful) { + Log.e("MangaDex", "Error trying to POST report to MD@Home: HTTP error ${response.code}") + } + + response.close() + } + } } } diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdUserAgentInterceptor.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdUserAgentInterceptor.kt new file mode 100644 index 0000000000..1771daf945 --- /dev/null +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MdUserAgentInterceptor.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.extension.all.mangadex + +import android.content.SharedPreferences +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +/** + * Interceptor to set custom useragent for MangaDex + */ +class MdUserAgentInterceptor( + private val preferences: SharedPreferences, + private val dexLang: String, +) : Interceptor { + + private val SharedPreferences.customUserAgent + get() = getString( + MDConstants.getCustomUserAgentPrefKey(dexLang), + MDConstants.defaultUserAgent, + ) + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + val newUserAgent = preferences.customUserAgent + ?: return chain.proceed(originalRequest) + + val originalHeaders = originalRequest.headers + + val modifiedHeaders = originalHeaders.newBuilder() + .set("User-Agent", newUserAgent) + .build() + + val modifiedRequest = originalRequest.newBuilder() + .headers(modifiedHeaders) + .build() + + return chain.proceed(modifiedRequest) + } +} diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ResponseDto.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ResponseDto.kt index 47dd7ef5b9..68c7db9355 100644 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ResponseDto.kt +++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/dto/ResponseDto.kt @@ -10,7 +10,11 @@ data class PaginatedResponseDto<T : EntityDto>( val limit: Int = 0, val offset: Int = 0, val total: Int = 0, -) +) { + + val hasNextPage: Boolean + get() = limit + offset < total +} @Serializable data class ResponseDto<T : EntityDto>( diff --git a/src/all/mangapark/AndroidManifest.xml b/src/all/mangapark/AndroidManifest.xml index e832f73e17..9c5dbd1fba 100644 --- a/src/all/mangapark/AndroidManifest.xml +++ b/src/all/mangapark/AndroidManifest.xml @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" -package="eu.kanade.tachiyomi.extension"> - +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".all.mangapark.MangaParkUrlActivity" diff --git a/src/all/mangaplus/AndroidManifest.xml b/src/all/mangaplus/AndroidManifest.xml index 9e6a755f12..c308de1b74 100644 --- a/src/all/mangaplus/AndroidManifest.xml +++ b/src/all/mangaplus/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -41,4 +40,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/all/mangaplus/README.md b/src/all/mangaplus/README.md index 5a56838572..aaf8352d72 100644 --- a/src/all/mangaplus/README.md +++ b/src/all/mangaplus/README.md @@ -9,10 +9,10 @@ Table of Content ### Why chapters are missing in some titles? -MANGA Plus does not host all chapters due to their licensing politics. The website +MANGA Plus does not host all chapters for free due to their licensing politics. The website is designed to let the users read the weekly/monthly official simulreleases. If you want -to read old chapters of a series that does not have the complete list, you need to find -another sources. +to read old chapters of a series that does not have the complete list, you need subscribe +to one of their paid plans and read directly in their app instead. ### Why disabling split pages setting does not work? diff --git a/src/all/mangaplus/assets/i18n/messages_en.properties b/src/all/mangaplus/assets/i18n/messages_en.properties new file mode 100644 index 0000000000..75adbec291 --- /dev/null +++ b/src/all/mangaplus/assets/i18n/messages_en.properties @@ -0,0 +1,24 @@ +chapter_expired=The chapter reading period has expired. +image_quality=Image quality +image_quality_high=High +image_quality_low=Low +image_quality_medium=Medium +not_available=Title not available in this language. +rating=Rating: %s +rating_all_ages=All ages +rating_mature=Mature +rating_teen=Teen +rating_teen_plus=Teen Plus +schedule=Schedule: %s +schedule_bimonthly=Bimonthly +schedule_biweekly=Biweekly +schedule_everyday=Everyday +schedule_monthly=Monthly +schedule_other=Other +schedule_trimonthly=Trimonthly +schedule_weekly=Weekly +serialization=Serialization: %s +split_double_pages=Split double pages +split_double_pages_summary=Only a few titles supports disabling this setting. +title_removed=This title was removed from the MANGA Plus catalogue. +unknown_error=An unknown error happened. diff --git a/src/all/mangaplus/assets/i18n/messages_pt_br.properties b/src/all/mangaplus/assets/i18n/messages_pt_br.properties new file mode 100644 index 0000000000..86272f5351 --- /dev/null +++ b/src/all/mangaplus/assets/i18n/messages_pt_br.properties @@ -0,0 +1,24 @@ +chapter_expired=O período de leitura do capítulo expirou. +image_quality=Qualidade da imagem +image_quality_high=Alta +image_quality_low=Baixa +image_quality_medium=Média +not_available=Título não disponível neste idioma. +rating=Classificação: %s +rating_all_ages=Todas as idades +rating_mature=+18 +rating_teen=+10 +rating_teen_plus=+16 +schedule=Periodicidade: %s +schedule_bimonthly=Bimestral +schedule_biweekly=Bisemanal +schedule_everyday=Diário +schedule_monthly=Mensal +schedule_other=Outros +schedule_trimonthly=Trimestral +schedule_weekly=Semanal +serialization=Serialização: %s +split_double_pages=Dividir as páginas duplas +split_double_pages_summary=Somente poucos títulos suportam a desativação desta configuração. +title_removed=Este título foi removido do catálogo do MANGA Plus. +unknown_error=Um erro desconhecido ocorreu. diff --git a/src/all/mangaplus/assets/i18n/messages_vi.properties b/src/all/mangaplus/assets/i18n/messages_vi.properties new file mode 100644 index 0000000000..094aac664d --- /dev/null +++ b/src/all/mangaplus/assets/i18n/messages_vi.properties @@ -0,0 +1,10 @@ +chapter_expired=Đã hết thời gian đọc chương này. +image_quality=Chất lượng ảnh +image_quality_high=Cao +image_quality_low=Thấp +image_quality_medium=Vừa +not_available=Truyện không có sẵn ở ngôn ngữ này. +split_double_pages=Tách trang đôi +split_double_pages_summary=Chỉ một số truyện hỗ trợ không tách trang đôi. +title_removed=Truyện này đã bị gỡ khỏi MANGA Plus. +unknown_error=Đã xảy ra lỗi không xác định. \ No newline at end of file diff --git a/src/all/mangaplus/build.gradle b/src/all/mangaplus/build.gradle index 229805972b..3df450203c 100644 --- a/src/all/mangaplus/build.gradle +++ b/src/all/mangaplus/build.gradle @@ -6,7 +6,11 @@ ext { extName = 'MANGA Plus by SHUEISHA' pkgNameSuffix = 'all.mangaplus' extClass = '.MangaPlusFactory' - extVersionCode = 42 + extVersionCode = 49 +} + +dependencies { + implementation(project(":lib-i18n")) } apply from: "$rootDir/common.gradle" diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt index 75f16885c2..378f1951ab 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt @@ -5,6 +5,7 @@ import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import eu.kanade.tachiyomi.source.ConfigurableSource @@ -57,7 +58,14 @@ class MangaPlus( private val json: Json by injectLazy() - private val intl by lazy { MangaPlusIntl(langCode) } + private val intl by lazy { + Intl( + language = lang, + baseLanguage = "en", + availableLanguages = setOf("en", "pt-BR", "vi"), + classLoader = this::class.java.classLoader!!, + ) + } private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) @@ -73,6 +81,7 @@ class MangaPlus( override fun popularMangaRequest(page: Int): Request { val newHeaders = headersBuilder() .set("Referer", "$baseUrl/manga_list/hot") + .set("X-Page", page.toString()) .build() return GET("$API_URL/title_list/ranking?format=json", newHeaders) @@ -82,7 +91,7 @@ class MangaPlus( val result = response.asMangaPlusResponse() checkNotNull(result.success) { - result.error!!.langPopup(langCode)?.body ?: intl.unknownError + result.error!!.langPopup(langCode)?.body ?: intl["unknown_error"] } val titleList = result.success.titleRankingView!!.titles @@ -90,7 +99,13 @@ class MangaPlus( titleCache = titleList.associateBy(Title::titleId) - return MangasPage(titleList.map(Title::toSManga), hasNextPage = false) + val page = response.request.headers["X-Page"]!!.toInt() + val pageList = titleList + .drop((page - 1) * LISTING_ITEMS_PER_PAGE) + .take(LISTING_ITEMS_PER_PAGE) + val hasNextPage = (page + 1) * LISTING_ITEMS_PER_PAGE <= titleList.size + + return MangasPage(pageList.map(Title::toSManga), hasNextPage) } override fun latestUpdatesRequest(page: Int): Request { @@ -105,7 +120,7 @@ class MangaPlus( val result = response.asMangaPlusResponse() checkNotNull(result.success) { - result.error!!.langPopup(langCode)?.body ?: intl.unknownError + result.error!!.langPopup(langCode)?.body ?: intl["unknown_error"] } // Fetch all titles to get newer thumbnail URLs in the interceptor. @@ -138,6 +153,7 @@ class MangaPlus( val newHeaders = headersBuilder() .set("Referer", "$baseUrl/manga_list/all") + .set("X-Page", page.toString()) .build() val apiUrl = "$API_URL/title_list/allV2".toHttpUrl().newBuilder() @@ -151,7 +167,7 @@ class MangaPlus( val result = response.asMangaPlusResponse() checkNotNull(result.success) { - result.error!!.langPopup(langCode)?.body ?: intl.unknownError + result.error!!.langPopup(langCode)?.body ?: intl["unknown_error"] } if (result.success.titleDetailView != null) { @@ -163,7 +179,7 @@ class MangaPlus( } if (result.success.mangaViewer != null) { - checkNotNull(result.success.mangaViewer.titleId) { intl.chapterExpired } + checkNotNull(result.success.mangaViewer.titleId) { intl["chapter_expired"] } val titleId = result.success.mangaViewer.titleId val cachedTitle = titleCache?.get(titleId) @@ -173,12 +189,12 @@ class MangaPlus( val titleResult = client.newCall(titleRequest).execute().asMangaPlusResponse() checkNotNull(titleResult.success) { - titleResult.error!!.langPopup(langCode)?.body ?: intl.unknownError + titleResult.error!!.langPopup(langCode)?.body ?: intl["unknown_error"] } titleResult.success.titleDetailView!! .takeIf { it.title.language == langCode } - ?.toSManga() + ?.toSManga(intl) } return MangasPage(listOfNotNull(title), hasNextPage = false) @@ -197,7 +213,13 @@ class MangaPlus( title.author.orEmpty().contains(filter, ignoreCase = true) } - return MangasPage(searchResults.map(Title::toSManga), hasNextPage = false) + val page = response.request.headers["X-Page"]!!.toInt() + val pageList = searchResults + .drop((page - 1) * LISTING_ITEMS_PER_PAGE) + .take(LISTING_ITEMS_PER_PAGE) + val hasNextPage = (page + 1) * LISTING_ITEMS_PER_PAGE <= searchResults.size + + return MangasPage(pageList.map(Title::toSManga), hasNextPage) } // Remove the '#' and map to the new url format used in website. @@ -212,7 +234,7 @@ class MangaPlus( .set("Referer", "$baseUrl/titles/$titleId") .build() - return GET("$API_URL/title_detail?title_id=$titleId&format=json", newHeaders) + return GET("$API_URL/title_detailV3?title_id=$titleId&format=json", newHeaders) } override fun mangaDetailsParse(response: Response): SManga { @@ -222,17 +244,17 @@ class MangaPlus( val error = result.error!!.langPopup(langCode) when { - error?.subject == NOT_FOUND_SUBJECT -> intl.titleRemoved + error?.subject == NOT_FOUND_SUBJECT -> intl["title_removed"] !error?.body.isNullOrEmpty() -> error!!.body - else -> intl.unknownError + else -> intl["unknown_error"] } } val titleDetails = result.success.titleDetailView!! .takeIf { it.title.language == langCode } - ?: throw Exception(intl.notAvailable) + ?: throw Exception(intl["not_available"]) - return titleDetails.toSManga() + return titleDetails.toSManga(intl) } override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga.url) @@ -244,19 +266,18 @@ class MangaPlus( val error = result.error!!.langPopup(langCode) when { - error?.subject == NOT_FOUND_SUBJECT -> intl.titleRemoved + error?.subject == NOT_FOUND_SUBJECT -> intl["title_removed"] !error?.body.isNullOrEmpty() -> error!!.body - else -> intl.unknownError + else -> intl["unknown_error"] } } val titleDetailView = result.success.titleDetailView!! - val chapters = titleDetailView.firstChapterList + titleDetailView.lastChapterList - - return chapters.reversed() + return titleDetailView.chapterList .filterNot(Chapter::isExpired) .map(Chapter::toSChapter) + .reversed() } // Remove the '#' and map to the new url format used in website. @@ -290,9 +311,9 @@ class MangaPlus( val error = result.error!!.langPopup(langCode) when { - error?.subject == NOT_FOUND_SUBJECT -> intl.chapterExpired + error?.subject == NOT_FOUND_SUBJECT -> intl["chapter_expired"] !error?.body.isNullOrEmpty() -> error!!.body - else -> intl.unknownError + else -> intl["unknown_error"] } } @@ -322,8 +343,12 @@ class MangaPlus( override fun setupPreferenceScreen(screen: PreferenceScreen) { val qualityPref = ListPreference(screen.context).apply { key = "${QUALITY_PREF_KEY}_$lang" - title = intl.imageQuality - entries = arrayOf(intl.imageQualityLow, intl.imageQualityMedium, intl.imageQualityHigh) + title = intl["image_quality"] + entries = arrayOf( + intl["image_quality_low"], + intl["image_quality_medium"], + intl["image_quality_high"], + ) entryValues = QUALITY_PREF_ENTRY_VALUES setDefaultValue(QUALITY_PREF_DEFAULT_VALUE) summary = "%s" @@ -331,8 +356,8 @@ class MangaPlus( val splitPref = SwitchPreferenceCompat(screen.context).apply { key = "${SPLIT_PREF_KEY}_$lang" - title = intl.splitDoublePages - summary = intl.splitDoublePagesSummary + title = intl["split_double_pages"] + summary = intl["split_double_pages_summary"] setDefaultValue(SPLIT_PREF_DEFAULT_VALUE) } @@ -402,7 +427,9 @@ class MangaPlus( companion object { private const val API_URL = "https://jumpg-webapi.tokyo-cdn.com/api" private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" + + private const val LISTING_ITEMS_PER_PAGE = 20 private const val QUALITY_PREF_KEY = "imageResolution" private val QUALITY_PREF_ENTRY_VALUES = arrayOf("low", "high", "super_high") diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt index c57c459f10..48b3ed5e7a 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusDto.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.extension.all.mangaplus +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName @@ -57,51 +58,86 @@ data class TitleDetailView( val title: Title, val titleImageUrl: String, val overview: String? = null, - val backgroundImageUrl: String, val nextTimeStamp: Int = 0, val viewingPeriodDescription: String = "", val nonAppearanceInfo: String = "", - val firstChapterList: List<Chapter> = emptyList(), - val lastChapterList: List<Chapter> = emptyList(), + val chapterListGroup: List<ChapterListGroup> = emptyList(), val isSimulReleased: Boolean = false, + val rating: Rating = Rating.ALL_AGES, val chaptersDescending: Boolean = true, + val titleLabels: TitleLabels, + val label: Label? = Label(LabelCode.WEEKLY_SHOUNEN_JUMP), ) { + + val chapterList: List<Chapter> by lazy { + // Doesn't include `midChapterList` by design as their site API returns it + // just for visual representation to redirect users to their app. The extension + // intends to allow users to read only what they can already read in their site. + chapterListGroup.flatMap { it.firstChapterList + it.lastChapterList } + } + private val isWebtoon: Boolean - get() = firstChapterList.all(Chapter::isVerticalOnly) && - lastChapterList.all(Chapter::isVerticalOnly) + get() = chapterList.isNotEmpty() && chapterList.all(Chapter::isVerticalOnly) private val isOneShot: Boolean - get() = chapterCount == 1 && firstChapterList.firstOrNull() + get() = chapterList.size == 1 && chapterList.firstOrNull() ?.name?.equals("one-shot", true) == true - private val chapterCount: Int - get() = firstChapterList.size + lastChapterList.size - private val isReEdition: Boolean get() = viewingPeriodDescription.contains(REEDITION_REGEX) private val isCompleted: Boolean - get() = nonAppearanceInfo.contains(COMPLETED_REGEX) || isOneShot + get() = nonAppearanceInfo.contains(COMPLETED_REGEX) || isOneShot || + titleLabels.releaseSchedule == ReleaseSchedule.COMPLETED || + titleLabels.releaseSchedule == ReleaseSchedule.DISABLED + + private val isSimulpub: Boolean + get() = isSimulReleased || titleLabels.isSimulpub private val isOnHiatus: Boolean get() = nonAppearanceInfo.contains(HIATUS_REGEX) - private val genres: List<String> - get() = listOfNotNull( - "Simulrelease".takeIf { isSimulReleased && !isReEdition && !isOneShot }, - "One-shot".takeIf { isOneShot }, - "Re-edition".takeIf { isReEdition }, - "Webtoon".takeIf { isWebtoon }, - ) + private fun createGenres(intl: Intl): List<String> = buildList { + if (isSimulpub && !isReEdition && !isOneShot && !isCompleted) { + add("Simulrelease") + } + + if (isOneShot) { + add("One-shot") + } + + if (isReEdition) { + add("Re-edition") + } + + if (isWebtoon) { + add("Webtoon") + } + + if (label?.magazine != null) { + add(intl.format("serialization", label.magazine)) + } + + if (!isCompleted) { + val scheduleLabel = intl["schedule_" + titleLabels.releaseSchedule.toString().lowercase()] + add(intl.format("schedule", scheduleLabel)) + } + + val ratingLabel = intl["rating_" + rating.toString().lowercase()] + add(intl.format("rating", ratingLabel)) + } - fun toSManga(): SManga = title.toSManga().apply { - description = (overview.orEmpty() + "\n\n" + viewingPeriodDescription).trim() + private val viewingInformation: String? + get() = viewingPeriodDescription.takeIf { !isCompleted } + + fun toSManga(intl: Intl): SManga = title.toSManga().apply { + description = "${overview.orEmpty()}\n\n${viewingInformation.orEmpty()}".trim() status = when { isCompleted -> SManga.COMPLETED isOnHiatus -> SManga.ON_HIATUS else -> SManga.ONGOING } - genre = genres.joinToString() + genre = createGenres(intl).joinToString() } companion object { @@ -111,6 +147,92 @@ data class TitleDetailView( } } +@Serializable +data class TitleLabels( + val releaseSchedule: ReleaseSchedule = ReleaseSchedule.DISABLED, + val isSimulpub: Boolean = false, +) + +enum class ReleaseSchedule { + DISABLED, + EVERYDAY, + WEEKLY, + BIWEEKLY, + MONTHLY, + BIMONTHLY, + TRIMONTHLY, + OTHER, + COMPLETED, +} + +@Serializable +enum class Rating { + @SerialName("ALLAGE") + ALL_AGES, + TEEN, + + @SerialName("TEENPLUS") + TEEN_PLUS, + MATURE, +} + +@Serializable +data class Label(val label: LabelCode? = LabelCode.WEEKLY_SHOUNEN_JUMP) { + val magazine: String? + get() = when (label) { + LabelCode.WEEKLY_SHOUNEN_JUMP -> "Weekly Shounen Jump" + LabelCode.JUMP_SQUARE -> "Jump SQ." + LabelCode.V_JUMP -> "V Jump" + LabelCode.SHOUNEN_JUMP_GIGA -> "Shounen Jump GIGA" + LabelCode.WEEKLY_YOUNG_JUMP -> "Weekly Young Jump" + LabelCode.TONARI_NO_YOUNG_JUMP -> "Tonari no Young Jump" + LabelCode.SHOUNEN_JUMP_PLUS -> "Shounen Jump+" + LabelCode.MANGA_PLUS_CREATORS -> "MANGA Plus Creators" + LabelCode.SAIKYOU_JUMP -> "Saikyou Jump" + else -> null + } +} + +@Serializable +enum class LabelCode { + @SerialName("CREATORS") + MANGA_PLUS_CREATORS, + + @SerialName("GIGA") + SHOUNEN_JUMP_GIGA, + + @SerialName("J_PLUS") + SHOUNEN_JUMP_PLUS, + + OTHERS, + + REVIVAL, + + @SerialName("SKJ") + SAIKYOU_JUMP, + + @SerialName("SQ") + JUMP_SQUARE, + + @SerialName("TYJ") + TONARI_NO_YOUNG_JUMP, + + @SerialName("VJ") + V_JUMP, + + @SerialName("YJ") + WEEKLY_YOUNG_JUMP, + + @SerialName("WSJ") + WEEKLY_SHOUNEN_JUMP, +} + +@Serializable +data class ChapterListGroup( + val firstChapterList: List<Chapter> = emptyList(), + val lastChapterList: List<Chapter> = emptyList(), +) + @Serializable data class MangaViewer( val pages: List<MangaPlusPage> = emptyList(), @@ -124,7 +246,6 @@ data class Title( val name: String, val author: String? = null, val portraitImageUrl: String, - val landscapeImageUrl: String, val viewCount: Int = 0, val language: Language? = Language.ENGLISH, ) { @@ -146,6 +267,7 @@ enum class Language { PORTUGUESE_BR, RUSSIAN, THAI, + VIETNAMESE, } @Serializable diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt index 37a56d1a30..83b5a3c1b8 100644 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt +++ b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt @@ -12,5 +12,6 @@ class MangaPlusFactory : SourceFactory { MangaPlus("pt-BR", "ptb", Language.PORTUGUESE_BR), MangaPlus("ru", "rus", Language.RUSSIAN), MangaPlus("th", "tha", Language.THAI), + MangaPlus("vi", "vie", Language.VIETNAMESE), ) } diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusIntl.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusIntl.kt deleted file mode 100644 index 028768e97f..0000000000 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusIntl.kt +++ /dev/null @@ -1,54 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangaplus - -class MangaPlusIntl(lang: Language) { - - val imageQuality: String = when (lang) { - Language.PORTUGUESE_BR -> "Qualidade da imagem" - else -> "Image quality" - } - - val imageQualityLow: String = when (lang) { - Language.PORTUGUESE_BR -> "Baixa" - else -> "Low" - } - - val imageQualityMedium: String = when (lang) { - Language.PORTUGUESE_BR -> "Média" - else -> "Medium" - } - - val imageQualityHigh: String = when (lang) { - Language.PORTUGUESE_BR -> "Alta" - else -> "High" - } - - val splitDoublePages: String = when (lang) { - Language.PORTUGUESE_BR -> "Dividir as páginas duplas" - else -> "Split double pages" - } - - val splitDoublePagesSummary: String = when (lang) { - Language.PORTUGUESE_BR -> "Somente poucos títulos suportam a desativação desta configuração." - else -> "Only a few titles supports disabling this setting." - } - - val chapterExpired: String = when (lang) { - Language.PORTUGUESE_BR -> "O período de leitura do capítulo expirou." - else -> "The chapter reading period has expired." - } - - val notAvailable: String = when (lang) { - Language.PORTUGUESE_BR -> "Título não disponível neste idioma." - else -> "Title not available in this language." - } - - val unknownError: String = when (lang) { - Language.PORTUGUESE_BR -> "Um erro desconhecido ocorreu." - else -> "An unknown error happened." - } - - val titleRemoved: String = when (lang) { - Language.PORTUGUESE_BR -> "Este título foi removido do catálogo do MANGA Plus." - else -> "This title was removed from the MANGA Plus catalogue." - } -} diff --git a/src/all/mangapluscreators/AndroidManifest.xml b/src/all/mangapluscreators/AndroidManifest.xml index 389c51256f..8072ee00db 100644 --- a/src/all/mangapluscreators/AndroidManifest.xml +++ b/src/all/mangapluscreators/AndroidManifest.xml @@ -1,3 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> - +<manifest /> diff --git a/src/all/mangatoon/AndroidManifest.xml b/src/all/mangatoon/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/mangatoon/AndroidManifest.xml +++ b/src/all/mangatoon/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/mangatoon/build.gradle b/src/all/mangatoon/build.gradle index c855ece11f..73fe35f9cb 100644 --- a/src/all/mangatoon/build.gradle +++ b/src/all/mangatoon/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MangaToon (Limited)' pkgNameSuffix = 'all.mangatoon' extClass = '.MangaToonFactory' - extVersionCode = 4 + extVersionCode = 5 } apply from: "$rootDir/common.gradle" diff --git a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt b/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt index 29bc140601..5e73af0a8c 100644 --- a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt +++ b/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt @@ -13,6 +13,7 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import org.jsoup.select.Elements import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -59,8 +60,8 @@ open class MangaToon( override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { title = element.select("div.content-title").text().trim() - thumbnail_url = element.select("img").attr("abs:src").toNormalPosterUrl() - url = element.selectFirst("a")!!.attr("href") + thumbnail_url = element.select("img").imgAttr().toNormalPosterUrl() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) } override fun popularMangaNextPageSelector() = "span.next" @@ -83,12 +84,12 @@ open class MangaToon( return GET(searchUrl, headers) } - override fun searchMangaSelector() = "div.comics-result div.recommend-item" + override fun searchMangaSelector() = "div.comics-result div.recommend-item:has(a[abs:href^=$baseUrl])" override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { title = element.select("div.recommend-comics-title").text().trim() - thumbnail_url = element.select("img").attr("abs:src").toNormalPosterUrl() - url = element.selectFirst("a")!!.attr("href") + thumbnail_url = element.select("img").imgAttr().toNormalPosterUrl() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) } override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() @@ -104,8 +105,10 @@ open class MangaToon( .sorted() .joinToString { it.trim() } status = document.select("div.detail-status").text().trim().toStatus() - thumbnail_url = document.select("div.detail-img img.ori-image").attr("abs:src") - .toNormalPosterUrl() + val thumbnail = document.select("div.detail-img img").imgAttr().toNormalPosterUrl() + if (!thumbnail.contains("cartoon-big-images")) { + thumbnail_url = thumbnail + } } override fun chapterListRequest(manga: SManga): Request { @@ -142,12 +145,12 @@ open class MangaToon( chapter_number = element.select("div.episode-number").text().trim() .toFloatOrNull() ?: -1f date_upload = element.select("div.episode-date span.open-date").text().toDate() - url = element.attr("href") + setUrlWithoutDomain(element.attr("href")) } override fun pageListParse(document: Document): List<Page> { return document.select("div.pictures div img:first-child") - .mapIndexed { i, element -> Page(i, "", element.attr("abs:src")) } + .mapIndexed { i, element -> Page(i, "", element.imgAttr()) } .takeIf { it.isNotEmpty() } ?: throw Exception(lockedError) } @@ -158,6 +161,13 @@ open class MangaToon( .getOrNull() ?: 0L } + protected open fun Element.imgAttr(): String = when { + hasAttr("data-src") -> attr("abs:data-src") + else -> attr("abs:src") + } + + protected open fun Elements.imgAttr(): String = this.first()!!.imgAttr() + private fun String.toNormalPosterUrl(): String = replace(POSTER_SUFFIX, "$1") private fun String.toStatus(): Int = when (lowercase(locale)) { diff --git a/src/all/mangaup/AndroidManifest.xml b/src/all/mangaup/AndroidManifest.xml index 02005096f2..964fa77eb4 100644 --- a/src/all/mangaup/AndroidManifest.xml +++ b/src/all/mangaup/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/all/mango/AndroidManifest.xml b/src/all/mango/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/mango/AndroidManifest.xml +++ b/src/all/mango/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/meituatop/AndroidManifest.xml b/src/all/meituatop/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/meituatop/AndroidManifest.xml +++ b/src/all/meituatop/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/meituatop/build.gradle b/src/all/meituatop/build.gradle index d477d89667..dc04b414c3 100644 --- a/src/all/meituatop/build.gradle +++ b/src/all/meituatop/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Meitua.top' pkgNameSuffix = 'all.meituatop' extClass = '.MeituaTop' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } diff --git a/src/all/meituatop/src/eu/kanade/tachiyomi/extension/all/meituatop/MeituaTop.kt b/src/all/meituatop/src/eu/kanade/tachiyomi/extension/all/meituatop/MeituaTop.kt index 1c2a4bccf0..3f1d6f91fe 100644 --- a/src/all/meituatop/src/eu/kanade/tachiyomi/extension/all/meituatop/MeituaTop.kt +++ b/src/all/meituatop/src/eu/kanade/tachiyomi/extension/all/meituatop/MeituaTop.kt @@ -23,7 +23,7 @@ class MeituaTop : HttpSource() { override val lang = "all" override val supportsLatest = false - override val baseUrl = "https://meitu1.one" + override val baseUrl = "https://meitu1.xyz" override fun popularMangaRequest(page: Int) = GET("$baseUrl/arttype/0b-$page.html", headers) @@ -75,7 +75,7 @@ class MeituaTop : HttpSource() { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { val chapter = SChapter.create().apply { url = manga.url - name = manga.title + name = "Gallery" date_upload = dateFormat.parse(manga.description!!)!!.time chapter_number = -2f } diff --git a/src/all/myreadingmanga/AndroidManifest.xml b/src/all/myreadingmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/myreadingmanga/AndroidManifest.xml +++ b/src/all/myreadingmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/myreadingmanga/build.gradle b/src/all/myreadingmanga/build.gradle index fa8dcf9f02..3286a3d838 100644 --- a/src/all/myreadingmanga/build.gradle +++ b/src/all/myreadingmanga/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MyReadingManga' pkgNameSuffix = 'all.myreadingmanga' extClass = '.MyReadingMangaFactory' - extVersionCode = 47 + extVersionCode = 48 isNsfw = true } diff --git a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt index e0c81f3927..5ffee4c8ef 100644 --- a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt +++ b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt @@ -106,7 +106,7 @@ open class MyReadingManga(override val lang: String, private val siteLang: Strin return manga } - private val extensionRegex = Regex("""\.(jpg|png|jpeg)""") + private val extensionRegex = Regex("""\.(jpg|png|jpeg|webp)""") private fun getImage(element: Element): String { return when { @@ -217,7 +217,7 @@ open class MyReadingManga(override val lang: String, private val siteLang: Strin // Pages override fun pageListParse(document: Document): List<Page> { - return (document.select("div > img") + document.select("div.separator img[data-src]")) + return (document.select("div img") + document.select("div.separator img[data-src]")) .map { getImage(it) } .distinct() .mapIndexed { i, url -> Page(i, "", url) } diff --git a/src/all/netcomics/AndroidManifest.xml b/src/all/netcomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/netcomics/AndroidManifest.xml +++ b/src/all/netcomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/nhentai/AndroidManifest.xml b/src/all/nhentai/AndroidManifest.xml index ce28ef43d9..24458dcdaa 100644 --- a/src/all/nhentai/AndroidManifest.xml +++ b/src/all/nhentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/all/nhentai/build.gradle b/src/all/nhentai/build.gradle index a66b450855..45f226e94c 100644 --- a/src/all/nhentai/build.gradle +++ b/src/all/nhentai/build.gradle @@ -5,8 +5,12 @@ ext { extName = 'NHentai' pkgNameSuffix = 'all.nhentai' extClass = '.NHFactory' - extVersionCode = 37 + extVersionCode = 39 isNsfw = true } +dependencies { + implementation(project(":lib-randomua")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt index 8023aeebd1..e11032add5 100644 --- a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt +++ b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt @@ -2,12 +2,18 @@ package eu.kanade.tachiyomi.extension.all.nhentai import android.app.Application import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getArtists import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getGroups import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getNumPages import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTagDescription import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTags import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTime +import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.interceptor.rateLimit @@ -44,14 +50,21 @@ open class NHentai( override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(4) - .build() - private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .setRandomUserAgent( + userAgentType = preferences.getPrefUAType(), + customUA = preferences.getPrefCustomUA(), + filterInclude = listOf("chrome"), + ) + .rateLimit(4) + .build() + } + private var displayFullTitle: Boolean = when (preferences.getString(TITLE_PREF, "full")) { "full" -> true else -> false @@ -60,13 +73,14 @@ open class NHentai( private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""") private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim() - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val serverPref = androidx.preference.ListPreference(screen.context).apply { + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { key = TITLE_PREF title = TITLE_PREF entries = arrayOf("Full Title", "Short Title") entryValues = arrayOf("full", "short") summary = "%s" + setDefaultValue("full") setOnPreferenceChangeListener { _, newValue -> displayFullTitle = when (newValue) { @@ -75,13 +89,9 @@ open class NHentai( } true } - } - - if (!preferences.contains(TITLE_PREF)) { - preferences.edit().putString(TITLE_PREF, "full").apply() - } + }.also(screen::addPreference) - screen.addPreference(serverPref) + addRandomUAPreferenceToScreen(screen) } override fun latestUpdatesRequest(page: Int) = GET(if (nhLang.isBlank()) "$baseUrl/?page=$page" else "$baseUrl/language/$nhLang/?page=$page", headers) diff --git a/src/all/ninemanga/AndroidManifest.xml b/src/all/ninemanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/ninemanga/AndroidManifest.xml +++ b/src/all/ninemanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/ninenineninehentai/AndroidManifest.xml b/src/all/ninenineninehentai/AndroidManifest.xml new file mode 100644 index 0000000000..d9e582cf32 --- /dev/null +++ b/src/all/ninenineninehentai/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <application android:icon="@mipmap/ic_launcher"> + <activity + android:name=".all.ninenineninehentai.NineNineNineHentaiUrlActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:host="999hentai.net"/> + <data android:scheme="https"/> + <data android:pathPattern="/hchapter/..*"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/all/ninenineninehentai/build.gradle b/src/all/ninenineninehentai/build.gradle new file mode 100644 index 0000000000..170bde6480 --- /dev/null +++ b/src/all/ninenineninehentai/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = '999Hentai' + pkgNameSuffix = 'all.ninenineninehentai' + extClass = '.NineNineNineHentaiFactory' + extVersionCode = 5 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/ninenineninehentai/res/mipmap-hdpi/ic_launcher.png b/src/all/ninenineninehentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f5d448ee4f Binary files /dev/null and b/src/all/ninenineninehentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/ninenineninehentai/res/mipmap-mdpi/ic_launcher.png b/src/all/ninenineninehentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9296153092 Binary files /dev/null and b/src/all/ninenineninehentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/ninenineninehentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/ninenineninehentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2a76504d50 Binary files /dev/null and b/src/all/ninenineninehentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/ninenineninehentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/ninenineninehentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8df4c773f9 Binary files /dev/null and b/src/all/ninenineninehentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/ninenineninehentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/ninenineninehentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..0f3523c570 Binary files /dev/null and b/src/all/ninenineninehentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/ninenineninehentai/res/web_hi_res_512.png b/src/all/ninenineninehentai/res/web_hi_res_512.png new file mode 100644 index 0000000000..7e94372b4c Binary files /dev/null and b/src/all/ninenineninehentai/res/web_hi_res_512.png differ diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentai.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentai.kt new file mode 100644 index 0000000000..cda524c454 --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentai.kt @@ -0,0 +1,334 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import android.annotation.SuppressLint +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.extension.all.ninenineninehentai.Url.Companion.toAbsUrl +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale + +open class NineNineNineHentai( + final override val lang: String, + private val siteLang: String = lang, +) : HttpSource(), ConfigurableSource { + + override val name = "999Hentai" + + override val baseUrl = "https://999hentai.net" + + private val apiUrl = "https://api.999hentai.net/api" + + override val supportsLatest = true + + private val json: Json by injectLazy() + + override val client = network.cloudflareClient.newBuilder() + .addInterceptor { chain -> + val request = chain.request() + val url = request.url + + if (url.host != "127.0.0.1") { + return@addInterceptor chain.proceed(request) + } + + val newRequest = request.newBuilder() + .url( + url.newBuilder() + .host(preference.cdnUrl) + .build(), + ).build() + + return@addInterceptor chain.proceed(newRequest) + } + .rateLimit(1) + .build() + + private val preference by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + + override fun headersBuilder() = super.headersBuilder() + .set("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int): Request { + val payload = GraphQL( + PopularVariables(size, page, 1, siteLang), + POPULAR_QUERY, + ).toJsonRequestBody() + + return POST(apiUrl, headers, payload) + } + + override fun popularMangaParse(response: Response) = browseMangaParse<PopularResponse>(response) + + override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList()) + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return if (query.startsWith(SEARCH_PREFIX)) { + val mangaId = query.substringAfter(SEARCH_PREFIX) + client.newCall(mangaFromIDRequest(mangaId)) + .asObservableSuccess() + .map(::searchMangaFromIDParse) + } else { + super.fetchSearchManga(page, query, filters) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val payload = GraphQL( + SearchVariables( + size = size, + page = page, + search = SearchPayload( + query = query.trim().takeUnless { it.isEmpty() }, + language = siteLang, + sortBy = filters.firstInstanceOrNull<SortFilter>()?.selected, + format = filters.firstInstanceOrNull<FormatFilter>()?.selected, + tags = filters.firstInstanceOrNull<IncludedTagFilter>()?.tags, + excludeTags = filters.firstInstanceOrNull<ExcludedTagFilter>()?.tags, + pagesRangeStart = filters.firstInstanceOrNull<MinPageFilter>()?.value, + pagesRangeEnd = filters.firstInstanceOrNull<MaxPageFilter>()?.value, + ), + ), + SEARCH_QUERY, + ).toJsonRequestBody() + + return POST(apiUrl, headers, payload) + } + + override fun searchMangaParse(response: Response) = browseMangaParse<SearchResponse>(response) + override fun getFilterList() = getFilters() + + private fun mangaFromIDRequest(id: String): Request { + val payload = GraphQL( + IdVariables(id), + DETAILS_QUERY, + ).toJsonRequestBody() + + return POST(apiUrl, headers, payload) + } + + private fun searchMangaFromIDParse(response: Response): MangasPage { + val res = response.parseAs<ApiDetailsResponse>() + + val manga = res.data.details + .takeIf { it.language == siteLang || lang == "all" } + ?.let { manga -> + preference.dateMap = preference.dateMap.also { dateMap -> + manga.uploadDate?.let { dateMap[manga.id] = it } + } + manga.toSManga(preference.shortTitle) + } + + return MangasPage(listOfNotNull(manga), false) + } + + override fun mangaDetailsRequest(manga: SManga): Request { + return mangaFromIDRequest(manga.url) + } + + override fun mangaDetailsParse(response: Response): SManga { + val res = response.parseAs<ApiDetailsResponse>() + val manga = res.data.details + + preference.dateMap = preference.dateMap.also { dateMap -> + manga.uploadDate?.let { dateMap[manga.id] = it } + } + + return manga.toSManga(preference.shortTitle) + } + + override fun getMangaUrl(manga: SManga) = "$baseUrl/hchapter/${manga.url}" + + override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { + val group = manga.description + ?.substringAfter("Group:", "") + ?.substringBefore("\n") + ?.trim() + ?.takeUnless { it.isEmpty() } + + return Observable.just( + listOf( + SChapter.create().apply { + name = "Chapter" + url = manga.url + date_upload = preference.dateMap[manga.url].parseDate() + scanlator = group + }, + ), + ) + } + + override fun getChapterUrl(chapter: SChapter) = "$baseUrl/hchapter/${chapter.url}" + + override fun pageListRequest(chapter: SChapter): Request { + val payload = GraphQL( + IdVariables(chapter.url), + PAGES_QUERY, + ).toJsonRequestBody() + + return POST(apiUrl, headers, payload) + } + + override fun pageListParse(response: Response): List<Page> { + val res = response.parseAs<ApiPageListResponse>() + + val pages = res.data.chapter.pages?.firstOrNull() + ?: return emptyList() + + val cdnUrl = "https://${getUpdatedCdn(res.data.chapter.id)}/" + val cdn = pages.urlPart.toAbsUrl(cdnUrl) + + val selectedImages = when (preference.getString(PREF_IMG_QUALITY_KEY, "original")) { + "medium" -> pages.qualityMedium?.mapIndexed { i, it -> + it ?: pages.qualityOriginal[i] + } + else -> pages.qualityOriginal + } ?: pages.qualityOriginal + + return selectedImages.mapIndexed { index, image -> + Page(index, "", "$cdn/${image.url}") + } + } + + private fun getUpdatedCdn(chapterId: String): String { + val url = "$baseUrl/hchapter/$chapterId" + val document = client.newCall(GET(url, headers)) + .execute().use { it.asJsoup() } + + val cdnHost = document.selectFirst("meta[property=og:image]") + ?.attr("content") + ?.toHttpUrlOrNull() + ?.host + + return cdnHost?.also { + preference.cdnUrl = it + } ?: preference.cdnUrl + } + + private inline fun <reified T> String.parseAs(): T = + json.decodeFromString(this) + + private inline fun <reified T> Response.parseAs(): T = + use { body.string() }.parseAs() + + private inline fun <reified T> List<*>.firstInstanceOrNull(): T? = + filterIsInstance<T>().firstOrNull() + + private inline fun <reified T : Any> T.toJsonRequestBody(): RequestBody = + json.encodeToString(this) + .toRequestBody(JSON_MEDIA_TYPE) + + private fun String?.parseDate(): Long { + return runCatching { + dateFormat.parse(this!!.trim())!!.time + }.getOrDefault(0L) + } + + private inline fun <reified T : BrowseResponse> browseMangaParse(response: Response): MangasPage { + val res = response.parseAs<Data<T>>() + val mangas = res.data.chapters.edges + val dateMap = preference.dateMap + val useShortTitle = preference.shortTitle + val entries = mangas.map { manga -> + manga.uploadDate?.let { dateMap[manga.id] = it } + manga.toSManga(useShortTitle) + } + preference.dateMap = dateMap + val hasNextPage = mangas.size == size + + return MangasPage(entries, hasNextPage) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = PREF_IMG_QUALITY_KEY + title = "Default Image Quality" + entries = arrayOf("Original", "Medium") + entryValues = arrayOf("original", "medium") + setDefaultValue("original") + summary = "%s" + }.also(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = PREF_SHORT_TITLE + title = "Display Short Titles" + summaryOff = "Showing Long Titles" + summaryOn = "Showing short Titles" + setDefaultValue(false) + }.also(screen::addPreference) + } + + private var SharedPreferences.dateMap: MutableMap<String, String> + get() { + val jsonMap = getString(PREF_DATE_MAP_KEY, "{}")!! + val dateMap = runCatching { jsonMap.parseAs<MutableMap<String, String>>() } + return dateMap.getOrDefault(mutableMapOf()) + } + + @SuppressLint("ApplySharedPref") + set(dateMap) { + edit() + .putString(PREF_DATE_MAP_KEY, json.encodeToString(dateMap)) + .commit() + } + + private var SharedPreferences.cdnUrl: String + get() = getString(PREF_CDN_URL, DEFAULT_CDN) ?: DEFAULT_CDN + + @SuppressLint("ApplySharedPref") + set(cdnUrl) { + edit().putString(PREF_CDN_URL, cdnUrl).commit() + } + + private val SharedPreferences.shortTitle get() = getBoolean(PREF_SHORT_TITLE, false) + + override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not Used") + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not Used") + + companion object { + private const val size = 20 + const val SEARCH_PREFIX = "id:" + + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + private val dateFormat by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) + } + + private const val PREF_DATE_MAP_KEY = "pref_date_map" + private const val PREF_CDN_URL = "pref_cdn_url" + private const val PREF_IMG_QUALITY_KEY = "pref_image_quality" + private const val PREF_SHORT_TITLE = "pref_short_title" + + private const val DEFAULT_CDN = "edge.fast4speed.rsvp" + } +} diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiDto.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiDto.kt new file mode 100644 index 0000000000..fff6a64213 --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiDto.kt @@ -0,0 +1,169 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.util.Locale + +typealias ApiDetailsResponse = Data<DetailsResponse> + +typealias ApiPageListResponse = Data<PageList> + +@Serializable +data class Data<T>(val data: T) + +@Serializable +data class Edges<T>(val edges: List<T>) + +interface BrowseResponse { + val chapters: Edges<ChapterResponse> +} + +@Serializable +data class PopularResponse( + @SerialName("queryPopularChapters") override val chapters: Edges<ChapterResponse>, +) : BrowseResponse + +@Serializable +data class SearchResponse( + @SerialName("queryChapters") override val chapters: Edges<ChapterResponse>, +) : BrowseResponse + +@Serializable +data class DetailsResponse( + @SerialName("queryChapter") val details: ChapterResponse, +) + +@Serializable +data class ChapterResponse( + @SerialName("_id") val id: String, + val name: String, + val uploadDate: String? = null, + val format: String? = null, + val description: String? = null, + val language: String? = null, + val pages: Int? = null, + @SerialName("firstPics") val cover: List<Url>? = emptyList(), + val tags: List<Tag>? = emptyList(), +) { + fun toSManga(shortTitle: Boolean) = SManga.create().apply { + url = id + title = if (shortTitle) name.replace(shortenTitleRegex, "").trim() else name + thumbnail_url = cover?.firstOrNull()?.absUrl + author = this@ChapterResponse.author + artist = author + genre = genres + description = buildString { + if (!this@ChapterResponse.description.isNullOrEmpty()) append(this@ChapterResponse.description.trim(), "\n\n") + if (formatParsed != null) append("Format: ${formatParsed}\n") + if (languageParsed != null) append("Language: $languageParsed\n") + if (group != null) append("Group: $group\n") + if (characters != null) append("Character(s): $characters\n") + if (parody != null) append("Parody: $parody\n") + if (magazine != null) append("Magazine: $magazine\n") + if (pages != null) append("Pages: $pages\n") + } + status = SManga.COMPLETED + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + initialized = true + } + + private val formatParsed = when (format) { + "artistcg" -> "ArtistCG" + "gamecg" -> "GameCG" + "imageset" -> "ImageSet" + else -> format?.capitalize() + } + + private val languageParsed = when (language) { + "en" -> "English" + "jp" -> "Japanese" + "cn" -> "Chinese" + "es" -> "Spanish" + else -> language + } + + private val author = tags?.firstOrNull { it.tagType == "artist" }?.tagName?.capitalize() + + private val group = tags?.filter { it.tagType == "group" } + ?.joinToString { it.tagName.capitalize() } + ?.takeUnless { it.isEmpty() } + + private val characters = tags?.filter { it.tagType == "character" } + ?.joinToString { it.tagName.capitalize() } + ?.takeUnless { it.isEmpty() } + + private val parody = tags?.filter { it.tagType == "parody" } + ?.joinToString { it.tagName.capitalize() } + ?.takeUnless { it.isEmpty() } + + private val magazine = tags?.filter { it.tagType == "magazine" } + ?.joinToString { it.tagName.capitalize() } + ?.takeUnless { it.isEmpty() } + + private val genres = tags?.filterNot { it.tagType in filterTags } + ?.joinToString { it.tagName.capitalize() } + ?.takeUnless { it.isEmpty() } + + companion object { + private val filterTags = listOf("artist", "group", "character", "parody", "magazine") + private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""") + + private fun String.capitalize(): String { + return this.trim().split(" ").joinToString(" ") { word -> + word.replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase( + Locale.getDefault(), + ) + } else { + it.toString() + } + } + } + } + } +} + +@Serializable +data class Url(val url: String) { + val absUrl get() = url.toAbsUrl() + + companion object { + fun String.toAbsUrl(baseUrl: String = loUrl): String { + return if (this.matches(urlRegex)) { + this + } else { + baseUrl + this + } + } + + private const val loUrl = "https://127.0.0.1/" + private val urlRegex = Regex("^https?://.*") + } +} + +@Serializable +data class Tag( + val tagName: String, + val tagType: String? = "genre", +) + +@Serializable +data class PageList( + @SerialName("queryChapter") val chapter: PageUrl, +) + +@Serializable +data class PageUrl( + @SerialName("_id") val id: String, + @SerialName("pictureUrls") val pages: List<Pages?>? = emptyList(), +) + +@Serializable +data class Pages( + @SerialName("picCdn") val urlPart: String, + @SerialName("pics") val qualityOriginal: List<Url>, + @SerialName("picsM") val qualityMedium: List<Url?>? = emptyList(), +) diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFactory.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFactory.kt new file mode 100644 index 0000000000..eb3d6ba7be --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFactory.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import eu.kanade.tachiyomi.source.SourceFactory + +class NineNineNineHentaiFactory : SourceFactory { + override fun createSources() = listOf( + NineNineNineHentai("all"), + NineNineNineHentai("en"), + NineNineNineHentai("ja", "jp"), + NineNineNineHentai("zh", "cn"), + NineNineNineHentai("es"), + ) +} diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFilters.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFilters.kt new file mode 100644 index 0000000000..61704e7576 --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiFilters.kt @@ -0,0 +1,69 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList + +abstract class SelectFilter( + displayName: String, + private val options: Array<Pair<String, String>>, +) : Filter.Select<String>( + displayName, + options.map { it.first }.toTypedArray(), +) { + val selected get() = options[state].second.takeUnless { it.isEmpty() } +} + +abstract class TextFilter(name: String) : Filter.Text(name) + +abstract class TagFilter(name: String) : TextFilter(name) { + val tags get() = state.split(",") + .map { it.trim().lowercase() } + .filter { it.isNotEmpty() } + .takeUnless { it.isEmpty() } +} + +abstract class PageFilter(name: String) : TextFilter(name) { + val value get() = state.trim().toIntOrNull() +} + +class SortFilter : SelectFilter( + "Sort By", + arrayOf( + Pair("Update", ""), + Pair("Popular", "Popular"), + Pair("Name Ascending", "Name_ASC"), + Pair("Name Descending", "Name_DESC"), + ), +) + +class FormatFilter : SelectFilter( + "Format", + arrayOf( + Pair("", ""), + Pair("Manga", "manga"), + Pair("Doujinshi", "doujinshi"), + Pair("ArtistCG", "artistcg"), + Pair("GameCG", "gamecg"), + Pair("ImageSet", "imageset"), + ), +) + +class MinPageFilter : PageFilter("Minimum Pages") + +class MaxPageFilter : PageFilter("Maximum Pages") + +class IncludedTagFilter : TagFilter("Include Tags") + +class ExcludedTagFilter : TagFilter("Exclude Tags") + +fun getFilters() = FilterList( + SortFilter(), + FormatFilter(), + Filter.Separator(), + MinPageFilter(), + MaxPageFilter(), + Filter.Separator(), + IncludedTagFilter(), + ExcludedTagFilter(), + Filter.Header("comma (,) separated tag/parody/character/artist/group"), +) diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiPayloadDto.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiPayloadDto.kt new file mode 100644 index 0000000000..e7ebc3b3ff --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiPayloadDto.kt @@ -0,0 +1,39 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import kotlinx.serialization.Serializable + +@Serializable +data class GraphQL<T>( + val variables: T, + val query: String, +) + +@Serializable +data class PopularVariables( + val size: Int, + val page: Int, + val dateRange: Int, + val language: String, +) + +@Serializable +data class SearchVariables( + val size: Int, + val page: Int, + val search: SearchPayload, +) + +@Serializable +data class SearchPayload( + val query: String?, + val language: String, + val sortBy: String?, + val format: String?, + val tags: List<String>?, + val excludeTags: List<String>?, + val pagesRangeStart: Int?, + val pagesRangeEnd: Int?, +) + +@Serializable +data class IdVariables(val id: String) diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiQueries.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiQueries.kt new file mode 100644 index 0000000000..644eedb15b --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiQueries.kt @@ -0,0 +1,106 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +private fun buildQuery(queryAction: () -> String): String { + return queryAction() + .trimIndent() + .replace("%", "$") +} + +val POPULAR_QUERY: String = buildQuery { + """ + query( + %size: Int + %language: String + %dateRange: Int + %page: Int + ) { + queryPopularChapters( + size: %size + language: %language + dateRange: %dateRange + page: %page + ) { + edges { + _id + name + uploadDate + format + description + language + pages + firstPics + tags + } + } + } + """ +} + +val SEARCH_QUERY: String = buildQuery { + """ + query( + %search: SearchInput + %size: Int + %page: Int + ) { + queryChapters( + limit: %size + search: %search + page: %page + ) { + edges { + _id + name + uploadDate + format + description + language + pages + firstPics + tags + } + } + } + """ +} + +val DETAILS_QUERY: String = buildQuery { + """ + query( + %id: String + ) { + queryChapter( + chapterId: %id + ) { + _id + name + uploadDate + format + description + language + pages + firstPics + tags + } + } + """ +} + +val PAGES_QUERY: String = buildQuery { + """ + query( + %id: String + ) { + queryChapter( + chapterId: %id + ) { + _id + pictureUrls { + picCdn + pics + picsM + } + } + } + """ +} diff --git a/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiUrlActivity.kt b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiUrlActivity.kt new file mode 100644 index 0000000000..d598b5fba9 --- /dev/null +++ b/src/all/ninenineninehentai/src/eu/kanade/tachiyomi/extension/all/ninenineninehentai/NineNineNineHentaiUrlActivity.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.all.ninenineninehentai + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class NineNineNineHentaiUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val id = pathSegments[1] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${NineNineNineHentai.SEARCH_PREFIX}$id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("999HentaiUrlActivity", e.toString()) + } + } else { + Log.e("999HentaiUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +} diff --git a/src/all/noisemanga/AndroidManifest.xml b/src/all/noisemanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/noisemanga/AndroidManifest.xml +++ b/src/all/noisemanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/novelcool/AndroidManifest.xml b/src/all/novelcool/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/all/novelcool/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/all/novelcool/build.gradle b/src/all/novelcool/build.gradle new file mode 100644 index 0000000000..92f2e091eb --- /dev/null +++ b/src/all/novelcool/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'NovelCool' + pkgNameSuffix = 'all.novelcool' + extClass = '.NovelCoolFactory' + extVersionCode = 2 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/novelcool/res/mipmap-hdpi/ic_launcher.png b/src/all/novelcool/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..1630e02484 Binary files /dev/null and b/src/all/novelcool/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/novelcool/res/mipmap-mdpi/ic_launcher.png b/src/all/novelcool/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..579024f45c Binary files /dev/null and b/src/all/novelcool/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/novelcool/res/mipmap-xhdpi/ic_launcher.png b/src/all/novelcool/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b47d8315d8 Binary files /dev/null and b/src/all/novelcool/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/novelcool/res/mipmap-xxhdpi/ic_launcher.png b/src/all/novelcool/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f8b8e8b78a Binary files /dev/null and b/src/all/novelcool/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/novelcool/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/novelcool/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6b682ecee2 Binary files /dev/null and b/src/all/novelcool/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/novelcool/res/web_hi_res_512.png b/src/all/novelcool/res/web_hi_res_512.png new file mode 100644 index 0000000000..84e3ff7823 Binary files /dev/null and b/src/all/novelcool/res/web_hi_res_512.png differ diff --git a/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCool.kt b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCool.kt new file mode 100644 index 0000000000..5ae0038864 --- /dev/null +++ b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCool.kt @@ -0,0 +1,455 @@ +package eu.kanade.tachiyomi.extension.all.novelcool + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale + +open class NovelCool( + final override val baseUrl: String, + final override val lang: String, + private val siteLang: String = lang, +) : ParsedHttpSource(), ConfigurableSource { + + override val name = "NovelCool" + + override val supportsLatest = true + + private val apiUrl = "https://api.novelcool.com" + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(1) + .build() + + private val pageClient by lazy { + client.newBuilder() + .addInterceptor(::jsRedirect) + .build() + } + + private val json: Json by injectLazy() + + private val preference by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + + override fun fetchPopularManga(page: Int): Observable<MangasPage> { + return when (preference.useAppApi) { + true -> client.newCall(commonApiRequest("$apiUrl/elite/hot/", page)) + .asObservableSuccess() + .map(::commonApiResponseParse) + else -> super.fetchPopularManga(page) + } + } + + override fun popularMangaRequest(page: Int): Request { + // popular on the site only have novels + return GET("$baseUrl/category/new_list.html", headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + runCatching { fetchGenres() } + + return super.popularMangaParse(response) + } + + override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() + + override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element) + + override fun popularMangaSelector() = searchMangaSelector() + + override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { + return when (preference.useAppApi) { + true -> client.newCall(commonApiRequest("$apiUrl/elite/latest/", page)) + .asObservableSuccess() + .map(::commonApiResponseParse) + else -> super.fetchLatestUpdates(page) + } + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/category/latest.html", headers) + } + + override fun latestUpdatesParse(response: Response): MangasPage { + runCatching { fetchGenres() } + + return super.latestUpdatesParse(response) + } + + override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) + + override fun latestUpdatesSelector() = searchMangaSelector() + + override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return when (preference.useAppApi) { + true -> client.newCall(commonApiRequest("$apiUrl/book/search/", page, query)) + .asObservableSuccess() + .map(::commonApiResponseParse) + else -> super.fetchSearchManga(page, query, filters) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/search".toHttpUrl().newBuilder().apply { + addQueryParameter("name", query.trim()) + + filters.forEach { filter -> + when (filter) { + is AuthorFilter -> { + addQueryParameter("author", filter.state.trim()) + } + is GenreFilter -> { + addQueryParameter("category_id", filter.included.joinToString(",", ",")) + addQueryParameter("out_category_id", filter.excluded.joinToString(",", ",")) + } + is StatusFilter -> { + addQueryParameter("completed_series", filter.getValue()) + } + is RatingFilter -> { + addQueryParameter("rate_star", filter.getValue()) + } + else -> { } + } + } + + addQueryParameter("page", page.toString()) + }.build() + + return GET(url, headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val document = Jsoup.parse(response.peekBody(Long.MAX_VALUE).string()) + runCatching { fetchGenres(document) } + + return super.searchMangaParse(response) + } + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + title = element.select(".book-pic").attr("title") + setUrlWithoutDomain(element.select("a").attr("href")) + thumbnail_url = element.select("img").imgAttr() + } + + override fun searchMangaSelector() = ".book-list .book-item:not(:has(.book-type-novel))" + + override fun searchMangaNextPageSelector() = "div.page-nav a div.next" + + private class AuthorFilter(title: String) : Filter.Text(title) + + private class GenreFilter(title: String, genres: List<Pair<String, String>>) : + Filter.Group<Genre>(title, genres.map { Genre(it.first, it.second) }) { + val included: List<String> + get() = state.filter { it.isIncluded() }.map { it.id } + + val excluded: List<String> + get() = state.filter { it.isExcluded() }.map { it.id } + } + class Genre(name: String, val id: String) : Filter.TriState(name) + + private fun getStatusList() = listOf( + Pair("All", ""), + Pair("Completed", "YES"), + Pair("Ongoing", "NO"), + ) + + private class StatusFilter(title: String, private val status: List<Pair<String, String>>) : + Filter.Select<String>(title, status.map { it.first }.toTypedArray()) { + fun getValue() = status[state].second + } + + private fun getRatingList() = listOf( + Pair("All", ""), + Pair("5 Star", "5"), + Pair("4 Star", "4"), + Pair("3 Star", "3"), + Pair("2 Star", "2"), + ) + private class RatingFilter(title: String, private val ratings: List<Pair<String, String>>) : + Filter.Select<String>(title, ratings.map { it.first }.toTypedArray()) { + fun getValue() = ratings[state].second + } + + override fun getFilterList(): FilterList { + if (preference.useAppApi) { + return FilterList(Filter.Header("Not supported when using App API")) + } + + val filters: MutableList<Filter<*>> = mutableListOf( + AuthorFilter("Author"), + StatusFilter("Status", getStatusList()), + RatingFilter("Rating", getRatingList()), + ) + + filters += if (genresList.isNotEmpty()) { + listOf( + GenreFilter("Genres", genresList), + ) + } else { + listOf( + Filter.Separator(), + Filter.Header("Press 'Reset' to attempt to show the genres"), + ) + } + + return FilterList(filters) + } + + private var fetchGenresAttempts = 0 + private var fetchGenresFailed = false + private var genresList: List<Pair<String, String>> = emptyList() + + private fun fetchGenres(document: Document? = null) { + if (fetchGenresAttempts < 3 && (genresList.isEmpty() || fetchGenresFailed) && !preference.useAppApi) { + val genres = runCatching { + if (document == null) { + client.newCall(genresRequest()).execute() + .use { parseGenres(it.asJsoup()) } + } else { + parseGenres(document) + } + } + + fetchGenresFailed = genres.isFailure + genresList = genres.getOrNull().orEmpty() + fetchGenresAttempts++ + } + } + + private fun genresRequest(): Request { + return GET("$baseUrl/search/", headers) + } + + private fun parseGenres(document: Document): List<Pair<String, String>> { + return document.selectFirst(".category-list") + ?.select(".category-id-item") + .orEmpty() + .map { div -> + Pair( + div.attr("title"), + div.attr("cate_id"), + ) + } + } + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + title = document.selectFirst("h1.bookinfo-title")!!.text() + description = document.selectFirst("div.bk-summary-txt")?.text() + genre = document.select(".bookinfo-category-list a").joinToString { it.text() } + author = document.selectFirst(".bookinfo-author > a")?.attr("title") + thumbnail_url = document.selectFirst(".bookinfo-pic-img")?.attr("abs:src") + status = document.select(".bookinfo-category-list a").first()?.text().parseStatus() + } + + private fun String?.parseStatus(): Int { + this ?: return SManga.UNKNOWN + return when { + this.lowercase() in completedStatusList -> SManga.COMPLETED + this.lowercase() in ongoingStatusList -> SManga.ONGOING + else -> SManga.UNKNOWN + } + } + + override fun chapterListSelector() = ".chapter-item-list a" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.attr("title") + date_upload = element.select(".chapter-item-time").text().parseDate() + } + + private fun String.parseDate(): Long { + return runCatching { DATE_FORMATTER.parse(this)?.time } + .getOrNull() ?: 0L + } + + override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { + return pageClient.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .map(::pageListParse) + } + + override fun pageListRequest(chapter: SChapter): Request { + return super.pageListRequest(chapter).newBuilder() + .addHeader("Referer", baseUrl) + .build() + } + + override fun pageListParse(document: Document): List<Page> { + val script = document.select("script:containsData(all_imgs_url)").html() + + val images = imgRegex.find(script)?.groupValues?.get(1) + ?.let { json.decodeFromString<List<String>>("[$it]") } + ?: return singlePageParse(document) + + return images.mapIndexed { idx, img -> + Page(idx, "", img) + } + } + + private fun singlePageParse(document: Document): List<Page> { + return document.selectFirst(".mangaread-pagenav > .sl-page")?.select("option") + ?.mapIndexed { idx, page -> + Page(idx, page.attr("value")) + } ?: emptyList() + } + + override fun imageUrlParse(document: Document): String { + return document.select(".mangaread-manga-pic").attr("src") + } + + private fun Elements.imgAttr(): String { + return when { + hasAttr("lazy_url") -> attr("abs:lazy_url") + else -> attr("abs:src") + } + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + SwitchPreferenceCompat(screen.context).apply { + key = PREF_API_SEARCH + title = "Use App API for browse" + summary = "Results may be more reliable" + setDefaultValue(true) + }.also(screen::addPreference) + } + + private val SharedPreferences.useAppApi: Boolean + get() = getBoolean(PREF_API_SEARCH, true) + + private fun jsRedirect(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + + val document = Jsoup.parse(response.peekBody(Long.MAX_VALUE).string()) + val jsRedirect = document.selectFirst("script:containsData(window.location.href)")?.html() + ?.substringAfter("\"") + ?.substringBefore("\"") + ?: return response + + val requestUrl = response.request.url + + val url = "${requestUrl.scheme}://${requestUrl.host}$jsRedirect".toHttpUrlOrNull() + ?: return response + + response.close() + + val newHeaders = headersBuilder() + .add("Referer", requestUrl.toString()) + .build() + + return chain.proceed( + request.newBuilder() + .url(url) + .headers(newHeaders) + .build(), + ) + } + + private fun commonApiRequest(url: String, page: Int, query: String? = null): Request { + val payload = NovelCoolBrowsePayload( + appId = appId, + lang = siteLang, + query = query, + type = "manga", + page = page.toString(), + size = size.toString(), + secret = appSecret, + ) + + val body = json.encodeToString(payload) + .toRequestBody(JSON_MEDIA_TYPE) + + val apiHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() + + return POST(url, apiHeaders, body) + } + + private fun commonApiResponseParse(response: Response): MangasPage { + runCatching { fetchGenres() } + + val browse = json.decodeFromString<NovelCoolBrowseResponse>(response.body.string()) + + val hasNextPage = browse.list?.size == size + + return browse.list?.map { + SManga.create().apply { + setUrlWithoutDomain(it.url) + title = it.name + thumbnail_url = it.cover + } + }.let { MangasPage(it ?: emptyList(), hasNextPage) } + } + + companion object { + private const val appId = "202201290625004" + private const val appSecret = "c73a8590641781f203660afca1d37ada" + private const val size = 20 + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + private val DATE_FORMATTER by lazy { + SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH) + } + private val imgRegex = Regex("""all_imgs_url\s*:\s*\[\s*([^]]*)\s*,\s*]""") + + private const val PREF_API_SEARCH = "pref_use_search_api" + + // copied from Madara + private val completedStatusList: Array<String> = arrayOf( + "completed", + "completo", + "completado", + "concluído", + "concluido", + "finalizado", + "terminé", + "hoàn thành", + ) + + private val ongoingStatusList: Array<String> = arrayOf( + "ongoing", "Продолжается", "updating", "em lançamento", "em lançamento", "em andamento", + "em andamento", "en cours", "ativo", "lançando", "Đang Tiến Hành", "devam ediyor", + "devam ediyor", "in corso", "in arrivo", "en curso", "en curso", "emision", + "curso", "en marcha", "Publicandose", "en emision", + ) + } +} diff --git a/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolDto.kt b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolDto.kt new file mode 100644 index 0000000000..ab513d2e6d --- /dev/null +++ b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolDto.kt @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.extension.all.novelcool + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NovelCoolBrowsePayload( + val appId: String, + @SerialName("keyword") val query: String? = null, + val lang: String, + @SerialName("lc_type") val type: String, + val page: String, + @SerialName("page_size") val size: String, + val secret: String, +) + +@Serializable +data class NovelCoolBrowseResponse( + val list: List<Manga>? = emptyList(), +) + +@Serializable +data class Manga( + val url: String, + val name: String, + val cover: String, +) diff --git a/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolFactory.kt b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolFactory.kt new file mode 100644 index 0000000000..1417808cd5 --- /dev/null +++ b/src/all/novelcool/src/eu/kanade/tachiyomi/extension/all/novelcool/NovelCoolFactory.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.all.novelcool + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class NovelCoolFactory : SourceFactory { + override fun createSources(): List<Source> = listOf( + NovelCool("https://www.novelcool.com", "en"), + NovelCool("https://es.novelcool.com", "es"), + NovelCool("https://de.novelcool.com", "de"), + NovelCool("https://ru.novelcool.com", "ru"), + NovelCool("https://it.novelcool.com", "it"), + NovelCool("https://br.novelcool.com", "pt-BR", "br"), + NovelCool("https://fr.novelcool.com", "fr"), + ) +} diff --git a/src/all/peppercarrot/AndroidManifest.xml b/src/all/peppercarrot/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/peppercarrot/AndroidManifest.xml +++ b/src/all/peppercarrot/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/photos18/AndroidManifest.xml b/src/all/photos18/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/photos18/AndroidManifest.xml +++ b/src/all/photos18/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/pixiv/AndroidManifest.xml b/src/all/pixiv/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/pixiv/AndroidManifest.xml +++ b/src/all/pixiv/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/pixiv/build.gradle b/src/all/pixiv/build.gradle index 0215f94dbd..c458e47950 100644 --- a/src/all/pixiv/build.gradle +++ b/src/all/pixiv/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Pixiv' pkgNameSuffix = 'all.pixiv' extClass = '.PixivFactory' - extVersionCode = 3 + extVersionCode = 8 isNsfw = true } diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Filters.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Filters.kt deleted file mode 100644 index bea80d1ce4..0000000000 --- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Filters.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.pixiv -import eu.kanade.tachiyomi.source.model.Filter - -internal class FilterType : Filter.Select<String>("Type", values, 2) { - companion object { - val keys = arrayOf("all", "illust", "manga") - val values = arrayOf("All", "Illustrations", "Manga") - } - - val value: String get() = keys[state] -} - -internal class FilterRating : Filter.Select<String>("Rating", values, 0) { - companion object { - val keys = arrayOf("all", "safe", "r18") - val values = arrayOf("All", "All ages", "R-18") - } - - val value: String get() = keys[state] -} - -internal class FilterSearchMode : Filter.Select<String>("Mode", values, 1) { - companion object { - val keys = arrayOf("s_tag", "s_tag_full", "s_tc") - val values = arrayOf("Tags (partial)", "Tags (full)", "Title, description") - } - - val value: String get() = keys[state] -} - -internal class FilterOrder : Filter.Sort("Order", arrayOf("Date posted")) { - val value: String get() = if (state?.ascending == true) "date" else "date_d" -} - -internal class FilterDateBefore : Filter.Text("Posted before") { - val value: String? get() = state.ifEmpty { null } -} - -internal class FilterDateAfter : Filter.Text("Posted after") { - val value: String? get() = state.ifEmpty { null } -} diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt index 81cfa939d0..4e3ba16677 100644 --- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt +++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Pixiv.kt @@ -1,216 +1,401 @@ package eu.kanade.tachiyomi.extension.all.pixiv -import android.util.LruCache -import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response import org.jsoup.Jsoup import rx.Observable import uy.kohesive.injekt.injectLazy -import java.net.URLEncoder -import java.text.SimpleDateFormat -import java.util.Locale class Pixiv(override val lang: String) : HttpSource() { override val name = "Pixiv" override val baseUrl = "https://www.pixiv.net" override val supportsLatest = true - private val siteLang: String = if (lang == "all") "ja" else lang - private val illustCache by lazy { LruCache<String, PixivIllust>(50) } - private val json: Json by injectLazy() - private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH) } - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") - .add("Accept-Language", siteLang) - - private fun apiRequest(method: String, path: String, params: Map<String, String> = emptyMap()) = Request( - url = baseUrl.toHttpUrl().newBuilder() - .addEncodedPathSegments("ajax$path") - .addEncodedQueryParameter("lang", siteLang) - .apply { params.forEach { (k, v) -> addEncodedQueryParameter(k, v) } } - .build(), - - headers = headersBuilder().add("Accept", "application/json").build(), - method = method, - ) - - private inline fun <reified T> apiResponseParse(response: Response): T { - if (!response.isSuccessful) { - throw Exception(response.message) + + override fun headersBuilder(): Headers.Builder = + super.headersBuilder().add("Referer", "$baseUrl/") + + private open inner class HttpCall(href: String?) { + val url: HttpUrl.Builder = baseUrl.toHttpUrl() + .run { href?.let { newBuilder(it)!! } ?: newBuilder() } + + val request: Request.Builder = Request.Builder() + .headers(headersBuilder().build()) + + fun execute(): Response = + client.newCall(request.url(url.build()).build()).execute() + } + + private inner class ApiCall(href: String?) : HttpCall(href) { + init { + url.addEncodedQueryParameter("lang", lang) + request.addHeader("Accept", "application/json") } - return response.body.string() - .let { json.decodeFromString<PixivApiResponse<T>>(it) } - .apply { if (error) throw Exception(message ?: response.message) } - .let { it.body!! } + inline fun <reified T> executeApi(): T = + json.decodeFromString<PixivApiResponse<T>>(execute().body.string()).body!! } - private fun illustUrlToId(url: String): String = - url.substringAfterLast("/") + private var popularMangaNextPage = 1 + private lateinit var popularMangaIterator: Iterator<SManga> + + override fun fetchPopularManga(page: Int): Observable<MangasPage> { + if (page == 1) { + popularMangaIterator = sequence { + val call = ApiCall("/touch/ajax/ranking/illust?mode=daily&type=manga") + + for (p in countUp(start = 1)) { + call.url.setEncodedQueryParameter("page", p.toString()) - private fun urlEncode(string: String): String = - URLEncoder.encode(string, "UTF-8").replace("+", "%20") + val entries = call.executeApi<PixivRankings>().ranking!! + if (entries.isEmpty()) break - private fun parseTimestamp(string: String) = - runCatching { dateFormat.parse(string)?.time!! }.getOrDefault(0) + val call = ApiCall("/touch/ajax/illust/details/many") + entries.forEach { call.url.addEncodedQueryParameter("illust_ids[]", it.illustId!!) } + + call.executeApi<PixivIllustsDetails>().illust_details!!.forEach { yield(it) } + } + } + .toSManga() + .iterator() - private fun parseSearchResult(result: PixivSearchResult) = SManga.create().apply { - url = "/artworks/${result.id!!}" - title = result.title ?: "" - thumbnail_url = result.url + popularMangaNextPage = 2 + } else { + require(page == popularMangaNextPage++) + } + + val mangas = popularMangaIterator.truncateToList(50) + return Observable.just(MangasPage(mangas, hasNextPage = mangas.isNotEmpty())) } - private fun fetchIllust(url: String): Observable<PixivIllust> = - Observable.fromCallable { illustCache.get(url) }.filter { it != null }.switchIfEmpty( - Observable.defer { - client.newCall(illustRequest(url)).asObservable() - .map { illustParse(it) } - .doOnNext { illustCache.put(url, it) } - }, - ) + private var searchNextPage = 1 + private var searchHash: Int? = null + private lateinit var searchIterator: Iterator<SManga> + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + val filters = filters.list as PixivFilters + val hash = Pair(query, filters).hashCode() + + if (hash != searchHash || page == 1) { + searchHash = hash + + lateinit var searchSequence: Sequence<PixivIllust> + lateinit var predicates: List<(PixivIllust) -> Boolean> + + if (query.isNotBlank()) { + searchSequence = makeIllustSearchSequence( + word = query, + order = filters.order, + mode = filters.rating, + sMode = "s_tc", + type = filters.type, + dateBefore = filters.dateBefore.ifBlank { null }, + dateAfter = filters.dateAfter.ifBlank { null }, + ) + + predicates = buildList { + filters.makeTagsPredicate()?.let(::add) + filters.makeUsersPredicate()?.let(::add) + } + } else if (filters.users.isNotBlank()) { + searchSequence = makeUserIllustSearchSequence( + nick = filters.users, + type = filters.type, + ) + + predicates = buildList { + filters.makeTagsPredicate()?.let(::add) + filters.makeRatingPredicate()?.let(::add) + } + } else { + searchSequence = makeIllustSearchSequence( + word = filters.tags.ifBlank { "漫画" }, + order = filters.order, + mode = filters.rating, + sMode = filters.searchMode, + type = filters.type, + dateBefore = filters.dateBefore.ifBlank { null }, + dateAfter = filters.dateAfter.ifBlank { null }, + ) + + predicates = emptyList() + } + + if (predicates.isNotEmpty()) { + searchSequence = searchSequence.filter { predicates.all { p -> p(it) } } + } - private fun illustRequest(url: String): Request = - apiRequest("GET", "/illust/${illustUrlToId(url)}") + searchIterator = searchSequence.toSManga().iterator() + searchNextPage = 2 + } else { + require(page == searchNextPage++) + } - private fun illustParse(response: Response): PixivIllust = - apiResponseParse(response) + val mangas = searchIterator.truncateToList(50).toList() + return Observable.just(MangasPage(mangas, hasNextPage = mangas.isNotEmpty())) + } - override fun popularMangaRequest(page: Int): Request = - searchMangaRequest(page, "", FilterList()) - - override fun popularMangaParse(response: Response) = MangasPage( - mangas = apiResponseParse<PixivSearchResults>(response) - .popular?.run { recent.orEmpty() + permanent.orEmpty() } - ?.map(::parseSearchResult) - .orEmpty(), - - hasNextPage = false, - ) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val word = urlEncode(query.ifBlank { "漫画" }) - - val parameters = mutableMapOf( - "word" to query, - "order" to "date_d", - "mode" to "all", - "p" to page.toString(), - "s_mode" to "s_tag_full", - "type" to "manga", - ) - - filters.forEach { filter -> - when (filter) { - is FilterType -> parameters["type"] = filter.value - is FilterRating -> parameters["mode"] = filter.value - is FilterSearchMode -> parameters["s_mode"] = filter.value - is FilterOrder -> parameters["order"] = filter.value - is FilterDateBefore -> filter.value?.let { parameters["ecd"] = it } - is FilterDateAfter -> filter.value?.let { parameters["scd"] = it } - else -> {} + private fun makeIllustSearchSequence( + word: String, + sMode: String, + order: String?, + mode: String?, + type: String?, + dateBefore: String?, + dateAfter: String?, + ) = sequence<PixivIllust> { + val call = ApiCall("/touch/ajax/search/illusts") + + call.url.addQueryParameter("word", word) + call.url.addEncodedQueryParameter("s_mode", sMode) + type?.let { call.url.addEncodedQueryParameter("type", it) } + order?.let { call.url.addEncodedQueryParameter("order", it) } + mode?.let { call.url.addEncodedQueryParameter("mode", it) } + dateBefore?.let { call.url.addEncodedQueryParameter("ecd", it) } + dateAfter?.let { call.url.addEncodedQueryParameter("scd", it) } + + for (p in countUp(start = 1)) { + call.url.setEncodedQueryParameter("p", p.toString()) + + val illusts = call.executeApi<PixivResults>().illusts!! + if (illusts.isEmpty()) break + + for (illust in illusts) { + if (illust.is_ad_container == 1) continue + if (illust.type == "2") continue + + yield(illust) } } + } + + private fun makeUserIllustSearchSequence(nick: String, type: String?) = sequence<PixivIllust> { + val searchUsers = HttpCall("/search_user.php?s_mode=s_usr") + .apply { url.addQueryParameter("nick", nick) } - val endpoint = when (parameters["type"]) { - "all" -> "artworks" - "illust" -> "illustrations" - "manga" -> "manga" - else -> "" + val fetchUserIllusts = ApiCall("/touch/ajax/user/illusts") + .apply { type?.let { url.setEncodedQueryParameter("type", it) } } + + for (p in countUp(start = 1)) { + searchUsers.url.setEncodedQueryParameter("p", p.toString()) + + val userIds = Jsoup.parse(searchUsers.execute().body.string()) + .select(".user-recommendation-item > a").eachAttr("href") + .map { it.substringAfterLast('/') } + + if (userIds.isEmpty()) break + + for (userId in userIds) { + fetchUserIllusts.url.setEncodedQueryParameter("id", userId) + + for (p in countUp(start = 1)) { + fetchUserIllusts.url.setEncodedQueryParameter("p", p.toString()) + + val illusts = fetchUserIllusts.executeApi<PixivResults>().illusts!! + if (illusts.isEmpty()) break + + yieldAll(illusts) + } + } } + } - return apiRequest("GET", "/search/$endpoint/$word", parameters) + override fun getFilterList() = FilterList(PixivFilters()) + + private fun Sequence<PixivIllust>.toSManga() = sequence<SManga> { + val seriesIdsSeen = mutableSetOf<String>() + + forEach { illust -> + val series = illust.series + + if (series == null) { + val manga = SManga.create() + manga.setUrlWithoutDomain("/artworks/${illust.id!!}") + manga.title = illust.title ?: "(null)" + manga.thumbnail_url = illust.url + yield(manga) + } else if (seriesIdsSeen.add(series.id!!)) { + val manga = SManga.create() + manga.setUrlWithoutDomain("/user/${series.userId!!}/series/${series.id}") + manga.title = series.title ?: "(null)" + manga.thumbnail_url = series.coverImage ?: illust.url + yield(manga) + } + } } - override fun searchMangaParse(response: Response): MangasPage { - val mangas = apiResponseParse<PixivSearchResults>(response) - .run { illustManga ?: illust ?: manga }?.data - ?.filter { it.isAdContainer != true } - ?.map(::parseSearchResult) - .orEmpty() + private var latestMangaNextPage = 1 + private lateinit var latestMangaIterator: Iterator<SManga> - return MangasPage(mangas, hasNextPage = mangas.isNotEmpty()) + override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { + if (page == 1) { + latestMangaIterator = sequence { + val call = ApiCall("/touch/ajax/latest?type=manga") + + for (p in countUp(start = 1)) { + call.url.setEncodedQueryParameter("p", p.toString()) + + val illusts = call.executeApi<PixivResults>().illusts!! + if (illusts.isEmpty()) break + + for (illust in illusts) { + if (illust.is_ad_container == 1) continue + yield(illust) + } + } + } + .toSManga() + .iterator() + + latestMangaNextPage = 2 + } else { + require(page == latestMangaNextPage++) + } + + val mangas = latestMangaIterator.truncateToList(50).toList() + return Observable.just(MangasPage(mangas, hasNextPage = mangas.isNotEmpty())) } - override fun latestUpdatesRequest(page: Int): Request = - searchMangaRequest(page, "", FilterList()) + private val getIllustCached by lazy { + lruCached<String, PixivIllust>(25) { illustId -> + val call = ApiCall("/touch/ajax/illust/details?illust_id=$illustId") + return@lruCached call.executeApi<PixivIllustDetails>().illust_details!! + } + } - override fun latestUpdatesParse(response: Response): MangasPage = - searchMangaParse(response) - - override fun mangaDetailsRequest(manga: SManga): Request = - illustRequest(manga.url) - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val illust = illustParse(response) - - url = "/artworks/${illust.id!!}" - title = illust.title ?: "" - artist = illust.userName - author = illust.userName - description = illust.description?.let { Jsoup.parseBodyFragment(it).wholeText() } - genre = illust.tags?.tags?.mapNotNull { it.tag }?.joinToString() - thumbnail_url = illust.urls?.thumb - update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + private val getSeriesIllustsCached by lazy { + lruCached<String, List<PixivIllust>>(25) { seriesId -> + val call = ApiCall("/touch/ajax/illust/series_content/$seriesId") + var lastOrder = 0 + + return@lruCached buildList { + while (true) { + call.url.setEncodedQueryParameter("last_order", lastOrder.toString()) + + val illusts = call.executeApi<PixivSeriesContents>().series_contents!! + if (illusts.isEmpty()) break + + addAll(illusts) + lastOrder += illusts.size + } + } + } + } + + override fun fetchMangaDetails(manga: SManga): Observable<SManga> { + val (id, isSeries) = parseSMangaUrl(manga.url) + + if (isSeries) { + val series = ApiCall("/touch/ajax/illust/series/$id") + .executeApi<PixivSeriesDetails>().series!! + + val illusts = getSeriesIllustsCached(id) + + if (series.id != null && series.userId != null) { + manga.setUrlWithoutDomain("/user/${series.userId}/series/${series.id}") + } + + series.title?.let { manga.title = it } + series.caption?.let { manga.description = it } + + illusts.firstOrNull()?.author_details?.user_name?.let { + manga.artist = it + manga.author = it + } + + val tags = illusts.flatMap { it.tags ?: emptyList() }.toSet() + if (tags.isNotEmpty()) manga.genre = tags.joinToString() + + val coverImage = series.coverImage?.let { if (it.isString) it.content else null } + (coverImage ?: illusts.firstOrNull()?.url)?.let { manga.thumbnail_url = it } + } else { + val illust = getIllustCached(id) + + illust.id?.let { manga.setUrlWithoutDomain("/artworks/$it") } + illust.title?.let { manga.title = it } + + illust.author_details?.user_name?.let { + manga.artist = it + manga.author = it + } + + illust.comment?.let { manga.description = it } + illust.tags?.let { manga.genre = it.joinToString() } + illust.url?.let { manga.thumbnail_url = it } + } + + return Observable.just(manga) } - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = - fetchIllust(manga.url).map { illust -> - listOf( - SChapter.create().apply { - url = manga.url - name = "Oneshot" - date_upload = illust.uploadDate?.let(::parseTimestamp) ?: 0 - chapter_number = 0F - }, - ) + override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { + val (id, isSeries) = parseSMangaUrl(manga.url) + + val illusts = when (isSeries) { + true -> getSeriesIllustsCached(id) + false -> listOf(getIllustCached(id)) + } + + val chapters = illusts.mapIndexed { i, illust -> + SChapter.create().apply { + setUrlWithoutDomain("/artworks/${illust.id!!}") + name = illust.title ?: "(null)" + date_upload = (illust.upload_timestamp ?: 0) * 1000 + chapter_number = (illusts.size - i).toFloat() + } } - override fun chapterListRequest(manga: SManga): Request = - throw IllegalStateException("Not used") + return Observable.just(chapters) + } + + override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { + val illustId = chapter.url.substringAfterLast('/') + + val pages = ApiCall("/ajax/illust/$illustId/pages") + .executeApi<List<PixivIllustPage>>() + .mapIndexed { i, it -> Page(i, chapter.url, it.urls!!.original!!) } + + return Observable.just(pages) + } override fun chapterListParse(response: Response): List<SChapter> = - throw IllegalStateException("Not used") + throw UnsupportedOperationException("Not used.") - override fun pageListRequest(chapter: SChapter): Request = - apiRequest("GET", "/illust/${illustUrlToId(chapter.url)}/pages") + override fun imageUrlParse(response: Response): String = + throw UnsupportedOperationException("Not used.") + + override fun latestUpdatesParse(response: Response): MangasPage = + throw UnsupportedOperationException("Not used.") + + override fun latestUpdatesRequest(page: Int): Request = + throw UnsupportedOperationException("Not used.") + + override fun mangaDetailsParse(response: Response): SManga = + throw UnsupportedOperationException("Not used.") override fun pageListParse(response: Response): List<Page> = - apiResponseParse<List<PixivPage>>(response) - .mapIndexed { i, it -> Page(index = i, imageUrl = it.urls?.original) } + throw UnsupportedOperationException("Not used.") - override fun imageUrlRequest(page: Page): Request = - throw IllegalStateException("Not used") + override fun popularMangaParse(response: Response): MangasPage = + throw UnsupportedOperationException("Not used.") - override fun imageUrlParse(response: Response): String = - throw IllegalStateException("Not used") - - override fun getMangaUrl(manga: SManga): String = - baseUrl + manga.url - - override fun getChapterUrl(chapter: SChapter): String = - baseUrl + chapter.url - - override fun getFilterList() = FilterList( - listOf( - FilterType(), - FilterRating(), - FilterSearchMode(), - FilterOrder(), - FilterDateBefore(), - FilterDateAfter(), - ), - ) + override fun popularMangaRequest(page: Int): Request = + throw UnsupportedOperationException("Not used.") + + override fun searchMangaParse(response: Response): MangasPage = + throw UnsupportedOperationException("Not used.") + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + throw UnsupportedOperationException("Not used.") } diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFactory.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFactory.kt index 578b73272f..e4b10cec25 100644 --- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFactory.kt +++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFactory.kt @@ -5,5 +5,5 @@ import eu.kanade.tachiyomi.source.SourceFactory class PixivFactory : SourceFactory { override fun createSources(): List<Source> = - listOf("all", "ja", "en", "ko", "zh").map { lang -> Pixiv(lang) } + listOf("ja", "en", "ko", "zh").map { lang -> Pixiv(lang) } } diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt new file mode 100644 index 0000000000..0e14013f5b --- /dev/null +++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivFilters.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.extension.all.pixiv +import eu.kanade.tachiyomi.source.model.Filter + +private val TYPE_VALUES = arrayOf("All", "Illustrations", "Manga") +private val TYPE_PARAMS = arrayOf(null, "illust", "manga") + +private val TAGS_MODE_VALUES = arrayOf("Partial", "Full") +private val TAGS_MODE_PARAMS = arrayOf("s_tag", "s_tag_full") + +private val RATING_VALUES = arrayOf("All", "All ages", "R-18") +private val RATING_PARAMS = arrayOf(null, "all", "r18") + +private val RATING_PREDICATES: Array<((PixivIllust) -> Boolean)?> = + arrayOf(null, { it.x_restrict == "0" }, { it.x_restrict == "1" }) + +internal class PixivFilters : MutableList<Filter<*>> by mutableListOf() { + private val typeFilter = object : Filter.Select<String>("Type", TYPE_VALUES, 2) {}.also(::add) + private val tagsFilter = object : Filter.Text("Tags") {}.also(::add) + private val tagsModeFilter = object : Filter.Select<String>("Tags mode", TAGS_MODE_VALUES, 0) {}.also(::add) + private val usersFilter = object : Filter.Text("Users") {}.also(::add) + private val ratingFilter = object : Filter.Select<String>("Rating", RATING_VALUES, 0) {}.also(::add) + + init { add(Filter.Header("(the following are ignored when the users filter is in use)")) } + + private val orderFilter = object : Filter.Sort("Order", arrayOf("Date posted")) {}.also(::add) + private val dateBeforeFilter = object : Filter.Text("Posted before") {}.also(::add) + private val dateAfterFilter = object : Filter.Text("Posted after") {}.also(::add) + + val type: String? get() = TYPE_PARAMS[typeFilter.state] + + val tags: String by tagsFilter::state + val searchMode: String get() = TAGS_MODE_PARAMS[tagsModeFilter.state] + + fun makeTagsPredicate(): ((PixivIllust) -> Boolean)? { + val tags = tags.ifBlank { return null }.split(' ') + + if (tagsModeFilter.state == 0) { + val regex = Regex(tags.joinToString("|") { Regex.escape(it) }) + return { it.tags?.any(regex::containsMatchIn) == true } + } else { + return { it.tags?.containsAll(tags) == true } + } + } + + val users: String by usersFilter::state + + fun makeUsersPredicate(): ((PixivIllust) -> Boolean)? { + val users = users.ifBlank { return null } + val regex = Regex(users.split(' ').joinToString("|") { Regex.escape(it) }) + + return { it.author_details?.user_name?.contains(regex) == true } + } + + val rating: String? get() = RATING_PARAMS[ratingFilter.state] + fun makeRatingPredicate() = RATING_PREDICATES[ratingFilter.state] + + val order: String? get() = orderFilter.state?.ascending?.let { "date" } + + val dateBefore: String by dateBeforeFilter::state + val dateAfter: String by dateAfterFilter::state +} diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivTypes.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivTypes.kt new file mode 100644 index 0000000000..0c57be291e --- /dev/null +++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/PixivTypes.kt @@ -0,0 +1,90 @@ +package eu.kanade.tachiyomi.extension.all.pixiv +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonPrimitive + +@Serializable +internal data class PixivApiResponse<T>( + val body: T? = null, +) + +@Serializable +internal data class PixivResults( + val illusts: List<PixivIllust>? = null, +) + +@Serializable +internal data class PixivIllust( + val author_details: PixivAuthorDetails? = null, + val comment: String? = null, + val id: String? = null, + val is_ad_container: Int? = null, + val series: PixivSearchResultSeries? = null, + val tags: List<String>? = null, + val title: String? = null, + val type: String? = null, + val upload_timestamp: Long? = null, + val url: String? = null, + val x_restrict: String? = null, +) + +@Serializable +internal data class PixivSearchResultSeries( + val coverImage: String? = null, + val id: String? = null, + val title: String? = null, + val userId: String? = null, +) + +@Serializable +internal data class PixivIllustDetails( + val illust_details: PixivIllust? = null, +) + +@Serializable +internal data class PixivIllustsDetails( + val illust_details: List<PixivIllust>? = null, +) + +@Serializable +internal data class PixivIllustPage( + val urls: PixivIllustPageUrls? = null, +) + +@Serializable +internal data class PixivIllustPageUrls( + val original: String? = null, +) + +@Serializable +internal data class PixivAuthorDetails( + val user_name: String? = null, +) + +@Serializable +internal data class PixivSeriesDetails( + val series: PixivSeries?, +) + +@Serializable +internal data class PixivSeries( + val caption: String? = null, + val coverImage: JsonPrimitive? = null, + val id: String? = null, + val title: String? = null, + val userId: String? = null, +) + +@Serializable +internal data class PixivSeriesContents( + val series_contents: List<PixivIllust>? = null, +) + +@Serializable +internal data class PixivRankings( + val ranking: List<PixivRankingEntry>? = null, +) + +@Serializable +internal data class PixivRankingEntry( + val illustId: String? = null, +) diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Structures.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Structures.kt deleted file mode 100644 index f5dca8fe86..0000000000 --- a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Structures.kt +++ /dev/null @@ -1,69 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.pixiv - -import kotlinx.serialization.Serializable - -@Serializable -internal data class PixivApiResponse<T>( - val error: Boolean, - val body: T? = null, - val message: String? = null, -) - -@Serializable -internal data class PixivIllust( - val id: Int? = null, - val title: String? = null, - val userName: String? = null, - val description: String? = null, - val tags: PixivTags? = null, - val urls: PixivImageUrls? = null, - val uploadDate: String? = null, -) - -@Serializable -internal data class PixivSearchResult( - val id: Int? = null, - val title: String? = null, - val url: String? = null, - val isAdContainer: Boolean? = null, -) - -@Serializable -internal data class PixivTag( - val tag: String? = null, -) - -@Serializable -internal data class PixivTags( - val tags: List<PixivTag>? = null, -) - -@Serializable -internal data class PixivSearchResults( - val illustManga: PixivSearchResultsIllusts? = null, - val illust: PixivSearchResultsIllusts? = null, - val manga: PixivSearchResultsIllusts? = null, - val popular: PixivSearchResultsPopular? = null, -) - -@Serializable -internal data class PixivSearchResultsIllusts( - val data: List<PixivSearchResult>? = null, -) - -@Serializable -internal data class PixivSearchResultsPopular( - val permanent: List<PixivSearchResult>? = null, - val recent: List<PixivSearchResult>? = null, -) - -@Serializable -internal data class PixivPage( - val urls: PixivImageUrls? = null, -) - -@Serializable -internal data class PixivImageUrls( - val original: String? = null, - val thumb: String? = null, -) diff --git a/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Util.kt b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Util.kt new file mode 100644 index 0000000000..1ca14c1c0e --- /dev/null +++ b/src/all/pixiv/src/eu/kanade/tachiyomi/extension/all/pixiv/Util.kt @@ -0,0 +1,28 @@ +package eu.kanade.tachiyomi.extension.all.pixiv + +import android.util.LruCache + +internal fun countUp(start: Int = 0) = sequence<Int> { + yieldAll(start..Int.MAX_VALUE) + throw RuntimeException("Overflow") +} + +internal fun <T> Iterator<T>.truncateToList(count: Int): List<T> = buildList { + repeat(count) { + if (!hasNext()) return@buildList + add(next()) + } +} + +internal fun parseSMangaUrl(url: String): Pair<String, Boolean> { + val isSeries = url.getOrNull(1) != 'a' + return Pair(url.substringAfterLast('/'), isSeries) +} + +internal fun <K, V> lruCached(capacity: Int, compute: (K) -> V): (K) -> V { + val cache = object : LruCache<K, V>(capacity) { + override fun create(key: K): V = compute(key) + } + + return { key -> synchronized(cache) { cache.get(key) } } +} diff --git a/src/all/projectsuki/AndroidManifest.xml b/src/all/projectsuki/AndroidManifest.xml new file mode 100644 index 0000000000..867eb056fe --- /dev/null +++ b/src/all/projectsuki/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <application> + <activity + android:name=".all.projectsuki.ProjectSukiUrlActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="projectsuki.com" + android:pathPattern="/book/..*" + android:scheme="https" /> + </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="projectsuki.com" + android:pathPattern="/read/..*" + android:scheme="https" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/all/projectsuki/build.gradle b/src/all/projectsuki/build.gradle new file mode 100644 index 0000000000..5ea5dc348d --- /dev/null +++ b/src/all/projectsuki/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Project Suki' + pkgNameSuffix = 'all.projectsuki' + extClass = '.ProjectSuki' + extVersionCode = 1 +} + +dependencies { + implementation(project(":lib-randomua")) +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/projectsuki/res/mipmap-hdpi/ic_launcher.png b/src/all/projectsuki/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..57b7a52966 Binary files /dev/null and b/src/all/projectsuki/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/projectsuki/res/mipmap-mdpi/ic_launcher.png b/src/all/projectsuki/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..5b568e2ae8 Binary files /dev/null and b/src/all/projectsuki/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/projectsuki/res/mipmap-xhdpi/ic_launcher.png b/src/all/projectsuki/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..ac0b1b10d5 Binary files /dev/null and b/src/all/projectsuki/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/projectsuki/res/mipmap-xxhdpi/ic_launcher.png b/src/all/projectsuki/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bd8d04b107 Binary files /dev/null and b/src/all/projectsuki/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/projectsuki/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/projectsuki/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6aeb83f51a Binary files /dev/null and b/src/all/projectsuki/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/projectsuki/res/web_hi_res_512.png b/src/all/projectsuki/res/web_hi_res_512.png new file mode 100644 index 0000000000..83e3bb55a4 Binary files /dev/null and b/src/all/projectsuki/res/web_hi_res_512.png differ diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/NormalizedURL.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/NormalizedURL.kt new file mode 100644 index 0000000000..3f6840ddef --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/NormalizedURL.kt @@ -0,0 +1,54 @@ +package eu.kanade.tachiyomi.extension.all.projectsuki + +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.jsoup.nodes.Element + +typealias NormalizedURL = HttpUrl + +val NormalizedURL.rawAbsolute: String + get() = toString() + +private val psDomainURI = """https://projectsuki.com/""".toHttpUrl().toUri() + +val NormalizedURL.rawRelative: String? + get() { + val uri = toUri() + return psDomainURI + .relativize(uri) + .takeIf { it != uri } + ?.let { """/$it""" } + } + +private val protocolMatcher = """^https?://""".toRegex() +private val domainMatcher = """^https?://(?:[a-zA-Z\d\-]+\.)+[a-zA-Z\d\-]+""".toRegex() +fun String.toNormalURL(): NormalizedURL? { + if (contains(':') && !contains(protocolMatcher)) { + return null + } + + val toParse = StringBuilder() + + if (!contains(domainMatcher)) { + toParse.append("https://projectsuki.com") + if (!this.startsWith("/")) toParse.append('/') + } + + toParse.append(this) + + return toParse.toString().toHttpUrlOrNull() +} + +fun NormalizedURL.pathStartsWith(other: Iterable<String>): Boolean = pathSegments.zip(other).all { (l, r) -> l == r } + +fun NormalizedURL.isPSUrl() = host.endsWith("${PS.identifier}.com") + +fun NormalizedURL.isBookURL() = isPSUrl() && pathSegments.first() == "book" +fun NormalizedURL.isReadURL() = isPSUrl() && pathStartsWith(PS.chapterPath) +fun NormalizedURL.isImagesGalleryURL() = isPSUrl() && pathStartsWith(PS.pagePath) + +fun Element.attrNormalizedUrl(attrName: String): NormalizedURL? { + val attrValue = attr("abs:$attrName").takeIf { it.isNotBlank() } ?: return null + return attrValue.toNormalURL() +} diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PS.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PS.kt new file mode 100644 index 0000000000..68c4dd935c --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PS.kt @@ -0,0 +1,129 @@ +@file:Suppress("MayBeConstant", "unused") + +package eu.kanade.tachiyomi.extension.all.projectsuki + +import org.jsoup.nodes.Element +import java.util.Calendar +import java.util.Locale +import kotlin.concurrent.getOrSet + +@Suppress("MemberVisibilityCanBePrivate") +internal object PS { + const val identifier: String = "projectsuki" + const val identifierShort: String = "ps" + + val bookPath = listOf("book") + val pagePath = listOf("images", "gallery") + val chapterPath = listOf("read") + + const val SEARCH_INTENT_PREFIX: String = "$identifierShort:" + + const val PREFERENCE_WHITELIST_LANGUAGES = "$identifier-languages-whitelist" + const val PREFERENCE_WHITELIST_LANGUAGES_TITLE = "Whitelist the following languages:" + const val PREFERENCE_WHITELIST_LANGUAGES_SUMMARY = + "Will keep project chapters in the following languages." + + " Takes precedence over blacklisted languages." + + " It will match the string present in the \"Language\" column of the chapter." + + " Whitespaces will be trimmed." + + " Leave empty to allow all languages." + + " Separate each entry with a comma ','" + + const val PREFERENCE_BLACKLIST_LANGUAGES = "$identifier-languages-blacklist" + const val PREFERENCE_BLACKLIST_LANGUAGES_TITLE = "Blacklist the following languages:" + const val PREFERENCE_BLACKLIST_LANGUAGES_SUMMARY = + "Will hide project chapters in the following languages." + + " Works identically to whitelisting." +} + +fun Element.containsBookLinks(): Boolean = select("a").any { + it.attrNormalizedUrl("href")?.isBookURL() == true +} + +fun Element.containsReadLinks(): Boolean = select("a").any { + it.attrNormalizedUrl("href")?.isReadURL() == true +} + +fun Element.containsImageGalleryLinks(): Boolean = select("a").any { + it.attrNormalizedUrl("href")?.isImagesGalleryURL() == true +} + +fun Element.getAllUrlElements(selector: String, attrName: String, predicate: (NormalizedURL) -> Boolean): Map<Element, NormalizedURL> { + return select(selector) + .mapNotNull { element -> element.attrNormalizedUrl(attrName)?.let { element to it } } + .filter { (_, url) -> predicate(url) } + .toMap() +} + +fun Element.getAllBooks(): Map<String, PSBook> { + val bookUrls = getAllUrlElements("a", "href") { it.isBookURL() } + val byID: Map<String, Map<Element, NormalizedURL>> = bookUrls.groupBy { (_, url) -> url.pathSegments[1] /* /book/<bookid> */ } + + @Suppress("UNCHECKED_CAST") + return byID.mapValues { (bookid, elements) -> + val thumb: Element? = elements.entries.firstNotNullOfOrNull { (element, _) -> + element.select("img").firstOrNull() + } + val title = elements.entries.firstOrNull { (element, _) -> + element.select("img").isEmpty() && element.text().let { + it.isNotBlank() && it.lowercase(Locale.US) != "show more" + } + } + + if (thumb != null && title != null) { + PSBook(thumb, title.key, title.key.text(), bookid, title.value) + } else { + null + } + }.filterValues { it != null } as Map<String, PSBook> +} + +inline fun <SK, K, V> Map<K, V>.groupBy(keySelector: (Map.Entry<K, V>) -> SK): Map<SK, Map<K, V>> = buildMap<_, MutableMap<K, V>> { + this@groupBy.entries.forEach { entry -> + getOrPut(keySelector(entry)) { HashMap() }[entry.key] = entry.value + } +} + +private val absoluteDateFormat: ThreadLocal<java.text.SimpleDateFormat> = ThreadLocal() +fun String.parseDate(ifFailed: Long = 0L): Long { + return when { + endsWith("ago") -> { + // relative + val number = takeWhile { it.isDigit() }.toInt() + val cal = Calendar.getInstance() + + when { + contains("day") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) } + contains("hour") -> cal.apply { add(Calendar.HOUR, -number) } + contains("minute") -> cal.apply { add(Calendar.MINUTE, -number) } + contains("second") -> cal.apply { add(Calendar.SECOND, -number) } + contains("week") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) } + contains("month") -> cal.apply { add(Calendar.MONTH, -number) } + contains("year") -> cal.apply { add(Calendar.YEAR, -number) } + else -> null + }?.timeInMillis ?: ifFailed + } + + else -> { + // absolute? + absoluteDateFormat.getOrSet { java.text.SimpleDateFormat("MMMM dd, yyyy", Locale.US) }.parse(this)?.time ?: ifFailed + } + } +} + +private val imageExtensions = setOf(".jpg", ".png", ".jpeg", ".webp", ".gif", ".avif", ".tiff") +private val simpleSrcVariants = listOf("src", "data-src", "data-lazy-src") +fun Element.imgNormalizedURL(): NormalizedURL? { + simpleSrcVariants.forEach { variant -> + if (hasAttr(variant)) { + return attrNormalizedUrl(variant) + } + } + + if (hasAttr("srcset")) { + return attr("abs:srcset").substringBefore(" ").toNormalURL() + } + + return attributes().firstOrNull { + it.key.contains("src") && imageExtensions.any { ext -> it.value.contains(ext) } + }?.value?.substringBefore(" ")?.toNormalURL() +} diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSBook.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSBook.kt new file mode 100644 index 0000000000..87198dee1f --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSBook.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.all.projectsuki + +import org.jsoup.nodes.Element + +data class PSBook( + val imgElement: Element, + val titleElement: Element, + val title: String, + val mangaID: String, + val url: NormalizedURL, +) diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSFilters.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSFilters.kt new file mode 100644 index 0000000000..dfa2d16468 --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/PSFilters.kt @@ -0,0 +1,90 @@ +@file:Suppress("CanSealedSubClassBeObject") + +package eu.kanade.tachiyomi.extension.all.projectsuki + +import eu.kanade.tachiyomi.source.model.Filter +import okhttp3.HttpUrl + +@Suppress("NOTHING_TO_INLINE") +object PSFilters { + internal sealed interface AutoFilter { + fun applyTo(builder: HttpUrl.Builder) + } + + private inline fun HttpUrl.Builder.setAdv() = setQueryParameter("adv", "1") + + class Author : Filter.Text("Author"), AutoFilter { + + override fun applyTo(builder: HttpUrl.Builder) { + when { + state.isNotBlank() -> builder.setAdv().addQueryParameter("author", state) + } + } + + companion object { + val ownHeader by lazy { Header("Cannot search by multiple authors") } + } + } + + class Artist : Filter.Text("Artist"), AutoFilter { + + override fun applyTo(builder: HttpUrl.Builder) { + when { + state.isNotBlank() -> builder.setAdv().addQueryParameter("artist", state) + } + } + + companion object { + val ownHeader by lazy { Header("Cannot search by multiple artists") } + } + } + + class Status : Filter.Select<Status.Value>("Status", Value.values()), AutoFilter { + enum class Value(val display: String, val query: String) { + ANY("Any", ""), + ONGOING("Ongoing", "ongoing"), + COMPLETED("Completed", "completed"), + HIATUS("Hiatus", "hiatus"), + CANCELLED("Cancelled", "cancelled"), + ; + + override fun toString(): String = display + + companion object { + private val values: Array<Value> = values() + operator fun get(ordinal: Int) = values[ordinal] + } + } + + override fun applyTo(builder: HttpUrl.Builder) { + when (val state = Value[state]) { + Value.ANY -> {} // default, do nothing + else -> builder.setAdv().addQueryParameter("status", state.query) + } + } + } + + class Origin : Filter.Select<Origin.Value>("Origin", Value.values()), AutoFilter { + enum class Value(val display: String, val query: String?) { + ANY("Any", null), + KOREA("Korea", "kr"), + CHINA("China", "cn"), + JAPAN("Japan", "jp"), + ; + + override fun toString(): String = display + + companion object { + private val values: Array<Value> = Value.values() + operator fun get(ordinal: Int) = values[ordinal] + } + } + + override fun applyTo(builder: HttpUrl.Builder) { + when (val state = Value[state]) { + Value.ANY -> {} // default, do nothing + else -> builder.setAdv().addQueryParameter("origin", state.query) + } + } + } +} diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSuki.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSuki.kt new file mode 100644 index 0000000000..0dbf62fc6c --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSuki.kt @@ -0,0 +1,443 @@ +package eu.kanade.tachiyomi.extension.all.projectsuki + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.Locale + +@Suppress("unused") +class ProjectSuki : HttpSource(), ConfigurableSource { + override val name: String = "Project Suki" + override val baseUrl: String = "https://projectsuki.com" + override val lang: String = "en" + + private val preferences: SharedPreferences by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + + private fun String.processLangPref(): List<String> = split(",").map { it.trim().lowercase(Locale.US) } + + private val SharedPreferences.whitelistedLanguages: List<String> + get() = getString(PS.PREFERENCE_WHITELIST_LANGUAGES, "")!! + .processLangPref() + + private val SharedPreferences.blacklistedLanguages: List<String> + get() = getString(PS.PREFERENCE_BLACKLIST_LANGUAGES, "")!! + .processLangPref() + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + addRandomUAPreferenceToScreen(screen) + + screen.addPreference( + EditTextPreference(screen.context).apply { + key = PS.PREFERENCE_WHITELIST_LANGUAGES + title = PS.PREFERENCE_WHITELIST_LANGUAGES_TITLE + summary = PS.PREFERENCE_WHITELIST_LANGUAGES_SUMMARY + }, + ) + + screen.addPreference( + EditTextPreference(screen.context).apply { + key = PS.PREFERENCE_BLACKLIST_LANGUAGES + title = PS.PREFERENCE_BLACKLIST_LANGUAGES_TITLE + summary = PS.PREFERENCE_BLACKLIST_LANGUAGES_SUMMARY + }, + ) + } + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .setRandomUserAgent( + userAgentType = preferences.getPrefUAType(), + customUA = preferences.getPrefCustomUA(), + filterInclude = listOf("chrome"), + ) + .rateLimit(4) + .build() + + override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) + + // differentiating between popular and latest manga in the main page is + // *theoretically possible* but a pain, as such, this is fine "for now" + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val allBooks = document.getAllBooks() + return MangasPage( + mangas = allBooks.mapNotNull mangas@{ (_, psbook) -> + val (img, _, titleText, _, url) = psbook + + val relativeUrl = url.rawRelative ?: return@mangas null + + SManga.create().apply { + this.url = relativeUrl + this.title = titleText + this.thumbnail_url = img.imgNormalizedURL()?.rawAbsolute + } + }, + hasNextPage = false, + ) + } + + override val supportsLatest: Boolean = false + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException() + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException() + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return when { + /*query.startsWith(PS.SEARCH_INTENT_PREFIX) -> { + val id = query.substringAfter(PS.SEARCH_INTENT_PREFIX) + client.newCall(getMangaByIdAsSearchResult(id)) + .asObservableSuccess() + .map { response -> searchMangaParse(response) } + }*/ + + else -> Observable.defer { + try { + client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + } catch (e: NoClassDefFoundError) { + throw RuntimeException(e) + } + }.map { response -> searchMangaParse(response) } + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET( + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("search") + addQueryParameter("page", (page - 1).toString()) + addQueryParameter("q", query) + + filters.applyFilter<PSFilters.Origin>(this) + filters.applyFilter<PSFilters.Status>(this) + filters.applyFilter<PSFilters.Author>(this) + filters.applyFilter<PSFilters.Artist>(this) + }.build(), + headers, + ) + } + + private inline fun <reified T> FilterList.applyFilter(to: HttpUrl.Builder) where T : Filter<*>, T : PSFilters.AutoFilter { + firstNotNullOfOrNull { it as? T }?.applyTo(to) + } + + override fun getFilterList() = FilterList( + Filter.Header("Filters only take effect when searching for something!"), + PSFilters.Origin(), + PSFilters.Status(), + PSFilters.Author.ownHeader, + PSFilters.Author(), + PSFilters.Artist.ownHeader, + PSFilters.Artist(), + ) + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val allBooks = document.getAllBooks() + + val mangas = allBooks.mapNotNull mangas@{ (_, psbook) -> + val (img, _, titleText, _, url) = psbook + + val relativeUrl = url.rawRelative ?: return@mangas null + + SManga.create().apply { + this.url = relativeUrl + this.title = titleText + this.thumbnail_url = img.imgNormalizedURL()?.rawAbsolute + } + } + + return MangasPage( + mangas = mangas, + hasNextPage = mangas.size >= 30, // observed max number of results in search + ) + } + + override fun fetchMangaDetails(manga: SManga): Observable<SManga> { + return client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response, incomplete = manga).apply { initialized = true } + } + } + + private val displayNoneMatcher = """display: ?none;""".toRegex() + private val emptyImageURLAbsolute = """https://projectsuki.com/images/gallery/empty.jpg""".toNormalURL()!!.rawAbsolute + private val emptyImageURLRelative = """https://projectsuki.com/images/gallery/empty.jpg""".toNormalURL()!!.rawRelative!! + override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("not used") + private fun mangaDetailsParse(response: Response, incomplete: SManga): SManga { + val document = response.asJsoup() + val allLinks = document.getAllUrlElements("a", "href") { it.isPSUrl() } + + val thumb: Element? = document.select("img").firstOrNull { img -> + img.attr("onerror").let { + it.contains(emptyImageURLAbsolute) || + it.contains(emptyImageURLRelative) + } + } + + val authors: Map<Element, NormalizedURL> = allLinks.filter { (_, url) -> + url.queryParameterNames.contains("author") + } + + val artists: Map<Element, NormalizedURL> = allLinks.filter { (_, url) -> + url.queryParameterNames.contains("artist") + } + + val statuses: Map<Element, NormalizedURL> = allLinks.filter { (_, url) -> + url.queryParameterNames.contains("status") + } + + val origins: Map<Element, NormalizedURL> = allLinks.filter { (_, url) -> + url.queryParameterNames.contains("origin") + } + + val genres: Map<Element, NormalizedURL> = allLinks.filter { (_, url) -> + url.pathStartsWith(listOf("genre")) + } + + val description = document.select("#descriptionCollapse").joinToString("\n-----\n", postfix = "\n") { it.wholeText() } + + val alerts = document.select(".alert, .alert-info") + .filter( + predicate = { + it.parents().none { parent -> + parent.attr("style") + .contains(displayNoneMatcher) + } + }, + ) + + val userRating = document.select("#ratings") + .firstOrNull() + ?.children() + ?.count { it.hasClass("text-warning") } + ?.takeIf { it > 0 } + + return SManga.create().apply { + url = incomplete.url + title = incomplete.title + thumbnail_url = thumb?.imgNormalizedURL()?.rawAbsolute ?: incomplete.thumbnail_url + + author = authors.keys.joinToString(", ") { it.text() } + artist = artists.keys.joinToString(", ") { it.text() } + status = when (statuses.keys.joinToString("") { it.text().trim() }.lowercase(Locale.US)) { + "ongoing" -> SManga.ONGOING + "completed" -> SManga.PUBLISHING_FINISHED + "hiatus" -> SManga.ON_HIATUS + "cancelled" -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + + this.description = buildString { + if (alerts.isNotEmpty()) { + appendLine("Alerts have been found, refreshing the manga later might help in removing them.") + appendLine() + + alerts.forEach { alert -> + var appendedSomething = false + alert.select("h4").singleOrNull()?.let { + appendLine(it.text()) + appendedSomething = true + } + alert.select("p").singleOrNull()?.let { + appendLine(it.text()) + appendedSomething = true + } + if (!appendedSomething) { + appendLine(alert.text()) + } + } + + appendLine() + appendLine() + } + + appendLine(description) + + fun appendToDescription(by: String, data: String?) { + if (data != null) append(by).appendLine(data) + } + + appendToDescription("User Rating: ", """${userRating ?: "?"}/5""") + appendToDescription("Authors: ", author) + appendToDescription("Artists: ", artist) + appendToDescription("Status: ", statuses.keys.joinToString(", ") { it.text() }) + appendToDescription("Origin: ", origins.keys.joinToString(", ") { it.text() }) + appendToDescription("Genres: ", genres.keys.joinToString(", ") { it.text() }) + } + + this.update_strategy = if (status != SManga.CANCELLED) UpdateStrategy.ALWAYS_UPDATE else UpdateStrategy.ONLY_FETCH_ONCE + this.genre = buildList { + addAll(genres.keys.map { it.text() }) + origins.values.forEach { url -> + when (url.queryParameter("origin")) { + "kr" -> add("Manhwa") + "cn" -> add("Manhua") + "jp" -> add("Manga") + } + } + }.joinToString(", ") + } + } + + private val chapterHeaderMatcher = """chapters?""".toRegex() + private val groupHeaderMatcher = """groups?""".toRegex() + private val dateHeaderMatcher = """added|date""".toRegex() + private val languageHeaderMatcher = """language""".toRegex() + private val chapterNumberMatcher = """[Cc][Hh][Aa][Pp][Tt][Ee][Rr]\s*(\d+)(?:\s*[.,-]\s*(\d+))?""".toRegex() + private val looseNumberMatcher = """(\d+)(?:\s*[.,-]\s*(\d+))?""".toRegex() + override fun chapterListParse(response: Response): List<SChapter> { + val document = response.asJsoup() + val chaptersTable = document.select("table").firstOrNull { it.containsReadLinks() } ?: return emptyList() + + val thead: Element = chaptersTable.select("thead").firstOrNull() ?: return emptyList() + val tbody: Element = chaptersTable.select("tbody").firstOrNull() ?: return emptyList() + + val columnTypes = thead.select("tr").firstOrNull()?.children()?.select("td") ?: return emptyList() + val textTypes = columnTypes.map { it.text().lowercase(Locale.US) } + val normalSize = textTypes.size + + val chaptersIndex: Int = textTypes.indexOfFirst { it.matches(chapterHeaderMatcher) }.takeIf { it >= 0 } ?: return emptyList() + val dateIndex: Int = textTypes.indexOfFirst { it.matches(dateHeaderMatcher) }.takeIf { it >= 0 } ?: return emptyList() + val groupIndex: Int? = textTypes.indexOfFirst { it.matches(groupHeaderMatcher) }.takeIf { it >= 0 } + val languageIndex: Int? = textTypes.indexOfFirst { it.matches(languageHeaderMatcher) }.takeIf { it >= 0 } + + val dataRows = tbody.children().select("tr") + + val blLangs = preferences.blacklistedLanguages + val wlLangs = preferences.whitelistedLanguages + + return dataRows.mapNotNull chapters@{ tr -> + val rowData = tr.children().select("td") + + if (rowData.size != normalSize) { + return@chapters null + } + + val chapter: Element = rowData[chaptersIndex] + val date: Element = rowData[dateIndex] + val group: Element? = groupIndex?.let(rowData::get) + val language: Element? = languageIndex?.let(rowData::get) + + language?.text()?.lowercase(Locale.US)?.let { lang -> + if (lang in blLangs && lang !in wlLangs) return@chapters null + } + + val chapterLink = chapter.select("a").first()!!.attrNormalizedUrl("href")!! + + val relativeURL = chapterLink.rawRelative ?: return@chapters null + + SChapter.create().apply { + chapter_number = chapter.text() + .let { (chapterNumberMatcher.find(it) ?: looseNumberMatcher.find(it)) } + ?.let { result -> + val integral = result.groupValues[1] + val fractional = result.groupValues.getOrNull(2) + + """${integral}$fractional""".toFloat() + } ?: -1f + + url = relativeURL + scanlator = group?.text() ?: "<UNKNOWN>" + name = chapter.text() + date_upload = date.text().parseDate() + } + }.toList() + } + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used") + + private val callpageUrl = """https://projectsuki.com/callpage""" + private val jsonMediaType = "application/json;charset=UTF-8".toMediaType() + override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { + // chapter.url is /read/<bookid>/<chapterid>/... + val url = chapter.url.toNormalURL() ?: return Observable.just(emptyList()) + + val bookid = url.pathSegments[1] // <bookid> + val chapterid = url.pathSegments[2] // <chapterid> + + val callpageHeaders = headersBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .add("Content-Type", "application/json;charset=UTF-8") + .build() + + val callpageBody = Json.encodeToString( + mapOf( + "bookid" to bookid, + "chapterid" to chapterid, + "first" to "true", + ), + ).toRequestBody(jsonMediaType) + + return client.newCall( + POST(callpageUrl, callpageHeaders, callpageBody), + ).asObservableSuccess() + .map { response -> + callpageParse(chapter, response) + } + } + + @Suppress("UNUSED_PARAMETER") + private fun callpageParse(chapter: SChapter, response: Response): List<Page> { + // response contains the html src with images + val src = Json.parseToJsonElement(response.body.string()).jsonObject["src"]?.jsonPrimitive?.content ?: return emptyList() + val images = Jsoup.parseBodyFragment(src).select("img") + // images urls are /images/gallery/<bookid>/<uuid>/<pagenum>? (empty query for some reason) + val urls = images.mapNotNull { it.attrNormalizedUrl("src") } + if (urls.isEmpty()) return emptyList() + + val anUrl = urls.random() + val pageNums = urls.mapTo(ArrayList()) { it.pathSegments[4] } + pageNums += "001" + + fun makeURL(pageNum: String) = anUrl.newBuilder() + .setPathSegment(anUrl.pathSegments.lastIndex, pageNum) + .build() + + return pageNums.distinct().sortedBy { it.toInt() }.mapIndexed { index, number -> + Page( + index, + "", + makeURL(number).rawAbsolute, + ) + }.distinctBy { it.imageUrl } + } + + override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("not used") +} diff --git a/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSukiUrlActivity.kt b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSukiUrlActivity.kt new file mode 100644 index 0000000000..a72a020b1e --- /dev/null +++ b/src/all/projectsuki/src/eu/kanade/tachiyomi/extension/all/projectsuki/ProjectSukiUrlActivity.kt @@ -0,0 +1,33 @@ +package eu.kanade.tachiyomi.extension.all.projectsuki + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class ProjectSukiUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${PS.SEARCH_INTENT_PREFIX}${pathSegments[1]}") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("PSUrlActivity", e.toString()) + } + } else { + Log.e("PSUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +} diff --git a/src/all/sandraandwoo/AndroidManifest.xml b/src/all/sandraandwoo/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/sandraandwoo/AndroidManifest.xml +++ b/src/all/sandraandwoo/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/simplycosplay/AndroidManifest.xml b/src/all/simplycosplay/AndroidManifest.xml new file mode 100644 index 0000000000..15f26f0fef --- /dev/null +++ b/src/all/simplycosplay/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <application> + <activity + android:name=".all.simplycosplay.SimplyCosplayUrlActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:host="www.simply-cosplay.com" /> + <data android:host="simply-cosplay.com" /> + <data android:scheme="https" /> + <data android:pathPattern="/gallery/..*" /> + <data android:pathPattern="/image/..*" /> + + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/all/simplycosplay/build.gradle b/src/all/simplycosplay/build.gradle new file mode 100644 index 0000000000..c99a818f1f --- /dev/null +++ b/src/all/simplycosplay/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Simply Cosplay' + pkgNameSuffix = 'all.simplycosplay' + extClass = '.SimplyCosplay' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/all/simplycosplay/res/mipmap-hdpi/ic_launcher.png b/src/all/simplycosplay/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..fa27adc26b Binary files /dev/null and b/src/all/simplycosplay/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/simplycosplay/res/mipmap-mdpi/ic_launcher.png b/src/all/simplycosplay/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..3935d64588 Binary files /dev/null and b/src/all/simplycosplay/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/simplycosplay/res/mipmap-xhdpi/ic_launcher.png b/src/all/simplycosplay/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..23f5ee5a88 Binary files /dev/null and b/src/all/simplycosplay/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/simplycosplay/res/mipmap-xxhdpi/ic_launcher.png b/src/all/simplycosplay/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..88bbe4b20d Binary files /dev/null and b/src/all/simplycosplay/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/simplycosplay/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/simplycosplay/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..bc9f7fdffa Binary files /dev/null and b/src/all/simplycosplay/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/simplycosplay/res/web_hi_res_512.png b/src/all/simplycosplay/res/web_hi_res_512.png new file mode 100644 index 0000000000..2614f6dcd9 Binary files /dev/null and b/src/all/simplycosplay/res/web_hi_res_512.png differ diff --git a/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplay.kt b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplay.kt new file mode 100644 index 0000000000..9736f28614 --- /dev/null +++ b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplay.kt @@ -0,0 +1,382 @@ +package eu.kanade.tachiyomi.extension.all.simplycosplay + +import android.app.Application +import android.content.SharedPreferences +import android.util.Log +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Locale + +class SimplyCosplay : HttpSource(), ConfigurableSource { + + override val name = "Simply Cosplay" + + override val lang = "all" + + override val baseUrl = "https://www.simply-cosplay.com" + + private val apiUrl = "https://api.simply-porn.com/v2".toHttpUrl() + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder() + .addInterceptor(::tokenIntercept) + .rateLimit(2) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", baseUrl) + + private val json: Json by injectLazy() + + private val preference by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + + private fun tokenIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (request.url.host != apiUrl.host) { + return chain.proceed(request) + } + + val url = request.url.newBuilder() + .setQueryParameter("token", preference.getToken()) + .build() + + val response = chain.proceed( + request.newBuilder() + .url(url) + .build(), + ) + + if (response.isSuccessful.not() && response.code == 403) { + response.close() + + val newToken = fetchNewToken() + + preference.putToken(newToken) + + val newUrl = request.url.newBuilder() + .setQueryParameter("token", newToken) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build(), + ) + } + + return response + } + + private fun fetchNewToken(): String { + val document = client.newCall(GET(baseUrl, headers)).execute().asJsoup() + + val scriptUrl = document.selectFirst("script[src*=main]") + ?.attr("abs:src") + ?: throw IOException(TOKEN_EXCEPTION) + + val scriptContent = client.newCall(GET(scriptUrl, headers)).execute() + .use { it.body.string() } + .replace("\'", "\"") + + return TokenRegex.find(scriptContent)?.groupValues?.get(1) + ?: throw IOException(TOKEN_EXCEPTION) + } + + private fun browseUrlBuilder(endPoint: String, sort: String, page: Int): HttpUrl.Builder { + return apiUrl.newBuilder().apply { + addPathSegment(endPoint) + addQueryParameter("sort", sort) + addQueryParameter("limit", limit.toString()) + addQueryParameter("page", page.toString()) + } + } + + override fun popularMangaRequest(page: Int): Request { + val url = browseUrlBuilder(preference.getDefaultBrowse(), "hot", page) + + return GET(url.build(), headers) + } + + override fun popularMangaParse(response: Response): MangasPage { + runCatching { fetchTags() } + + val result = response.parseAs<browseResponse>() + + val entries = result.data.map(BrowseItem::toSManga) + val hasNextPage = result.data.size >= limit + + return MangasPage(entries, hasNextPage) + } + + override fun latestUpdatesRequest(page: Int): Request { + val url = browseUrlBuilder(preference.getDefaultBrowse(), "new", page) + + return GET(url.build(), headers) + } + + override fun latestUpdatesParse(response: Response) = popularMangaParse(response) + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return if (query.startsWith(SEARCH_PREFIX)) { + val url = query.substringAfter(SEARCH_PREFIX) + val manga = SManga.create().apply { this.url = url } + fetchMangaDetails(manga).map { + MangasPage(listOf(it), false) + } + } else { + super.fetchSearchManga(page, query, filters) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val sort = filters.filterIsInstance<SortFilter>().firstOrNull()?.getSort() ?: "new" + + val url = browseUrlBuilder("search", sort, page).apply { + if (query.isNotEmpty()) { + addQueryParameter("query", query) + } + filters.map { filter -> + when (filter) { + is TagFilter -> { + filter.getSelected().forEachIndexed { index, tag -> + addQueryParameter( + "filter[tag_names][$index]", + tag.name.replace(" ", "+"), + ) + } + } + is TypeFilter -> { + filter.getValue().let { + if (it.isNotEmpty()) { + addQueryParameter("filter[type][0]", it) + } + } + } + else -> { } + } + } + } + + return GET(url.build(), headers) + } + + override fun searchMangaParse(response: Response) = popularMangaParse(response) + + private var tagList: List<String> = emptyList() + private var tagsFetchAttempt = 0 + private var tagsFetchFailed = false + + private fun fetchTags() { + if (tagsFetchAttempt < 3 && (tagList.isEmpty() || tagsFetchFailed)) { + val tags = runCatching { + client.newCall(tagsRequest()) + .execute().use(::tagsParse) + } + + tagsFetchFailed = tags.isFailure + tagList = tags.getOrElse { + Log.e("SimplyHentaiTags", it.stackTraceToString()) + emptyList() + } + tagsFetchAttempt++ + } + } + + private fun tagsRequest(): Request { + val url = apiUrl.newBuilder() + .addPathSegment("search") + .build() + + return GET(url, headers) + } + + private fun tagsParse(response: Response): List<String> { + val result = response.parseAs<TagsResponse>() + + return result.aggs.tag_names.buckets.map { + it.key.trim() + } + } + + class Tag(name: String) : Filter.CheckBox(name) + + class TagFilter(title: String, tags: List<String>) : + Filter.Group<Tag>(title, tags.map(::Tag)) { + + fun getSelected() = state.filter { it.state } + } + + class TypeFilter(title: String, private val types: List<String>) : + Filter.Select<String>(title, types.toTypedArray()) { + + fun getValue() = types[state].lowercase() + } + + class SortFilter(title: String, private val sorts: List<String>) : + Filter.Select<String>(title, sorts.toTypedArray()) { + + fun getSort() = sorts[state].lowercase() + } + + override fun getFilterList(): FilterList { + val filters: MutableList<Filter<*>> = mutableListOf( + SortFilter("Sort", listOf("New", "Hot")), + TypeFilter("Type", listOf("", "Image", "Gallery")), + ) + + if (tagList.isNotEmpty()) { + filters += TagFilter("Tags", tagList) + } else { + filters += listOf( + Filter.Separator(), + Filter.Header("Press 'Reset' to attempt to show tags"), + ) + } + + return FilterList(filters) + } + + private fun mangaUrlBuilder(dbUrl: String): HttpUrl.Builder { + val pathSegments = dbUrl.split("/") + val type = pathSegments[1] + val slug = pathSegments[3] + + return apiUrl.newBuilder().apply { + addPathSegment(type) + addPathSegments(slug) + } + } + + override fun mangaDetailsRequest(manga: SManga): Request { + val url = mangaUrlBuilder(manga.url) + + return GET(url.build(), headers) + } + + override fun mangaDetailsParse(response: Response): SManga { + val result = response.parseAs<detailsResponse>() + + return result.data.toSManga() + } + + override fun getMangaUrl(manga: SManga) = baseUrl + manga.url + + override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { + return Observable.just( + listOf( + SChapter.create().apply { + url = manga.url + name = manga.url.split("/")[1].replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase( + Locale.ROOT, + ) + } else { + it.toString() + } + } + date_upload = manga.description?.substringAfterLast("Date: ").parseDate() + }, + ), + ) + } + + override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url + + override fun pageListRequest(chapter: SChapter): Request { + val url = mangaUrlBuilder(chapter.url) + + return GET(url.build(), headers) + } + + override fun pageListParse(response: Response): List<Page> { + val result = response.parseAs<pageResponse>() + + return result.data.images?.mapIndexedNotNull { index, image -> + if (image.urls.url.isNullOrEmpty()) { + null + } else { + Page(index, "", image.urls.url) + } + } + ?: Page(1, "", result.data.preview.urls.url).let(::listOf) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = BROWSE_TYPE_PREF_KEY + title = BROWSE_TYPE_TITLE + entries = arrayOf("Gallery", "Image") + entryValues = arrayOf("gallery", "image") + summary = "%s" + setDefaultValue("gallery") + }.also(screen::addPreference) + } + + private fun SharedPreferences.getDefaultBrowse() = + getString(BROWSE_TYPE_PREF_KEY, "gallery")!! + + private fun SharedPreferences.getToken() = + getString(DEFAULT_TOKEN_PREF, DEFAULT_FALLBACK_TOKEN) ?: DEFAULT_FALLBACK_TOKEN + + private fun SharedPreferences.putToken(token: String) = + edit().putString(DEFAULT_TOKEN_PREF, token).commit() + + private inline fun <reified T> Response.parseAs(): T { + return json.decodeFromString(body.string()) + } + + private fun String?.parseDate(): Long { + return runCatching { dateFormat.parse(this!!)!!.time } + .getOrDefault(0L) + } + + companion object { + private const val limit = 20 + const val SEARCH_PREFIX = "url:" + + private const val DEFAULT_TOKEN_PREF = "default_token_pref" + private const val DEFAULT_FALLBACK_TOKEN = "01730876" + private const val TOKEN_EXCEPTION = "Unable to fetch new Token" + private val TokenRegex = Regex("""token\s*:\s*"([^\"]+)""") + + private val dateFormat by lazy { SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH) } + + private const val BROWSE_TYPE_PREF_KEY = "default_browse_type_key" + private const val BROWSE_TYPE_TITLE = "Default Browse List" + } + + override fun chapterListParse(response: Response) = + throw UnsupportedOperationException("Not implemented") + + override fun imageUrlParse(response: Response) = + throw UnsupportedOperationException("Not implemented") +} diff --git a/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayDto.kt b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayDto.kt new file mode 100644 index 0000000000..9cbe15cdff --- /dev/null +++ b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayDto.kt @@ -0,0 +1,116 @@ +package eu.kanade.tachiyomi.extension.all.simplycosplay + +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.Serializable +import java.util.Locale + +typealias browseResponse = Data<List<BrowseItem>> + +typealias detailsResponse = Data<DetailsResponse> + +typealias pageResponse = Data<PageResponse> + +@Serializable +data class Data<T>(val data: T) + +@Serializable +data class BrowseItem( + val title: String? = null, + val slug: String, + val type: String, + val preview: Images, +) { + fun toSManga() = SManga.create().apply { + title = this@BrowseItem.title ?: "" + url = "/${type.lowercase().trim()}/new/$slug" + thumbnail_url = preview.urls.thumb.url + description = preview.publish_date?.let { "Date: $it" } + } +} + +@Serializable +data class TagsResponse( + val aggs: Agg, +) + +@Serializable +data class Agg( + val tag_names: TagNames, +) + +@Serializable +data class TagNames( + val buckets: List<GenreItem>, +) + +@Serializable +data class GenreItem( + val key: String, +) + +@Serializable +data class DetailsResponse( + val title: String? = null, + val slug: String, + val type: String, + val preview: Images, + val tags: List<Tag>? = emptyList(), + val image_count: Int? = null, +) { + fun toSManga() = SManga.create().apply { + title = this@DetailsResponse.title ?: "" + url = "/${type.lowercase().trim()}/new/$slug" + thumbnail_url = preview.urls.thumb.url + genre = tags?.mapNotNull { it -> + it.name?.trim()?.split(" ")?.let { genre -> + genre.map { + it.replaceFirstChar { char -> + if (char.isLowerCase()) { + char.titlecase( + Locale.ROOT, + ) + } else { + char.toString() + } + } + } + }?.joinToString(" ") + }?.joinToString() + description = buildString { + append("Type: $type\n") + image_count?.let { append("Images: $it\n") } + preview.publish_date?.let { append("Date: $it\n") } + } + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + status = SManga.COMPLETED + } +} + +@Serializable +data class PageResponse( + val images: List<Images>? = null, + val preview: Images, +) + +@Serializable +data class Images( + val publish_date: String? = null, + val urls: Urls, +) + +@Serializable +data class Urls( + val url: String? = null, + val thumb: Url, +) + +@Serializable +data class Url( + val url: String? = null, +) + +@Serializable +data class Tag( + val name: String? = null, +) diff --git a/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayUrlActivity.kt b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayUrlActivity.kt new file mode 100644 index 0000000000..30bdb1ea8e --- /dev/null +++ b/src/all/simplycosplay/src/eu/kanade/tachiyomi/extension/all/simplycosplay/SimplyCosplayUrlActivity.kt @@ -0,0 +1,33 @@ +package eu.kanade.tachiyomi.extension.all.simplycosplay + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class SimplyCosplayUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size >= 3) { + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${SimplyCosplay.SEARCH_PREFIX}/${pathSegments[0]}/new/${pathSegments[2]}") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("SimplyCosplayUrlActivit", e.toString()) + } + } else { + Log.e("SimplyCosplayUrlActivit", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +} diff --git a/src/all/simplyhentai/AndroidManifest.xml b/src/all/simplyhentai/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/simplyhentai/AndroidManifest.xml +++ b/src/all/simplyhentai/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/tachidesk/AndroidManifest.xml b/src/all/tachidesk/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/tachidesk/AndroidManifest.xml +++ b/src/all/tachidesk/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/tachidesk/build.gradle b/src/all/tachidesk/build.gradle index 19b917ad17..d775d73abd 100644 --- a/src/all/tachidesk/build.gradle +++ b/src/all/tachidesk/build.gradle @@ -6,12 +6,7 @@ ext { extName = 'Suwayomi' pkgNameSuffix = 'all.tachidesk' extClass = '.Tachidesk' - extVersionCode = 10 -} - -dependencies { - implementation 'io.reactivex:rxandroid:1.2.1' - implementation 'io.reactivex:rxjava:1.3.8' + extVersionCode = 12 } apply from: "$rootDir/common.gradle" diff --git a/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt b/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt index cdcfd5cce6..e4f03de860 100644 --- a/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt +++ b/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt @@ -30,11 +30,11 @@ import okhttp3.Response import okhttp3.internal.toImmutableList import rx.Observable import rx.Single -import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.util.concurrent.TimeUnit import kotlin.math.min class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() { @@ -52,6 +52,7 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() { override val client: OkHttpClient = network.client.newBuilder() .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing + .callTimeout(2, TimeUnit.MINUTES) .build() override fun headersBuilder(): Headers.Builder = Headers.Builder().apply { @@ -206,7 +207,7 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() { client.newCall(GET("$baseUrl/api/v1/category", headers)).execute() } .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) + .observeOn(Schedulers.io()) .subscribe( { response -> categoryList = try { diff --git a/src/all/taddyink/AndroidManifest.xml b/src/all/taddyink/AndroidManifest.xml new file mode 100644 index 0000000000..c28a7be4e1 --- /dev/null +++ b/src/all/taddyink/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <application> + <activity + android:name=".all.taddyink.TaddyInkUrlActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="taddy.org" + android:scheme="https" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/all/taddyink/build.gradle b/src/all/taddyink/build.gradle new file mode 100644 index 0000000000..70d3eec33e --- /dev/null +++ b/src/all/taddyink/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Taddy INK (Webtoons)' + pkgNameSuffix = 'all.taddyink' + extClass = '.TaddyInkFactory' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/taddyink/res/mipmap-hdpi/ic_launcher.png b/src/all/taddyink/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..14da221504 Binary files /dev/null and b/src/all/taddyink/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/taddyink/res/mipmap-mdpi/ic_launcher.png b/src/all/taddyink/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..b039d0a934 Binary files /dev/null and b/src/all/taddyink/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/taddyink/res/mipmap-xhdpi/ic_launcher.png b/src/all/taddyink/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..eb6f492bba Binary files /dev/null and b/src/all/taddyink/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/taddyink/res/mipmap-xxhdpi/ic_launcher.png b/src/all/taddyink/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5484b6c507 Binary files /dev/null and b/src/all/taddyink/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/taddyink/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/taddyink/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a25796acc5 Binary files /dev/null and b/src/all/taddyink/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/taddyink/res/web_hi_res_512.png b/src/all/taddyink/res/web_hi_res_512.png new file mode 100644 index 0000000000..1f0bcbc437 Binary files /dev/null and b/src/all/taddyink/res/web_hi_res_512.png differ diff --git a/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt new file mode 100644 index 0000000000..d8e66548db --- /dev/null +++ b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt @@ -0,0 +1,188 @@ +package eu.kanade.tachiyomi.extension.all.taddyink + +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.injectLazy + +open class TaddyInk( + override val lang: String, + private val taddyLang: String, +) : ConfigurableSource, HttpSource() { + + final override val baseUrl = "https://taddy.org" + override val name = "Taddy INK (Webtoons)" + override val supportsLatest = false + + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .rateLimit(4) + .build() + } + + private val json: Json by injectLazy() + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + SwitchPreferenceCompat(screen.context).apply { + key = TITLE_PREF_KEY + title = TITLE_PREF + summaryOn = "Full Title" + summaryOff = "Short Title" + setDefaultValue(true) + }.also(screen::addPreference) + } + + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used!") + + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used!") + + override fun popularMangaRequest(page: Int): Request { + val url = "$baseUrl/feeds/directory/list".toHttpUrl().newBuilder() + .addQueryParameter("lang", taddyLang) + .addQueryParameter("taddyType", "comicseries") + .addQueryParameter("ua", "tc") + .addQueryParameter("page", page.toString()) + .addQueryParameter("limit", POPULAR_MANGA_LIMIT.toString()) + return GET(url.build(), headers) + } + + override fun popularMangaParse(response: Response) = parseManga(response) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val shouldFilterByGenre = filterList.findInstance<GenreFilter>()?.state != 0 + val shouldFilterByCreator = filterList.findInstance<CreatorFilter>()?.state?.isNotBlank() ?: false + val shouldFilterForTags = filterList.findInstance<TagFilter>()?.state?.isNotBlank() ?: false + + val url = "$baseUrl/feeds/directory/search".toHttpUrl().newBuilder() + .addQueryParameter("q", query) + .addQueryParameter("lang", taddyLang) + .addQueryParameter("taddyType", "comicseries") + .addQueryParameter("ua", "tc") + .addQueryParameter("page", page.toString()) + .addQueryParameter("limit", SEARCH_MANGA_LIMIT.toString()) + + if (shouldFilterByGenre) { + filterList.findInstance<GenreFilter>()?.let { f -> + url.addQueryParameter("genre", f.toUriPart()) + } + } + + if (shouldFilterByCreator) { + filterList.findInstance<CreatorFilter>()?.let { name -> + url.addQueryParameter("creator", name.state) + } + } + + if (shouldFilterForTags) { + filterList.findInstance<TagFilter>()?.let { tags -> + url.addQueryParameter("tags", tags.state) + } + } + + return GET(url.build(), headers) + } + + override fun searchMangaParse(response: Response) = parseManga(response) + + private fun parseManga(response: Response): MangasPage { + val comicSeries = json.decodeFromString<ComicResults>(response.body.string()) + val mangas = comicSeries.comicseries.map { TaddyUtils.getManga(it) } + val hasNextPage = comicSeries.comicseries.size == POPULAR_MANGA_LIMIT + return MangasPage(mangas, hasNextPage) + } + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET(manga.url, headers) + } + + override fun mangaDetailsParse(response: Response): SManga { + val comicObj = json.decodeFromString<Comic>(response.body.string()) + return TaddyUtils.getManga(comicObj) + } + + override fun chapterListRequest(manga: SManga): Request { + return GET(manga.url, headers) + } + + override fun chapterListParse(response: Response): List<SChapter> { + val comic = json.decodeFromString<Comic>(response.body.string()) + val sssUrl = comic.url + + val chapters = comic.issues.orEmpty().mapIndexed { i, chapter -> + SChapter.create().apply { + url = "$sssUrl#${chapter.identifier}" + name = chapter.name + date_upload = TaddyUtils.getTime(chapter.datePublished) + chapter_number = (comic.issues.orEmpty().size - i).toFloat() + } + } + + return chapters.reversed() + } + + override fun pageListRequest(chapter: SChapter): Request { + return GET(chapter.url, headers) + } + + override fun pageListParse(response: Response): List<Page> { + val requestUrl = response.request.url.toString() + val issueUuid = requestUrl.substringAfterLast("#") + val comic = json.decodeFromString<Comic>(response.body.string()) + + val issue = comic.issues.orEmpty().firstOrNull { it.identifier == issueUuid } + + return issue?.stories.orEmpty().mapIndexed { index, storyObj -> + Page(index, "", "${storyObj.storyImage?.base_url}${storyObj.storyImage?.story}") + } + } + + override fun imageUrlParse(response: Response) = "" + + override fun getFilterList(): FilterList = FilterList( + GenreFilter(), + Filter.Separator(), + Filter.Header("Filter by the creator or tags:"), + CreatorFilter(), + TagFilter(), + ) + + class CreatorFilter : AdvSearchEntryFilter("Creator") + class TagFilter : AdvSearchEntryFilter("Tags") + open class AdvSearchEntryFilter(name: String) : Filter.Text(name) + + private class GenreFilter : UriPartFilter( + "Filter By Genre", + TaddyUtils.genrePairs, + ) + + private open class UriPartFilter(displayName: String, val vals: List<Pair<String, String>>) : + Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second + } + + private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T + + companion object { + private const val TITLE_PREF_KEY = "display_full_title" + private const val TITLE_PREF = "Display manga title as" + + private const val POPULAR_MANGA_LIMIT = 25 + private const val SEARCH_MANGA_LIMIT = 25 + } +} diff --git a/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkFactory.kt b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkFactory.kt new file mode 100644 index 0000000000..19b2865516 --- /dev/null +++ b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkFactory.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.all.taddyink + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class TaddyInkFactory : SourceFactory { + override fun createSources(): List<Source> = listOf( + TaddyInk("all", ""), + ) +} diff --git a/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkUrlActivity.kt b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkUrlActivity.kt new file mode 100644 index 0000000000..3824961b80 --- /dev/null +++ b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkUrlActivity.kt @@ -0,0 +1,38 @@ +package eu.kanade.tachiyomi.extension.all.taddyink + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +/** + * Springboard that accepts https://nhentai.net/g/xxxxxx intents and redirects them to + * the main Tachiyomi process. + */ +class TaddyInkUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val id = pathSegments[1] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("TaddyInkUrlActivity", e.toString()) + } + } else { + Log.e("TaddyInkUrlActivity", "Could not parse URI from intent $intent") + } + + finish() + exitProcess(0) + } +} diff --git a/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt new file mode 100644 index 0000000000..7e54e641bb --- /dev/null +++ b/src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt @@ -0,0 +1,144 @@ +package eu.kanade.tachiyomi.extension.all.taddyink + +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.Serializable +import java.text.SimpleDateFormat +import java.util.Locale + +object TaddyUtils { + private val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + + fun getManga(comicObj: Comic): SManga { + val name = comicObj.name + val sssUrl = comicObj.url + val sssDescription = comicObj.description + val genres = comicObj.genres.orEmpty() + .mapNotNull { genreMap[it] } + .joinToString() + + val creators = comicObj.creators + ?.mapNotNull { it.name } + ?.joinToString() + + val thumbnailBaseUrl = comicObj.coverImage?.base_url ?: "" + val thumbnail = comicObj.coverImage?.cover_sm ?: "" + val thumbnailUrl = if (thumbnailBaseUrl.isNotEmpty() && thumbnail.isNotEmpty()) "$thumbnailBaseUrl$thumbnail" else "" + + return SManga.create().apply { + url = sssUrl + title = name + creators?.takeIf { it.isNotBlank() }?.let { author = it } + description = sssDescription + thumbnail_url = thumbnailUrl + status = SManga.ONGOING + genre = genres + initialized = true + } + } + + fun getTime(timeString: String): Long { + return runCatching { formatter.parse(timeString)?.time } + .getOrNull() ?: 0L + } + + val genrePairs: List<Pair<String, String>> = listOf( + Pair("", ""), + Pair("Action", "COMICSERIES_ACTION"), + Pair("Comedy", "COMICSERIES_COMEDY"), + Pair("Drama", "COMICSERIES_DRAMA"), + Pair("Educational", "COMICSERIES_EDUCATIONAL"), + Pair("Fantasy", "COMICSERIES_FANTASY"), + Pair("Historical", "COMICSERIES_HISTORICAL"), + Pair("Horror", "COMICSERIES_HORROR"), + Pair("Inspirational", "COMICSERIES_INSPIRATIONAL"), + Pair("Mystery", "COMICSERIES_MYSTERY"), + Pair("Romance", "COMICSERIES_ROMANCE"), + Pair("Sci-Fi", "COMICSERIES_SCI_FI"), + Pair("Slice Of Life", "COMICSERIES_SLICE_OF_LIFE"), + Pair("Superhero", "COMICSERIES_SUPERHERO"), + Pair("Supernatural", "COMICSERIES_SUPERNATURAL"), + Pair("Wholesome", "COMICSERIES_WHOLESOME"), + Pair("BL (Boy Love)", "COMICSERIES_BL"), + Pair("GL (Girl Love)", "COMICSERIES_GL"), + Pair("LGBTQ+", "COMICSERIES_LGBTQ"), + Pair("Thriller", "COMICSERIES_THRILLER"), + Pair("Zombies", "COMICSERIES_ZOMBIES"), + Pair("Post Apocalyptic", "COMICSERIES_POST_APOCALYPTIC"), + Pair("School", "COMICSERIES_SCHOOL"), + Pair("Sports", "COMICSERIES_SPORTS"), + Pair("Animals", "COMICSERIES_ANIMALS"), + Pair("Gaming", "COMICSERIES_GAMING"), + ) + + val genreMap: Map<String, String> = genrePairs.associateBy({ it.second }, { it.first }) +} + +@Serializable +data class ComicResults( + val status: String, + val comicseries: List<Comic> = emptyList(), +) + +@Serializable +data class Comic( + val identifier: String? = null, + val name: String = "Unknown", + val url: String, + val description: String? = null, + val genres: List<String>? = emptyList(), + val creators: List<Creator>? = emptyList(), + val coverImage: CoverImage? = null, + val bannerImage: BannerImage? = null, + val thumbnailImage: ThumbnailImage? = null, + val contentRating: String? = null, + val inLanguage: String? = null, + val seriesType: String? = null, + val issues: List<Chapter>? = emptyList(), +) + +@Serializable +data class CoverImage( + val base_url: String?, + val cover_sm: String?, + val cover_md: String?, + val cover_lg: String?, +) + +@Serializable +data class BannerImage( + val base_url: String?, + val banner_sm: String?, + val banner_md: String?, + val banner_lg: String?, +) + +@Serializable +data class ThumbnailImage( + val base_url: String?, + val thumbnail: String?, +) + +@Serializable +data class Creator( + val identifier: String? = null, + val name: String? = null, +) + +@Serializable +data class Chapter( + val identifier: String, + val name: String, + val datePublished: String, + val stories: List<Story>? = emptyList(), +) + +@Serializable +data class Story( + val storyImage: StoryImage?, +) + +@Serializable +data class StoryImage( + val base_url: String?, + val story: String?, +) diff --git a/src/all/tappytoon/AndroidManifest.xml b/src/all/tappytoon/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/tappytoon/AndroidManifest.xml +++ b/src/all/tappytoon/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/thelibraryofohara/AndroidManifest.xml b/src/all/thelibraryofohara/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/thelibraryofohara/AndroidManifest.xml +++ b/src/all/thelibraryofohara/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/toomics/AndroidManifest.xml b/src/all/toomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/toomics/AndroidManifest.xml +++ b/src/all/toomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/vinnieVeritas/AndroidManifest.xml b/src/all/vinnieVeritas/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/vinnieVeritas/AndroidManifest.xml +++ b/src/all/vinnieVeritas/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/xinmeitulu/AndroidManifest.xml b/src/all/xinmeitulu/AndroidManifest.xml index 0ecda378d4..be1cc85c1a 100644 --- a/src/all/xinmeitulu/AndroidManifest.xml +++ b/src/all/xinmeitulu/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -23,4 +22,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/all/xinmeitulu/build.gradle b/src/all/xinmeitulu/build.gradle index 1f0aac5886..39ccf2702e 100644 --- a/src/all/xinmeitulu/build.gradle +++ b/src/all/xinmeitulu/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Xinmeitulu' pkgNameSuffix = 'all.xinmeitulu' extClass = '.Xinmeitulu' - extVersionCode = 3 + extVersionCode = 4 isNsfw = true } diff --git a/src/all/xinmeitulu/src/eu/kanade/tachiyomi/extension/all/xinmeitulu/Xinmeitulu.kt b/src/all/xinmeitulu/src/eu/kanade/tachiyomi/extension/all/xinmeitulu/Xinmeitulu.kt index d99a4f2d5c..564875487b 100644 --- a/src/all/xinmeitulu/src/eu/kanade/tachiyomi/extension/all/xinmeitulu/Xinmeitulu.kt +++ b/src/all/xinmeitulu/src/eu/kanade/tachiyomi/extension/all/xinmeitulu/Xinmeitulu.kt @@ -36,7 +36,7 @@ class Xinmeitulu : ParsedHttpSource() { override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/$page") override fun popularMangaNextPageSelector() = ".next" - override fun popularMangaSelector() = ".container > .row > div" + override fun popularMangaSelector() = ".container > .row > div:has(figure)" override fun popularMangaFromElement(element: Element) = SManga.create().apply { setUrlWithoutDomain(element.select("figure > a").attr("abs:href")) title = element.select("figcaption").text() diff --git a/src/all/xkcd/AndroidManifest.xml b/src/all/xkcd/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/all/xkcd/AndroidManifest.xml +++ b/src/all/xkcd/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/all/yaoimangaonline/AndroidManifest.xml b/src/all/yaoimangaonline/AndroidManifest.xml index 95481c3e28..8072ee00db 100644 --- a/src/all/yaoimangaonline/AndroidManifest.xml +++ b/src/all/yaoimangaonline/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension"/> +<manifest /> diff --git a/src/ar/gmanga/AndroidManifest.xml b/src/ar/gmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ar/gmanga/AndroidManifest.xml +++ b/src/ar/gmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ar/gmanga/build.gradle b/src/ar/gmanga/build.gradle index 36fbab51f5..23005cdb25 100644 --- a/src/ar/gmanga/build.gradle +++ b/src/ar/gmanga/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'GMANGA' pkgNameSuffix = 'ar.gmanga' extClass = '.Gmanga' - extVersionCode = 12 + extVersionCode = 13 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt index 368c4d9bbe..5d56b48042 100644 --- a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt +++ b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt @@ -248,6 +248,7 @@ class Gmanga : ConfigurableSource, HttpSource() { val hasWebP = releaseData["webp_pages"]!!.jsonArray.size > 0 return releaseData[if (hasWebP) "webp_pages" else "pages"]!!.jsonArray.map { it.jsonPrimitive.content } + .sortedWith(pageSort) .mapIndexed { index, pageUri -> Page( index, @@ -257,6 +258,12 @@ class Gmanga : ConfigurableSource, HttpSource() { } } + private val pageSort = + compareBy<String>({ parseNumber(0, it) ?: Double.MAX_VALUE }, { parseNumber(1, it) }, { parseNumber(2, it) }) + + private fun parseNumber(index: Int, string: String): Double? = + Regex("\\d+").findAll(string).map { it.value }.toList().getOrNull(index)?.toDoubleOrNull() + override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", getFilterList()) diff --git a/src/ar/mangaae/AndroidManifest.xml b/src/ar/mangaae/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ar/mangaae/AndroidManifest.xml +++ b/src/ar/mangaae/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ar/remanga/AndroidManifest.xml b/src/ar/remanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ar/remanga/AndroidManifest.xml +++ b/src/ar/remanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ar/shqqaa/AndroidManifest.xml b/src/ar/shqqaa/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ar/shqqaa/AndroidManifest.xml +++ b/src/ar/shqqaa/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ar/teamx/AndroidManifest.xml b/src/ar/teamx/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ar/teamx/AndroidManifest.xml +++ b/src/ar/teamx/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ar/teamx/build.gradle b/src/ar/teamx/build.gradle index 1c639e7abb..921b0bd64a 100644 --- a/src/ar/teamx/build.gradle +++ b/src/ar/teamx/build.gradle @@ -5,7 +5,8 @@ ext { extName = 'Team X' pkgNameSuffix = 'ar.teamx' extClass = '.TeamX' - extVersionCode = 11 + extVersionCode = 15 + isNsfw = false } apply from: "$rootDir/common.gradle" diff --git a/src/ar/teamx/src/eu/kanade/tachiyomi/extension/ar/teamx/TeamX.kt b/src/ar/teamx/src/eu/kanade/tachiyomi/extension/ar/teamx/TeamX.kt index 6e11a4b0c2..47393f8f9f 100644 --- a/src/ar/teamx/src/eu/kanade/tachiyomi/extension/ar/teamx/TeamX.kt +++ b/src/ar/teamx/src/eu/kanade/tachiyomi/extension/ar/teamx/TeamX.kt @@ -22,7 +22,7 @@ class TeamX : ParsedHttpSource() { override val name = "Team X" - override val baseUrl = "https://team1x1.fun" + override val baseUrl = "https://team11x11.com" override val lang = "ar" @@ -190,7 +190,7 @@ class TeamX : ParsedHttpSource() { // Pages override fun pageListParse(document: Document): List<Page> { - return document.select("div.image_list img").mapIndexed { i, img -> + return document.select("div.image_list img[src]").mapIndexed { i, img -> Page(i, "", img.absUrl("src")) } } diff --git a/src/ca/fansubscat/build.gradle b/src/ca/fansubscat/build.gradle deleted file mode 100644 index 5712d25d87..0000000000 --- a/src/ca/fansubscat/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Fansubs.cat' - pkgNameSuffix = 'ca.fansubscat' - extClass = '.FansubsCat' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8e04687834..0000000000 Binary files a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0702ecea8..0000000000 Binary files a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b0027ad577..0000000000 Binary files a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2424e711f4..0000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ccc47ea5fc..0000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/web_hi_res_512.png b/src/ca/fansubscat/res/web_hi_res_512.png deleted file mode 100644 index 8e8555a100..0000000000 Binary files a/src/ca/fansubscat/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt b/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt deleted file mode 100644 index c65c57bba7..0000000000 --- a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt +++ /dev/null @@ -1,163 +0,0 @@ -package eu.kanade.tachiyomi.extension.ca.fansubscat - -import eu.kanade.tachiyomi.AppInfo -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.float -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.long -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy - -class FansubsCat : HttpSource() { - - override val name = "Fansubs.cat" - - override val baseUrl = "https://manga.fansubs.cat" - - override val lang = "ca" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Tachiyomi/FansubsCat/${AppInfo.getVersionName()}") - - override val client: OkHttpClient = network.client - - private val json: Json by injectLazy() - - private val apiBaseUrl = "https://api.fansubs.cat" - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = json.decodeFromString<JsonObject>(response.body.string()) - - val mangas = jsonObject["result"]!!.jsonArray.map { json -> - SManga.create().apply { - url = json.jsonObject["slug"]!!.jsonPrimitive.content - title = json.jsonObject["name"]!!.jsonPrimitive.content - thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content - author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull - description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull - status = json.jsonObject["status"]!!.jsonPrimitive.content.toStatus() - genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull - } - } - - return MangasPage(mangas, mangas.size >= 20) - } - - private fun parseChapterListFromJson(response: Response): List<SChapter> { - val jsonObject = json.decodeFromString<JsonObject>(response.body.string()) - - return jsonObject["result"]!!.jsonArray.map { json -> - SChapter.create().apply { - url = json.jsonObject["id"]!!.jsonPrimitive.content - name = json.jsonObject["title"]!!.jsonPrimitive.content - chapter_number = json.jsonObject["number"]!!.jsonPrimitive.float - scanlator = json.jsonObject["fansub"]!!.jsonPrimitive.content - date_upload = json.jsonObject["created"]!!.jsonPrimitive.long - } - } - } - - private fun parsePageListFromJson(response: Response): List<Page> { - val jsonObject = json.decodeFromString<JsonObject>(response.body.string()) - - return jsonObject["result"]!!.jsonArray.mapIndexed { i, it -> - Page(i, it.jsonObject["url"]!!.jsonPrimitive.content, it.jsonObject["url"]!!.jsonPrimitive.content) - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/popular/$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/recent/$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$apiBaseUrl/manga/search/$page".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("query", query) - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/${manga.url}", headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = json.decodeFromString<JsonObject>(response.body.string()) - val resultObject = jsonObject.jsonObject["result"]!!.jsonObject - - return SManga.create().apply { - url = resultObject["slug"]!!.jsonPrimitive.content - title = resultObject["name"]!!.jsonPrimitive.content - thumbnail_url = resultObject["thumbnail_url"]!!.jsonPrimitive.content - author = resultObject["author"]!!.jsonPrimitive.contentOrNull - description = resultObject["synopsis"]!!.jsonPrimitive.contentOrNull - status = resultObject["status"]!!.jsonPrimitive.content.toStatus() - genre = resultObject["genres"]!!.jsonPrimitive.contentOrNull - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("finished", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = GET("$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}", headers) - - override fun chapterListParse(response: Response): List<SChapter> = parseChapterListFromJson(response) - - // Pages - - override fun pageListRequest(chapter: SChapter): Request = GET("$apiBaseUrl/manga/pages/${chapter.url}", headers) - - override fun pageListParse(response: Response): List<Page> = parsePageListFromJson(response) - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/de/mangatube/AndroidManifest.xml b/src/de/mangatube/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/de/mangatube/AndroidManifest.xml +++ b/src/de/mangatube/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/de/wiemanga/AndroidManifest.xml b/src/de/wiemanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/de/wiemanga/AndroidManifest.xml +++ b/src/de/wiemanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/MangaRok/AndroidManifest.xml b/src/en/MangaRok/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/MangaRok/AndroidManifest.xml +++ b/src/en/MangaRok/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/allanime/AndroidManifest.xml b/src/en/allanime/AndroidManifest.xml index 8ce8c57a68..581bf58ca6 100644 --- a/src/en/allanime/AndroidManifest.xml +++ b/src/en/allanime/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" - xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".en.allanime.AllAnimeUrlActivity" @@ -13,15 +12,10 @@ <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> - <data android:host="allanime.to"/> - <data android:host="allanime.co"/> - - <data - android:pathPattern="/manga/..*" - android:scheme="https" /> - <data - android:pathPattern="/read/..*" - android:scheme="https" /> + <data android:host="allanime.ai"/> + <data android:scheme="https"/> + <data android:pathPattern="/manga/..*"/> + <data android:pathPattern="/read/..*"/> </intent-filter> </activity> </application> diff --git a/src/en/allanime/build.gradle b/src/en/allanime/build.gradle index c3a5b0e589..6003d48631 100644 --- a/src/en/allanime/build.gradle +++ b/src/en/allanime/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'AllAnime' pkgNameSuffix = 'en.allanime' extClass = '.AllAnime' - extVersionCode = 5 + extVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt index b13786d0b7..97e530a5a4 100644 --- a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnime.kt @@ -5,7 +5,10 @@ import android.content.SharedPreferences import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat -import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.buildApiHeaders +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.firstInstanceOrNull +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseAs +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.toJsonRequestBody import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.interceptor.rateLimit @@ -16,44 +19,30 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient +import kotlinx.serialization.json.float import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale class AllAnime : ConfigurableSource, HttpSource() { override val name = "AllAnime" + override val baseUrl = "https://allanime.ai" + + private val apiUrl = "https://api.allanime.day/api" + override val lang = "en" override val supportsLatest = true - private val json: Json = Json { - ignoreUnknownKeys = true - explicitNulls = false - encodeDefaults = true - coerceInputValues = true - } - - private val preferences: SharedPreferences = + private val preferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } - override val baseUrl = "https://allanime.ai" - - private val apiUrl = "https://api.allanime.day/api" - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() + override val client = network.cloudflareClient.newBuilder() .addInterceptor { chain -> val request = chain.request() val frag = request.url.fragment @@ -80,24 +69,34 @@ class AllAnime : ConfigurableSource, HttpSource() { /* Popular */ override fun popularMangaRequest(page: Int): Request { - val payloadObj = ApiPopularPayload( - size = limit, - dateRange = 0, - page = page, - allowAdult = preferences.allowAdult, + val payload = GraphQL( + PopularVariables( + type = "manga", + size = limit, + dateRange = 0, + page = page, + allowAdult = preferences.allowAdult, + allowUnknown = false, + ), + POPULAR_QUERY, ) - return apiRequest(payloadObj) + val requestBody = payload.toJsonRequestBody() + + val apiHeaders = headersBuilder().buildApiHeaders(requestBody) + + return POST(apiUrl, apiHeaders, requestBody) } override fun popularMangaParse(response: Response): MangasPage { val result = response.parseAs<ApiPopularResponse>() - val titleStyle = preferences.titlePref val mangaList = result.data.popular.mangas - .mapNotNull { it.manga?.toSManga(titleStyle) } + .mapNotNull { it.manga?.toSManga() } + + val hasNextPage = result.data.popular.mangas.size == limit - return MangasPage(mangaList, mangaList.size == limit) + return MangasPage(mangaList, hasNextPage) } /* Latest */ @@ -118,28 +117,41 @@ class AllAnime : ConfigurableSource, HttpSource() { } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val payloadObj = ApiSearchPayload( - query = query, - size = limit, - page = page, - genres = filters.firstInstanceOrNull<GenreFilter>()?.included, - excludeGenres = filters.firstInstanceOrNull<GenreFilter>()?.excluded, - translationType = "sub", - countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL", - allowAdult = preferences.allowAdult, + val payload = GraphQL( + SearchVariables( + search = SearchPayload( + query = query.takeUnless { it.isEmpty() }, + sortBy = filters.firstInstanceOrNull<SortFilter>()?.getValue(), + genres = filters.firstInstanceOrNull<GenreFilter>()?.included, + excludeGenres = filters.firstInstanceOrNull<GenreFilter>()?.excluded, + isManga = true, + allowAdult = preferences.allowAdult, + allowUnknown = false, + ), + size = limit, + page = page, + translationType = "sub", + countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL", + ), + SEARCH_QUERY, ) - return apiRequest(payloadObj) + val requestBody = payload.toJsonRequestBody() + + val apiHeaders = headersBuilder().buildApiHeaders(requestBody) + + return POST(apiUrl, apiHeaders, requestBody) } override fun searchMangaParse(response: Response): MangasPage { val result = response.parseAs<ApiSearchResponse>() - val titleStyle = preferences.titlePref - val mangaList = result.data.mangas.mangas - .map { it.toSManga(titleStyle) } + val mangaList = result.data.mangas.edges + .map(SearchManga::toSManga) - return MangasPage(mangaList, mangaList.size == limit) + val hasNextPage = result.data.mangas.edges.size == limit + + return MangasPage(mangaList, hasNextPage) } override fun getFilterList() = getFilters() @@ -147,15 +159,23 @@ class AllAnime : ConfigurableSource, HttpSource() { /* Details */ override fun mangaDetailsRequest(manga: SManga): Request { val mangaId = manga.url.split("/")[2] - val payloadObj = ApiIDPayload(mangaId, DETAILS_QUERY) - return apiRequest(payloadObj) + val payload = GraphQL( + IDVariables(mangaId), + DETAILS_QUERY, + ) + + val requestBody = payload.toJsonRequestBody() + + val apiHeaders = headersBuilder().buildApiHeaders(requestBody) + + return POST(apiUrl, apiHeaders, requestBody) } override fun mangaDetailsParse(response: Response): SManga { val result = response.parseAs<ApiMangaDetailsResponse>() - return result.data.manga.toSManga(preferences.titlePref) + return result.data.manga.toSManga() } override fun getMangaUrl(manga: SManga): String { @@ -173,44 +193,32 @@ class AllAnime : ConfigurableSource, HttpSource() { override fun chapterListRequest(manga: SManga): Request { val mangaId = manga.url.split("/")[2] - val payloadObj = ApiIDPayload(mangaId, CHAPTERS_QUERY) - return apiRequest(payloadObj) - } + val payload = GraphQL( + ChapterListVariables( + id = "manga@$mangaId", + chapterNumStart = 0f, + chapterNumEnd = 9999f, + ), + CHAPTERS_QUERY, + ) - private fun chapterDetailsRequest(manga: SManga, start: String, end: String): Request { - val mangaId = manga.url.split("/")[2] - val payloadObj = ApiChapterListDetailsPayload(mangaId, start.toFloat(), end.toFloat()) + val requestBody = payload.toJsonRequestBody() - return apiRequest(payloadObj) + val apiHeaders = headersBuilder().buildApiHeaders(requestBody) + + return POST(apiUrl, apiHeaders, requestBody) } private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { val result = response.parseAs<ApiChapterListResponse>() - val chapters = result.data.manga.chapters.sub - ?.sortedBy { it.toFloat() } - ?: return emptyList() - val chapterDetails = client.newCall( - chapterDetailsRequest(manga, chapters.first(), chapters.last()), - ).execute() - .use { - it.parseAs<ApiChapterListDetailsResponse>() - }.data.chapterList - ?.sortedBy { it.chapterNum } + val chapters = result.data.chapterList?.sortedByDescending { it.chapterNum.float } + ?: return emptyList() val mangaUrl = manga.url.substringAfter("/manga/") - return chapterDetails?.zip(chapters)?.map { (details, chapterNum) -> - SChapter.create().apply { - name = "Chapter $chapterNum" - if (!details.title.isNullOrEmpty() && !details.title.contains(numberRegex)) { - name += ": ${details.title}" - } - url = "/read/$mangaUrl/chapter-$chapterNum-sub" - date_upload = details.uploadDates?.sub.parseDate() - } - }?.reversed() ?: emptyList() + return chapters.map { it.toSChapter(mangaUrl) } } override fun chapterListParse(response: Response): List<SChapter> { @@ -222,56 +230,38 @@ class AllAnime : ConfigurableSource, HttpSource() { } /* Pages */ - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - pageListParse(response, chapter) - } - } - override fun pageListRequest(chapter: SChapter): Request { val chapterUrl = chapter.url.split("/") val mangaId = chapterUrl[2] val chapterNo = chapterUrl[4].split("-")[1] - val payloadObj = ApiPageListPayload( - id = mangaId, - chapterNum = chapterNo, - translationType = "sub", + val payload = GraphQL( + PageListVariables( + id = mangaId, + chapterNum = chapterNo, + translationType = "sub", + ), + PAGE_QUERY, ) - return apiRequest(payloadObj) + val requestBody = payload.toJsonRequestBody() + + val apiHeaders = headersBuilder().buildApiHeaders(requestBody) + + return POST(apiUrl, apiHeaders, requestBody) } - private fun pageListParse(response: Response, chapter: SChapter): List<Page> { - val result = json.decodeFromString<ApiPageListResponse>(response.body.string()) - val pages = result.data.pageList?.serverList?.get(0) ?: return emptyList() - - val imageDomain = if (!pages.serverUrl.isNullOrEmpty()) { - pages.serverUrl.let { server -> - if (server.matches(urlRegex)) { - server - } else { - "https://$server" - } - } - } else { - // in rare cases, the api doesn't return server url - // for that, we try to parse the frontend html to get it - val chapterUrl = getChapterUrl(chapter) - val frontendRequest = GET(chapterUrl, headers) - val url = client.newCall(frontendRequest).execute().use { frontendResponse -> - val document = frontendResponse.asJsoup() - val script = document.select("script:containsData(window.__NUXT__)").firstOrNull() - imageUrlFromPageRegex.matchEntire(script.toString()) - ?.groupValues - ?.getOrNull(1) - ?.replace("\\u002F", "/") - ?.substringBeforeLast(pages.pictureUrls?.first().toString(), "") + override fun pageListParse(response: Response): List<Page> { + val result = response.parseAs<ApiPageListResponse>() + val pages = result.data.pageList?.edges?.get(0) ?: return emptyList() + + val imageDomain = pages.serverUrl?.let { server -> + if (server.matches(urlRegex)) { + server + } else { + "https://$server" } - url?.takeIf { it.isNotEmpty() } ?: return emptyList() - } + } ?: return emptyList() return pages.pictureUrls?.mapIndexed { index, image -> Page( @@ -281,38 +271,10 @@ class AllAnime : ConfigurableSource, HttpSource() { } ?: emptyList() } - override fun pageListParse(response: Response): List<Page> { - throw UnsupportedOperationException("Not used") - } - override fun imageUrlParse(response: Response): String { throw UnsupportedOperationException("Not used") } - /* Helpers */ - private inline fun <reified T> apiRequest(payloadObj: T): Request { - val payload = json.encodeToString(payloadObj) - .toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .add("Content-Length", payload.contentLength().toString()) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST(apiUrl, newHeaders, payload) - } - - private inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string()) - - private inline fun <reified R> List<*>.firstInstanceOrNull(): R? = - filterIsInstance<R>().firstOrNull() - - private fun String?.parseDate(): Long { - return runCatching { - dateFormat.parse(this!!)!!.time - }.getOrDefault(0L) - } - override fun setupPreferenceScreen(screen: PreferenceScreen) { ListPreference(screen.context).apply { key = IMAGE_QUALITY_PREF @@ -321,27 +283,15 @@ class AllAnime : ConfigurableSource, HttpSource() { entryValues = arrayOf("original", "800", "480") setDefaultValue(IMAGE_QUALITY_PREF_DEFAULT) summary = "Warning: Wp quality servers can be slow and might not work sometimes" - }.let { screen.addPreference(it) } - - ListPreference(screen.context).apply { - key = TITLE_PREF - title = "Preferred Title Style" - entries = arrayOf("Romaji", "English", "Native") - entryValues = arrayOf("romaji", "eng", "native") - setDefaultValue(TITLE_PREF_DEFAULT) - summary = "%s" - }.let { screen.addPreference(it) } + }.also(screen::addPreference) SwitchPreferenceCompat(screen.context).apply { key = SHOW_ADULT_PREF title = "Show Adult Content" setDefaultValue(SHOW_ADULT_PREF_DEFAULT) - }.let { screen.addPreference(it) } + }.also(screen::addPreference) } - private val SharedPreferences.titlePref - get() = getString(TITLE_PREF, TITLE_PREF_DEFAULT) - private val SharedPreferences.allowAdult get() = getBoolean(SHOW_ADULT_PREF, SHOW_ADULT_PREF_DEFAULT) @@ -350,22 +300,11 @@ class AllAnime : ConfigurableSource, HttpSource() { companion object { private const val limit = 20 - private val numberRegex by lazy { Regex("\\d") } - val whitespace by lazy { Regex("\\s+") } - val dateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) - } const val SEARCH_PREFIX = "id:" - const val thumbnail_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/" - private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() val urlRegex = Regex("^https?://.*") private const val image_cdn = "https://wp.youtube-anime.com" private val imageQualityRegex = Regex("^https?://(.*)#.*") - val titleSpecialCharactersRegex = Regex("[^a-z\\d]+") - private val imageUrlFromPageRegex = Regex("selectedPicturesServer:\\[\\{.*?url:\"(.*?)\".*?\\}\\]") - private const val TITLE_PREF = "pref_title" - private const val TITLE_PREF_DEFAULT = "romaji" private const val SHOW_ADULT_PREF = "pref_adult" private const val SHOW_ADULT_PREF_DEFAULT = false private const val IMAGE_QUALITY_PREF = "pref_quality" diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt index db5dabcefd..3c6ffd281f 100644 --- a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeDto.kt @@ -1,45 +1,53 @@ package eu.kanade.tachiyomi.extension.en.allanime +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseDate +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseDescription +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseStatus +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.parseThumbnailUrl +import eu.kanade.tachiyomi.extension.en.allanime.AllAnimeHelper.titleToSlug +import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import org.jsoup.Jsoup -import java.util.Locale +import kotlinx.serialization.json.JsonPrimitive + +typealias ApiPopularResponse = Data<PopularData> + +typealias ApiSearchResponse = Data<SearchData> + +typealias ApiMangaDetailsResponse = Data<MangaDetailsData> + +typealias ApiChapterListResponse = Data<ChapterListData> + +typealias ApiPageListResponse = Data<PageListData> @Serializable -data class ApiPopularResponse( - val data: PopularResponseData, -) { - @Serializable - data class PopularResponseData( - @SerialName("queryPopular") val popular: PopularData, - ) { - @Serializable - data class PopularData( - @SerialName("recommendations") val mangas: List<Popular>, - ) { - @Serializable - data class Popular( - @SerialName("anyCard") val manga: SearchManga? = null, - ) - } - } -} +data class Data<T>(val data: T) @Serializable -data class ApiSearchResponse( - val data: SearchResponseData, -) { - @Serializable - data class SearchResponseData( - val mangas: SearchResultMangas, - ) { - @Serializable - data class SearchResultMangas( - @SerialName("edges") val mangas: List<SearchManga>, - ) - } -} +data class Edges<T>(val edges: List<T>) + +// Popular +@Serializable +data class PopularData( + @SerialName("queryPopular") val popular: PopularMangas, +) + +@Serializable +data class PopularMangas( + @SerialName("recommendations") val mangas: List<PopularManga>, +) + +@Serializable +data class PopularManga( + @SerialName("anyCard") val manga: SearchManga? = null, +) + +// Search +@Serializable +data class SearchData( + val mangas: Edges<SearchManga>, +) @Serializable data class SearchManga( @@ -47,164 +55,101 @@ data class SearchManga( val name: String, val thumbnail: String? = null, val englishName: String? = null, - val nativeName: String? = null, ) { - fun toSManga(titleStyle: String?) = SManga.create().apply { - title = titleStyle.preferedName(name, englishName, nativeName) + fun toSManga() = SManga.create().apply { + title = englishName ?: name url = "/manga/$id/${name.titleToSlug()}" thumbnail_url = thumbnail?.parseThumbnailUrl() } } +// Details @Serializable -data class ApiMangaDetailsResponse( - val data: MangaDetailsData, -) { - @Serializable - data class MangaDetailsData( - val manga: Manga, - ) { - @Serializable - data class Manga( - @SerialName("_id") val id: String, - val name: String, - val thumbnail: String? = null, - val description: String? = null, - val authors: List<String>? = emptyList(), - val genres: List<String>? = emptyList(), - val tags: List<String>? = emptyList(), - val status: String? = null, - val altNames: List<String>? = emptyList(), - val englishName: String? = null, - val nativeName: String? = null, - ) { - fun toSManga(titleStyle: String?) = SManga.create().apply { - title = titleStyle.preferedName(name, englishName, nativeName) - url = "/manga/$id/${name.titleToSlug()}" - thumbnail_url = thumbnail?.parseThumbnailUrl() - description = this@Manga.description?.parseDescription() - if (!altNames.isNullOrEmpty()) { - if (description.isNullOrEmpty()) { - description = "Alternative Titles:\n" - } else { - description += "\n\nAlternative Titles:\n" - } - - description += altNames.joinToString("\n") { "• ${it.trim()}" } - } - if (authors?.isNotEmpty() == true) { - author = authors.first().trim() - artist = author - } - genre = "${genres?.joinToString { it.trim() }}, ${tags?.joinToString { it.trim() }}" - status = this@Manga.status.parseStatus() - } - } - } -} +data class MangaDetailsData( + val manga: Manga, +) @Serializable -data class ApiChapterListResponse( - val data: ChapterListData, +data class Manga( + @SerialName("_id") val id: String, + val name: String, + val thumbnail: String? = null, + val description: String? = null, + val authors: List<String>? = emptyList(), + val genres: List<String>? = emptyList(), + val tags: List<String>? = emptyList(), + val status: String? = null, + val altNames: List<String>? = emptyList(), + val englishName: String? = null, ) { - @Serializable - data class ChapterListData( - val manga: ChapterList, - ) { - @Serializable - data class ChapterList( - @SerialName("availableChaptersDetail") val chapters: AvailableChapters, - ) { - @Serializable - data class AvailableChapters( - val sub: List<String>? = null, - ) + fun toSManga() = SManga.create().apply { + title = englishName ?: name + url = "/manga/$id/${name.titleToSlug()}" + thumbnail_url = thumbnail?.parseThumbnailUrl() + description = this@Manga.description?.parseDescription() + if (!altNames.isNullOrEmpty()) { + if (description.isNullOrEmpty()) { + description = "Alternative Titles:\n" + } else { + description += "\n\nAlternative Titles:\n" + } + + description += altNames.joinToString("\n") { "• ${it.trim()}" } + } + if (authors?.isNotEmpty() == true) { + author = authors.first().trim() + artist = author } + genre = ((genres ?: emptyList()) + (tags ?: emptyList())) + .joinToString { it.trim() } + status = this@Manga.status.parseStatus() } } +// chapters details @Serializable -data class ApiChapterListDetailsResponse( - val data: ChapterListData, -) { - @Serializable - data class ChapterListData( - @SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(), - ) { - @Serializable - data class ChapterData( - @SerialName("episodeIdNum") val chapterNum: Float, - @SerialName("notes") val title: String? = null, - val uploadDates: DateDto? = null, - ) { - @Serializable - data class DateDto( - val sub: String? = null, - ) - } - } -} +data class ChapterListData( + @SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(), +) @Serializable -data class ApiPageListResponse( - val data: PageListData, +data class ChapterData( + @SerialName("episodeIdNum") val chapterNum: JsonPrimitive, + @SerialName("notes") val title: String? = null, + val uploadDates: DateDto? = null, ) { - @Serializable - data class PageListData( - @SerialName("chapterPages") val pageList: PageList?, - ) { - @Serializable - data class PageList( - @SerialName("edges") val serverList: List<Servers>?, - ) { - @Serializable - data class Servers( - @SerialName("pictureUrlHead") val serverUrl: String? = null, - val pictureUrls: List<PageUrl>?, - ) { - @Serializable - data class PageUrl( - val url: String, - ) - } + fun toSChapter(mangaUrl: String) = SChapter.create().apply { + name = "Chapter $chapterNum" + if (!title.isNullOrEmpty() && !title.contains(numberRegex)) { + name += ": $title" } + url = "/read/$mangaUrl/chapter-$chapterNum-sub" + date_upload = uploadDates?.sub.parseDate() } -} -fun String.parseThumbnailUrl(): String { - return if (this.matches(AllAnime.urlRegex)) { - this - } else { - "${AllAnime.thumbnail_cdn}$this?w=250" + companion object { + private val numberRegex by lazy { Regex("\\d") } } } -fun String?.parseStatus(): Int { - if (this == null) { - return SManga.UNKNOWN - } - - return when { - this.contains("releasing", true) -> SManga.ONGOING - this.contains("finished", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } -} +@Serializable +data class DateDto( + val sub: String? = null, +) -private fun String.titleToSlug() = this.trim() - .lowercase(Locale.US) - .replace(AllAnime.titleSpecialCharactersRegex, "-") +// page lsit +@Serializable +data class PageListData( + @SerialName("chapterPages") val pageList: Edges<Servers>?, +) -private fun String?.preferedName(name: String, englishName: String?, nativeName: String?): String { - return when (this) { - "eng" -> englishName - "native" -> nativeName - else -> name - } ?: name -} +@Serializable +data class Servers( + @SerialName("pictureUrlHead") val serverUrl: String? = null, + val pictureUrls: List<PageUrl>?, +) -private fun String.parseDescription(): String { - return Jsoup.parse( - this.replace("<br>", "br2n"), - ).text().replace("br2n", "\n") -} +@Serializable +data class PageUrl( + val url: String, +) diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt index 60b0eeef08..4dce4326e1 100644 --- a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeFiters.kt @@ -3,91 +3,30 @@ package eu.kanade.tachiyomi.extension.en.allanime import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -internal class Genre(name: String) : Filter.TriState(name) - -internal class CountryFilter(name: String, private val countries: List<Pair<String, String>>) : - Filter.Select<String>(name, countries.map { it.first }.toTypedArray()) { - fun getValue() = countries[state].second +abstract class SelectFilter(name: String, private val options: List<Pair<String, String>>) : + Filter.Select<String>(name, options.map { it.first }.toTypedArray()) { + fun getValue() = options[state].second.takeUnless { it.isEmpty() } } -internal class GenreFilter(title: String, genres: List<Genre>) : - Filter.Group<Genre>(title, genres) { - val included: List<String> - get() = state.filter { it.isIncluded() }.map { it.name } +internal class SortFilter(name: String, sorts: List<Pair<String, String>>) : SelectFilter(name, sorts) + +internal class CountryFilter(name: String, countries: List<Pair<String, String>>) : SelectFilter(name, countries) + +internal class Genre(name: String) : Filter.TriState(name) + +internal class GenreFilter(title: String, genres: List<String>) : + Filter.Group<Genre>(title, genres.map(::Genre)) { + val included: List<String>? + get() = state.filter { it.isIncluded() }.map { it.name }.takeUnless { it.isEmpty() } - val excluded: List<String> - get() = state.filter { it.isExcluded() }.map { it.name } + val excluded: List<String>? + get() = state.filter { it.isExcluded() }.map { it.name }.takeUnless { it.isEmpty() } } -private val genreList: List<Genre> = listOf( - Genre("4 Koma"), - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Cars"), - Genre("Comedy"), - Genre("Cooking"), - Genre("Crossdressing"), - Genre("Dementia"), - Genre("Demons"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Game"), - Genre("Gender Bender"), - Genre("Gyaru"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Isekai"), - Genre("Josei"), - Genre("Kids"), - Genre("Loli"), - Genre("Magic"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Medical"), - Genre("Military"), - Genre("Monster Girls"), - Genre("Music"), - Genre("Mystery"), - Genre("One Shot"), - Genre("Parody"), - Genre("Police"), - Genre("Post Apocalyptic"), - Genre("Psychological"), - Genre("Reincarnation"), - Genre("Reverse Harem"), - Genre("Romance"), - Genre("Samurai"), - Genre("School"), - Genre("Sci-Fi"), - Genre("Seinen"), - Genre("Shota"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Space"), - Genre("Sports"), - Genre("Super Power"), - Genre("Supernatural"), - Genre("Suspense"), - Genre("Thriller"), - Genre("Tragedy"), - Genre("Unknown"), - Genre("Vampire"), - Genre("Webtoons"), - Genre("Yaoi"), - Genre("Youkai"), - Genre("Yuri"), - Genre("Zombies"), +private val sortList = listOf( + Pair("Update", ""), + Pair("Name Ascending", "Name_ASC"), + Pair("Name Descending", "Name_DESC"), ) private val countryList: List<Pair<String, String>> = listOf( @@ -97,7 +36,79 @@ private val countryList: List<Pair<String, String>> = listOf( Pair("Korea", "KR"), ) +private val genreList: List<String> = listOf( + "4 Koma", + "Action", + "Adult", + "Adventure", + "Cars", + "Comedy", + "Cooking", + "Crossdressing", + "Dementia", + "Demons", + "Doujinshi", + "Drama", + "Ecchi", + "Fantasy", + "Game", + "Gender Bender", + "Gyaru", + "Harem", + "Historical", + "Horror", + "Isekai", + "Josei", + "Kids", + "Loli", + "Magic", + "Manhua", + "Manhwa", + "Martial Arts", + "Mature", + "Mecha", + "Medical", + "Military", + "Monster Girls", + "Music", + "Mystery", + "One Shot", + "Parody", + "Police", + "Post Apocalyptic", + "Psychological", + "Reincarnation", + "Reverse Harem", + "Romance", + "Samurai", + "School", + "Sci-Fi", + "Seinen", + "Shota", + "Shoujo", + "Shoujo Ai", + "Shounen", + "Shounen Ai", + "Slice of Life", + "Smut", + "Space", + "Sports", + "Super Power", + "Supernatural", + "Suspense", + "Thriller", + "Tragedy", + "Unknown", + "Vampire", + "Webtoons", + "Yaoi", + "Youkai", + "Yuri", + "Zombies", +) + fun getFilters() = FilterList( + SortFilter("Sort", sortList), CountryFilter("Countries", countryList), GenreFilter("Genres", genreList), ) diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeHelper.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeHelper.kt new file mode 100644 index 0000000000..f579c4493b --- /dev/null +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeHelper.kt @@ -0,0 +1,81 @@ +package eu.kanade.tachiyomi.extension.en.allanime + +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.jsoup.Jsoup +import java.text.SimpleDateFormat +import java.util.Locale + +object AllAnimeHelper { + + val json: Json = Json { + ignoreUnknownKeys = true + explicitNulls = false + encodeDefaults = true + coerceInputValues = true + } + + fun String.parseThumbnailUrl(): String { + return if (this.matches(AllAnime.urlRegex)) { + this + } else { + "$thumbnail_cdn$this?w=250" + } + } + + fun String?.parseStatus(): Int { + if (this == null) { + return SManga.UNKNOWN + } + + return when { + this.contains("releasing", true) -> SManga.ONGOING + this.contains("finished", true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + } + + fun String.titleToSlug() = this.trim() + .lowercase(Locale.US) + .replace(titleSpecialCharactersRegex, "-") + + fun String.parseDescription(): String { + return Jsoup.parse( + this.replace("<br>", "br2n"), + ).text().replace("br2n", "\n") + } + + fun String?.parseDate(): Long { + return runCatching { + dateFormat.parse(this!!)!!.time + }.getOrDefault(0L) + } + + inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string()) + + inline fun <reified T> List<*>.firstInstanceOrNull(): T? = + filterIsInstance<T>().firstOrNull() + + inline fun <reified T : Any> T.toJsonRequestBody(): RequestBody = + json.encodeToString(this) + .toRequestBody(JSON_MEDIA_TYPE) + + fun Headers.Builder.buildApiHeaders(requestBody: RequestBody) = this + .add("Content-Length", requestBody.contentLength().toString()) + .add("Content-Type", requestBody.contentType().toString()) + .build() + + private const val thumbnail_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/" + private val titleSpecialCharactersRegex by lazy { Regex("[^a-z\\d]+") } + private val dateFormat by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) + } + val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() +} diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimePayloadDto.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimePayloadDto.kt index c481290a6a..7f53253a13 100644 --- a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimePayloadDto.kt +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimePayloadDto.kt @@ -1,163 +1,59 @@ package eu.kanade.tachiyomi.extension.en.allanime +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ApiPopularPayload( - val variables: ApiPopularVariables, +data class GraphQL<T>( + val variables: T, val query: String, -) { - @Serializable - data class ApiPopularVariables( - val type: String, - val size: Int, - val dateRange: Int, - val page: Int, - val allowAdult: Boolean, - val allowUnknown: Boolean, - ) - - constructor( - type: String = "manga", - size: Int, - dateRange: Int, - page: Int, - allowAdult: Boolean = false, - allowUnknown: Boolean = false, - ) : this( - ApiPopularVariables( - type = type, - size = size, - dateRange = dateRange, - page = page, - allowAdult = allowAdult, - allowUnknown = allowUnknown, - ), - POPULAR_QUERY, - ) -} +) @Serializable -data class ApiSearchPayload( - val variables: ApiSearchVariables, - val query: String, -) { - @Serializable - data class ApiSearchVariables( - val search: SearchPayload, - val limit: Int, - val page: Int, - val translationType: String, - val countryOrigin: String, - ) - - @Serializable - data class SearchPayload( - val query: String, - val genres: List<String>?, - val excludeGenres: List<String>?, - val isManga: Boolean, - val allowAdult: Boolean, - val allowUnknown: Boolean, - ) - - constructor( - query: String, - size: Int, - page: Int, - genres: List<String>?, - excludeGenres: List<String>?, - translationType: String, - countryOrigin: String, - isManga: Boolean = true, - allowAdult: Boolean = false, - allowUnknown: Boolean = false, - ) : this( - ApiSearchVariables( - search = SearchPayload( - query = query, - genres = genres, - excludeGenres = excludeGenres, - isManga = isManga, - allowAdult = allowAdult, - allowUnknown = allowUnknown, - ), - limit = size, - page = page, - translationType = translationType, - countryOrigin = countryOrigin, - ), - SEARCH_QUERY, - ) -} +data class PopularVariables( + val type: String, + val size: Int, + val dateRange: Int, + val page: Int, + val allowAdult: Boolean, + val allowUnknown: Boolean, +) @Serializable -data class ApiIDPayload( - val variables: ApiIDVariables, - val query: String, -) { - @Serializable - data class ApiIDVariables( - val id: String, - ) - - constructor( - id: String, - graphqlQuery: String, - ) : this( - ApiIDVariables(id), - graphqlQuery, - ) -} +data class SearchVariables( + val search: SearchPayload, + @SerialName("limit") val size: Int, + val page: Int, + val translationType: String, + val countryOrigin: String, +) @Serializable -data class ApiChapterListDetailsPayload( - val variables: ApiChapterDetailsVariables, - val query: String, -) { - @Serializable - data class ApiChapterDetailsVariables( - val id: String, - val chapterNumStart: Float, - val chapterNumEnd: Float, - ) +data class SearchPayload( + val query: String?, + val sortBy: String?, + val genres: List<String>?, + val excludeGenres: List<String>?, + val isManga: Boolean, + val allowAdult: Boolean, + val allowUnknown: Boolean, +) - constructor( - id: String, - chapterNumStart: Float, - chapterNumEnd: Float, - ) : this( - ApiChapterDetailsVariables( - id = "manga@$id", - chapterNumStart = chapterNumStart, - chapterNumEnd = chapterNumEnd, - ), - CHAPTERS_DETAILS_QUERY, - ) -} +@Serializable +data class IDVariables( + val id: String, +) @Serializable -data class ApiPageListPayload( - val variables: ApiPageListVariables, - val query: String, -) { - @Serializable - data class ApiPageListVariables( - val id: String, - val chapterNum: String, - val translationType: String, - ) +data class ChapterListVariables( + val id: String, + val chapterNumStart: Float, + val chapterNumEnd: Float, +) - constructor( - id: String, - chapterNum: String, - translationType: String, - ) : this( - ApiPageListVariables( - id = id, - chapterNum = chapterNum, - translationType = translationType, - ), - PAGE_QUERY, - ) -} +@Serializable +data class PageListVariables( + val id: String, + val chapterNum: String, + val translationType: String, +) diff --git a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt index 054503b2b2..2128947b75 100644 --- a/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt +++ b/src/en/allanime/src/eu/kanade/tachiyomi/extension/en/allanime/AllAnimeQueries.kt @@ -1,11 +1,8 @@ package eu.kanade.tachiyomi.extension.en.allanime -import eu.kanade.tachiyomi.extension.en.allanime.AllAnime.Companion.whitespace - private fun buildQuery(queryAction: () -> String): String { return queryAction() .trimIndent() - .replace(whitespace, " ") .replace("%", "$") } @@ -33,7 +30,6 @@ val POPULAR_QUERY: String = buildQuery { name thumbnail englishName - nativeName } } } @@ -62,7 +58,6 @@ val SEARCH_QUERY: String = buildQuery { name thumbnail englishName - nativeName } } } @@ -83,23 +78,12 @@ val DETAILS_QUERY: String = buildQuery { status altNames englishName - nativeName } } """ } val CHAPTERS_QUERY: String = buildQuery { - """ - query (%id: String!) { - manga(_id: %id) { - availableChaptersDetail - } - } - """ -} - -val CHAPTERS_DETAILS_QUERY: String = buildQuery { """ query (%id: String!, %chapterNumStart: Float!, %chapterNumEnd: Float!) { episodeInfos( diff --git a/src/en/apairof2/AndroidManifest.xml b/src/en/apairof2/AndroidManifest.xml index 92fcba2325..fa0c911c9a 100644 --- a/src/en/apairof2/AndroidManifest.xml +++ b/src/en/apairof2/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/apairof2/src/eu/kanade/tachiyomi/extension/en/apairof2/APairOf2.kt b/src/en/apairof2/src/eu/kanade/tachiyomi/extension/en/apairof2/APairOf2.kt index 91f687155b..0d736289bb 100644 --- a/src/en/apairof2/src/eu/kanade/tachiyomi/extension/en/apairof2/APairOf2.kt +++ b/src/en/apairof2/src/eu/kanade/tachiyomi/extension/en/apairof2/APairOf2.kt @@ -142,7 +142,7 @@ class APairOf2 : ParsedHttpSource() { return document.select(".swiper-slide img").mapIndexed { index, img -> Page( index = index, - imageUrl = img.imgAttr() + imageUrl = img.imgAttr(), ) } } diff --git a/src/en/aurora/AndroidManifest.xml b/src/en/aurora/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/aurora/AndroidManifest.xml +++ b/src/en/aurora/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/boommanga/AndroidManifest.xml b/src/en/boommanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/boommanga/AndroidManifest.xml +++ b/src/en/boommanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/buttsmithy/AndroidManifest.xml b/src/en/buttsmithy/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/buttsmithy/AndroidManifest.xml +++ b/src/en/buttsmithy/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/clonemanga/AndroidManifest.xml b/src/en/clonemanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/clonemanga/AndroidManifest.xml +++ b/src/en/clonemanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/collectedcurios/AndroidManifest.xml b/src/en/collectedcurios/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/en/collectedcurios/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/en/collectedcurios/build.gradle b/src/en/collectedcurios/build.gradle new file mode 100644 index 0000000000..fee1c12a43 --- /dev/null +++ b/src/en/collectedcurios/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Collected Curios' + pkgNameSuffix = 'en.collectedcurios' + extClass = '.Collectedcurios' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/collectedcurios/res/mipmap-hdpi/ic_launcher.png b/src/en/collectedcurios/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..1f05e5105a Binary files /dev/null and b/src/en/collectedcurios/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/collectedcurios/res/mipmap-mdpi/ic_launcher.png b/src/en/collectedcurios/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..d98bb875a9 Binary files /dev/null and b/src/en/collectedcurios/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/collectedcurios/res/mipmap-xhdpi/ic_launcher.png b/src/en/collectedcurios/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..8a9f7dbdfd Binary files /dev/null and b/src/en/collectedcurios/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/collectedcurios/res/mipmap-xxhdpi/ic_launcher.png b/src/en/collectedcurios/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8614ea6015 Binary files /dev/null and b/src/en/collectedcurios/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/collectedcurios/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/collectedcurios/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7a28937344 Binary files /dev/null and b/src/en/collectedcurios/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/collectedcurios/res/web_hi_res_512.png b/src/en/collectedcurios/res/web_hi_res_512.png new file mode 100644 index 0000000000..dd8a674151 Binary files /dev/null and b/src/en/collectedcurios/res/web_hi_res_512.png differ diff --git a/src/en/collectedcurios/src/eu/kanade/tachiyomi/extension/en/collectedcurios/Collectedcurios.kt b/src/en/collectedcurios/src/eu/kanade/tachiyomi/extension/en/collectedcurios/Collectedcurios.kt new file mode 100644 index 0000000000..fac6bf8738 --- /dev/null +++ b/src/en/collectedcurios/src/eu/kanade/tachiyomi/extension/en/collectedcurios/Collectedcurios.kt @@ -0,0 +1,153 @@ +package eu.kanade.tachiyomi.extension.en.collectedcurios + +import android.util.Log +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +class Collectedcurios : ParsedHttpSource() { + + override val name = "Collected Curios" + + override val baseUrl = "https://www.collectedcurios.com" + + override val lang = "en" + + override val supportsLatest = false + + override fun fetchPopularManga(page: Int): Observable<MangasPage> { + return Observable.just( + MangasPage( + arrayListOf( + SManga.create().apply { + title = "Sequential Art" + artist = "Jolly Jack aka Phillip M Jackson" + author = "Jolly Jack aka Phillip M Jackson" + status = SManga.ONGOING + url = "/sequentialart.php" + description = "Sequential Art webcomic." + thumbnail_url = "https://www.collectedcurios.com/images/CC_2011_Sequential_Art_Button.jpg" + }, + + SManga.create().apply { + title = "Battle Bunnies" + artist = "Jolly Jack aka Phillip M Jackson" + author = "Jolly Jack aka Phillip M Jackson" + status = SManga.ONGOING + url = "/battlebunnies.php" + description = "Battle Bunnies webcomic." + thumbnail_url = "https://www.collectedcurios.com/images/CC_2011_Battle_Bunnies_Button.jpg" + }, + + /* + SManga.create().apply { + title = "Spider and Scorpion" + artist = "Jolly Jack aka Phillip M Jackson" + author = "Jolly Jack aka Phillip M Jackson" + status = SManga.ONGOING + url = "/spiderandscorpion.php" + description = "Spider and Scorpion webcomic." + thumbnail_url = "https://www.collectedcurios.com/images/CC_2011_Spider_And_Scorpion_Button.jpg" + }, + */ + ), + false, + ), + ) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return fetchPopularManga(1) + } + + override fun fetchMangaDetails(manga: SManga): Observable<SManga> { + return Observable.just(manga) + } + + override fun chapterListParse(response: Response): List<SChapter> { + val responseJs = response.asJsoup() + + val chapters = + if (responseJs.selectFirst("img[title=Last]") == null) { + responseJs.selectFirst("input[title=Jump to number]") + ?.attr("value")?.toInt() + } else { + responseJs.selectFirst("img[title=Last]")?.parent() + ?.attr("href")?.substringAfter("=")?.toInt() + } + + var chapterList = mutableListOf<SChapter>() + + for (i in chapters?.downTo(1)!!) { + chapterList.add( + SChapter.create().apply { + url = "${response.request.url}?s=$i" + name = "Chapter - $i" + chapter_number = i.toFloat() + date_upload = 0L + }, + ) + } + return chapterList + } + + override fun chapterListSelector() = throw Exception("Not used") + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + name = element.selectFirst(".w3-round")?.attr("value") ?: "Chapter" + } + + override fun pageListParse(document: Document): List<Page> = throw Exception("Not used") + + override fun fetchPageList(chapter: SChapter) = Observable.just(listOf(Page(0, chapter.url)))!! + + override fun imageUrlParse(response: Response): String { + val url = response.request.url.toString() + val document = response.asJsoup() + + return when { + url.contains("sequentialart") -> + document.selectFirst(".w3-image")!!.absUrl("src") + url.contains("battlebunnies") || url.contains("spiderandscorpion") -> + document.selectFirst("#strip")!!.absUrl("src") + else -> throw Exception("Could not find the image") + } + } + + override fun imageUrlParse(document: Document) = throw Exception("Not used") + + override fun popularMangaSelector(): String = throw Exception("Not used") + + override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") + + override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") + + override fun searchMangaSelector(): String = throw Exception("Not used") + + override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") + + override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") + + override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") + + override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") + + override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") + + override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") + + override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") + + override fun latestUpdatesSelector(): String = throw Exception("Not used") +} diff --git a/src/en/comicastle/AndroidManifest.xml b/src/en/comicastle/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/comicastle/AndroidManifest.xml +++ b/src/en/comicastle/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/comicextra/AndroidManifest.xml b/src/en/comicextra/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/comicextra/AndroidManifest.xml +++ b/src/en/comicextra/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/comicextra/build.gradle b/src/en/comicextra/build.gradle index 66aa3e7c34..74cd6bd3ff 100644 --- a/src/en/comicextra/build.gradle +++ b/src/en/comicextra/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'ComicExtra' pkgNameSuffix = 'en.comicextra' extClass = '.ComicExtra' - extVersionCode = 13 + extVersionCode = 14 } apply from: "$rootDir/common.gradle" diff --git a/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt b/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt index 43c1048cdf..ee58828916 100644 --- a/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt +++ b/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt @@ -22,7 +22,7 @@ class ComicExtra : ParsedHttpSource() { override val name = "ComicExtra" - override val baseUrl = "https://comicextra.net" + override val baseUrl = "https://comicextra.me" override val lang = "en" diff --git a/src/en/darklegacycomics/AndroidManifest.xml b/src/en/darklegacycomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/darklegacycomics/AndroidManifest.xml +++ b/src/en/darklegacycomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/digitalcomicmuseum/AndroidManifest.xml b/src/en/digitalcomicmuseum/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/digitalcomicmuseum/AndroidManifest.xml +++ b/src/en/digitalcomicmuseum/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/disasterscans/AndroidManifest.xml b/src/en/disasterscans/AndroidManifest.xml index dd672cb933..3b3e56a7a2 100644 --- a/src/en/disasterscans/AndroidManifest.xml +++ b/src/en/disasterscans/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -21,4 +20,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/en/doujins/AndroidManifest.xml b/src/en/doujins/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/doujins/AndroidManifest.xml +++ b/src/en/doujins/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/dynasty/AndroidManifest.xml b/src/en/dynasty/AndroidManifest.xml index 794320d441..ef85bdb79a 100644 --- a/src/en/dynasty/AndroidManifest.xml +++ b/src/en/dynasty/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".en.dynasty.DynastyUrlActivity" @@ -35,4 +34,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/en/earlymanga/AndroidManifest.xml b/src/en/earlymanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/earlymanga/AndroidManifest.xml +++ b/src/en/earlymanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/earlymanga/build.gradle b/src/en/earlymanga/build.gradle index db56a167a9..c15395794c 100644 --- a/src/en/earlymanga/build.gradle +++ b/src/en/earlymanga/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'EarlyManga' pkgNameSuffix = 'en.earlymanga' extClass = '.EarlyManga' - extVersionCode = 20 + extVersionCode = 21 } apply from: "$rootDir/common.gradle" diff --git a/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt b/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt index 6a92558117..59903bb9d5 100644 --- a/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt +++ b/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt @@ -28,7 +28,7 @@ class EarlyManga : ParsedHttpSource() { override val name = "EarlyManga" - override val baseUrl = "https://v1.earlym.org" + override val baseUrl = "https://earlycomic.com" override val lang = "en" diff --git a/src/en/eggporncomics/AndroidManifest.xml b/src/en/eggporncomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/eggporncomics/AndroidManifest.xml +++ b/src/en/eggporncomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/elanschool/AndroidManifest.xml b/src/en/elanschool/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/elanschool/AndroidManifest.xml +++ b/src/en/elanschool/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/existentialcomics/AndroidManifest.xml b/src/en/existentialcomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/existentialcomics/AndroidManifest.xml +++ b/src/en/existentialcomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/explosm/AndroidManifest.xml b/src/en/explosm/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/explosm/AndroidManifest.xml +++ b/src/en/explosm/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/grrlpower/AndroidManifest.xml b/src/en/grrlpower/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/grrlpower/AndroidManifest.xml +++ b/src/en/grrlpower/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/grrlpower/src/eu/kanade/tachiyomi/extension/en/grrlpower/GrrlPower.kt b/src/en/grrlpower/src/eu/kanade/tachiyomi/extension/en/grrlpower/GrrlPower.kt index 7279ace402..ddda5d2c79 100644 --- a/src/en/grrlpower/src/eu/kanade/tachiyomi/extension/en/grrlpower/GrrlPower.kt +++ b/src/en/grrlpower/src/eu/kanade/tachiyomi/extension/en/grrlpower/GrrlPower.kt @@ -93,8 +93,8 @@ class GrrlPower( name = link.text() setUrlWithoutDomain(link.attr("href")) date_upload = date?.time ?: 0L - // chapter_number isn't set as suggested by arkon - // https://github.com/tachiyomiorg/tachiyomi-extensions/pull/15717#discussion_r1138014748 + // chapter_number isn't set as suggested by arkon + // https://github.com/tachiyomiorg/tachiyomi-extensions/pull/15717#discussion_r1138014748 } } } diff --git a/src/en/gunnerkriggcourt/AndroidManifest.xml b/src/en/gunnerkriggcourt/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/gunnerkriggcourt/AndroidManifest.xml +++ b/src/en/gunnerkriggcourt/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/gwtb/AndroidManifest.xml b/src/en/gwtb/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/gwtb/AndroidManifest.xml +++ b/src/en/gwtb/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/hbrowse/AndroidManifest.xml b/src/en/hbrowse/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/hbrowse/AndroidManifest.xml +++ b/src/en/hbrowse/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/hentai2read/AndroidManifest.xml b/src/en/hentai2read/AndroidManifest.xml index 20a0f3b7fe..0fa9f56ec8 100644 --- a/src/en/hentai2read/AndroidManifest.xml +++ b/src/en/hentai2read/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/hentaidexy/AndroidManifest.xml b/src/en/hentaidexy/AndroidManifest.xml index 8f35f8fe2a..e586b0e240 100644 --- a/src/en/hentaidexy/AndroidManifest.xml +++ b/src/en/hentaidexy/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/hentaifox/AndroidManifest.xml b/src/en/hentaifox/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/hentaifox/AndroidManifest.xml +++ b/src/en/hentaifox/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/hentaihere/AndroidManifest.xml b/src/en/hentaihere/AndroidManifest.xml index 5fd2b3523f..453dec0072 100644 --- a/src/en/hentaihere/AndroidManifest.xml +++ b/src/en/hentaihere/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/hiveworks/AndroidManifest.xml b/src/en/hiveworks/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/hiveworks/AndroidManifest.xml +++ b/src/en/hiveworks/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/honkaiimpact3/AndroidManifest.xml b/src/en/honkaiimpact3/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/honkaiimpact3/AndroidManifest.xml +++ b/src/en/honkaiimpact3/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/irovedout/AndroidManifest.xml b/src/en/irovedout/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/irovedout/AndroidManifest.xml +++ b/src/en/irovedout/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/keenspot/AndroidManifest.xml b/src/en/keenspot/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/keenspot/AndroidManifest.xml +++ b/src/en/keenspot/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/killsixbilliondemons/AndroidManifest.xml b/src/en/killsixbilliondemons/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/killsixbilliondemons/AndroidManifest.xml +++ b/src/en/killsixbilliondemons/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/kouhaiwork/AndroidManifest.xml b/src/en/kouhaiwork/AndroidManifest.xml index 91abd1c18b..7335b11c44 100644 --- a/src/en/kouhaiwork/AndroidManifest.xml +++ b/src/en/kouhaiwork/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".en.kouhaiwork.KouhaiActivity" android:theme="@android:style/Theme.NoDisplay" diff --git a/src/en/latisbooks/AndroidManifest.xml b/src/en/latisbooks/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/latisbooks/AndroidManifest.xml +++ b/src/en/latisbooks/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/latisbooks/build.gradle b/src/en/latisbooks/build.gradle index ab4e4dc384..51ba73f1bd 100644 --- a/src/en/latisbooks/build.gradle +++ b/src/en/latisbooks/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Latis Books' pkgNameSuffix = 'en.latisbooks' extClass = '.Latisbooks' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } diff --git a/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt b/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt index 3dc054b283..e3cc0517e4 100644 --- a/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt +++ b/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt @@ -37,7 +37,7 @@ class Latisbooks : HttpSource() { initialized = true title = "Bodysuit 23" url = "/archive/" - thumbnail_url = response.asJsoup().select("img.thumb-image").firstOrNull()?.attr("abs:data-src") + thumbnail_url = "https://images.squarespace-cdn.com/content/v1/56595108e4b01110e1cf8735/1511856223610-NSB8O5OJ1F6KPQL0ZGBH/image-asset.jpeg" } } @@ -121,7 +121,7 @@ class Latisbooks : HttpSource() { val blocks = response.asJsoup().select("div.content-wrapper div.row div.col") // Handle multiple images per page (e.g. Page 23+24) - val pages = blocks.select("img.thumb-image") + val pages = blocks.select("div.image-block-wrapper img") .mapIndexed { i, it -> Page(i, "", it.attr("abs:data-src")) } .toMutableList() diff --git a/src/en/lemonfont/AndroidManifest.xml b/src/en/lemonfont/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/lemonfont/AndroidManifest.xml +++ b/src/en/lemonfont/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/likemanga/AndroidManifest.xml b/src/en/likemanga/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/en/likemanga/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/en/likemanga/build.gradle b/src/en/likemanga/build.gradle new file mode 100644 index 0000000000..a0c5dc3b25 --- /dev/null +++ b/src/en/likemanga/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'LikeManga' + pkgNameSuffix = 'en.likemanga' + extClass = '.LikeManga' + extVersionCode = 3 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0992d97287 Binary files /dev/null and b/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4159c59d51 Binary files /dev/null and b/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..f55409dd99 Binary files /dev/null and b/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7a367aa387 Binary files /dev/null and b/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..7c485d4aa2 Binary files /dev/null and b/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/likemanga/res/web_hi_res_512.png b/src/en/likemanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..c15b6655d8 Binary files /dev/null and b/src/en/likemanga/res/web_hi_res_512.png differ diff --git a/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt new file mode 100644 index 0000000000..ea4104cec8 --- /dev/null +++ b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt @@ -0,0 +1,293 @@ +package eu.kanade.tachiyomi.extension.en.likemanga + +import android.util.Base64 +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale + +class LikeManga : ParsedHttpSource() { + + override val name = "LikeManga" + + override val lang = "en" + + override val baseUrl = "https://likemanga.io" + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(1, 2) + .build() + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", "$baseUrl/") + + private val json: Json by injectLazy() + + override fun popularMangaRequest(page: Int): Request { + return searchMangaRequest(page, "", FilterList(SortFilter("top-manga"))) + } + + override fun popularMangaParse(response: Response) = searchMangaParse(response) + override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element) + override fun popularMangaSelector() = searchMangaSelector() + override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() + + override fun latestUpdatesRequest(page: Int): Request { + return searchMangaRequest(page, "", FilterList(SortFilter("lastest-chap"))) + } + + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) + override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) + override fun latestUpdatesSelector() = searchMangaSelector() + override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("act", "searchadvance") + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + filter.checked?.forEach { + addQueryParameter("f[genres][]", it) + } + } + is ChapterCountFilter -> { + filter.selected?.let { + addQueryParameter("f[min_num_chapter]", it) + } + } + is StatusFilter -> { + filter.selected?.let { + addQueryParameter("f[status]", it) + } + } + is SortFilter -> { + filter.selected?.let { + addQueryParameter("f[sortby]", it) + } + } + else -> {} + } + } + if (query.isNotEmpty()) { + addQueryParameter("f[keyword]", query.trim()) + } + if (page > 1) { + addQueryParameter("pageNum", page.toString()) + } + }.build() + + return GET(url, headers) + } + + private var genresList: List<Pair<String, String>> = emptyList() + + private fun parseGenres(document: Document): List<Pair<String, String>> { + return document.selectFirst("div.search_genres") + ?.select("div.form-check") + .orEmpty() + .mapNotNull { + val label = it.selectFirst("label") + ?.text()?.trim() ?: return@mapNotNull null + + val value = it.selectFirst("input") + ?.attr("value") ?: return@mapNotNull null + + Pair(label, value) + } + } + + override fun getFilterList(): FilterList { + val filters: MutableList<Filter<*>> = mutableListOf( + SortFilter(), + StatusFilter(), + ChapterCountFilter(), + ) + + filters += if (genresList.isEmpty()) { + listOf( + Filter.Separator(), + Filter.Header("Press 'reset' to attempt to show Genres"), + ) + } else { + listOf( + GenreFilter("Genre", genresList), + ) + } + + return FilterList(filters) + } + + override fun searchMangaParse(response: Response): MangasPage { + if (genresList.isEmpty()) { + val document = response.peekBody(Long.MAX_VALUE).string() + .let { Jsoup.parse(it, response.request.url.toString()) } + + genresList = parseGenres(document) + } + + return super.searchMangaParse(response) + } + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + thumbnail_url = element.selectFirst("img")?.imgAttr() + title = element.select(".title-manga").text() + } + + override fun searchMangaSelector() = "div.card-body div.card" + override fun searchMangaNextPageSelector() = "ul.pagination a:contains(»)" + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + title = document.select("#title-detail-manga").text() + thumbnail_url = document.selectFirst(".detail-info img")?.imgAttr() + description = document.selectFirst("#summary_shortened")?.text()?.trim() + genre = document.select(".list-info a[href*=/genres/]").joinToString { it.text() } + status = document.selectFirst(".list-info .status p:nth-child(2)")?.text().parseStatus() + author = document.selectFirst(".list-info .author p:nth-child(2)")?.text() + ?.takeUnless { it.trim() == "Updating" } + } + + private fun String?.parseStatus(): Int { + if (this == null) return SManga.UNKNOWN + + return when { + contains("Complete", true) -> SManga.COMPLETED + contains("In process", true) -> SManga.ONGOING + contains("Pause", true) -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + } + + override fun chapterListParse(response: Response): List<SChapter> { + val document = response.use { it.asJsoup() } + + val chapters = document.select(chapterListSelector()) + .map(::chapterFromElement) + .toMutableList() + + val lastPage = document.select("div.chapters_pagination a:not(.next)").last() + ?.attr("onclick") + ?.substringAfter("(") + ?.substringBefore(")") + ?.toIntOrNull() + ?: return chapters + + val id = document.select("#title-detail-manga").attr("data-manga") + .toIntOrNull() ?: return chapters + + for (page in 2..lastPage) { + chapters.addAll(fetchAjaxChapterList(id, page)) + } + + return chapters + } + + private fun fetchAjaxChapterList(id: Int, page: Int): List<SChapter> { + val request = ajaxChapterListRequest(id, page) + val response = client.newCall(request).execute() + + if (!response.isSuccessful) { + response.close() + return emptyList() + } + + return ajaxChapterListParse(response) + } + + private fun ajaxChapterListRequest(id: Int, page: Int): Request { + val url = baseUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("act", "ajax") + addQueryParameter("code", "load_list_chapter") + addQueryParameter("manga_id", id.toString()) + addQueryParameter("page_num", page.toString()) + addQueryParameter("chap_id", "0") + addQueryParameter("keyword", "") + }.build() + + return GET(url, headers) + } + + private fun ajaxChapterListParse(response: Response): List<SChapter> { + val responseJson = response.use { json.parseToJsonElement(it.body.string()) }.jsonObject + val htmlString = responseJson["list_chap"]!!.jsonPrimitive.content + val document = Jsoup.parseBodyFragment(htmlString, response.request.url.toString()) + + return document.select(chapterListSelector()) + .map(::chapterFromElement) + } + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + name = element.select("a").text() + date_upload = element.selectFirst(".chapter-release-date")?.text().parseDate() + } + + override fun chapterListSelector() = ".wp-manga-chapter" + + private fun String?.parseDate(): Long { + return runCatching { + dateFormat.parse(this!!)!!.time + }.getOrDefault(0L) + } + + override fun pageListParse(document: Document): List<Page> { + val element = document.selectFirst("div.reading input#next_img_token") + + if (element != null) { + val imgCdnUrl = document.selectFirst("div.reading #currentlink")?.attr("value") + ?: throw Exception("Could not find image CDN URL") + + val token = element.attr("value").split(".")[1] + val jsonData = json.parseToJsonElement(String(Base64.decode(token, Base64.DEFAULT))).jsonObject + val encodedImgArray = jsonData["data"]!!.jsonPrimitive.content + val imgArray = String(Base64.decode(encodedImgArray, Base64.DEFAULT)) + + return json.parseToJsonElement(imgArray).jsonArray.mapIndexed { i, img -> + Page(i, "", "$imgCdnUrl/${img.jsonPrimitive.content}") + } + } + + return document.select("div.reading-detail.box_doc img:not(noscript img)") + .mapIndexed { i, img -> Page(i, "", img.imgAttr()) } + } + + private fun Element.imgAttr(): String? { + return when { + hasAttr("data-cfsrc") -> attr("abs:data-cfsrc") + hasAttr("data-src") -> attr("abs:data-src") + hasAttr("data-lazy-src") -> attr("abs:data-lazy-src") + hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ") + else -> attr("abs:src") + } + } + + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not Used") + + companion object { + val dateFormat by lazy { + SimpleDateFormat("MMMM dd, yyyy", Locale.ENGLISH) + } + } +} diff --git a/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt new file mode 100644 index 0000000000..21946c7825 --- /dev/null +++ b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt @@ -0,0 +1,71 @@ +package eu.kanade.tachiyomi.extension.en.likemanga + +import eu.kanade.tachiyomi.source.model.Filter + +abstract class SelectFilter( + name: String, + private val options: List<Pair<String, String>>, + defaultValue: String? = null, +) : Filter.Select<String>( + name, + options.map { it.first }.toTypedArray(), + options.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0, +) { + val selected get() = options[state].second.takeUnless { it.isEmpty() } +} + +class CheckBoxFilter( + name: String, + val value: String, +) : Filter.CheckBox(name) + +class GenreFilter( + name: String, + genres: List<Pair<String, String>>, +) : Filter.Group<CheckBoxFilter>( + name, + genres.map { CheckBoxFilter(it.first, it.second) }, +) { + val checked get() = state.filter { it.state }.map { it.value }.takeUnless { it.isEmpty() } +} + +class SortFilter(default: String? = null) : SelectFilter( + "Sort By", + listOf( + Pair("", ""), + Pair("Lasted update", "lastest-chap"), + Pair("Lasted manga", "lastest-manga"), + Pair("Top all", "top-manga"), + Pair("Top month", "top-month"), + Pair("Top week", "top-week"), + Pair("Top day", "top-day"), + Pair("Follow", "follow"), + Pair("Comments", "comment"), + Pair("Number of Chapters", "num-chap"), + ), + default, +) + +class StatusFilter : SelectFilter( + "Status", + listOf( + Pair("All", ""), + Pair("Complete", "Complete"), + Pair("In process", "In process"), + Pair("Pause", "Pause"), + ), +) + +class ChapterCountFilter : SelectFilter( + "Number of Chapters", + listOf( + Pair("", ""), + Pair(">= 0 chapter", "1"), + Pair(">= 50 chapter", "50"), + Pair(">= 100 chapter", "100"), + Pair(">= 200 chapter", "200"), + Pair(">= 300 chapter", "300"), + Pair(">= 400 chapter", "400"), + Pair(">= 500 chapter", "500"), + ), +) diff --git a/src/en/loadingartist/AndroidManifest.xml b/src/en/loadingartist/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/loadingartist/AndroidManifest.xml +++ b/src/en/loadingartist/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/lynxscans/AndroidManifest.xml b/src/en/lynxscans/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/en/lynxscans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/lynxscans/build.gradle b/src/en/lynxscans/build.gradle deleted file mode 100644 index 4b62f029fb..0000000000 --- a/src/en/lynxscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'LynxScans' - pkgNameSuffix = 'en.lynxscans' - extClass = '.LynxScans' - extVersionCode = 7 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/lynxscans/res/mipmap-hdpi/ic_launcher.png b/src/en/lynxscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1dd6cfde42..0000000000 Binary files a/src/en/lynxscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lynxscans/res/mipmap-mdpi/ic_launcher.png b/src/en/lynxscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1c411bc85c..0000000000 Binary files a/src/en/lynxscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lynxscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/lynxscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f9083e6c81..0000000000 Binary files a/src/en/lynxscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lynxscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/lynxscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 951a16c8f1..0000000000 Binary files a/src/en/lynxscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5e87a4ede4..0000000000 Binary files a/src/en/lynxscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lynxscans/res/web_hi_res_512.png b/src/en/lynxscans/res/web_hi_res_512.png deleted file mode 100644 index 65950d349a..0000000000 Binary files a/src/en/lynxscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScans.kt b/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScans.kt deleted file mode 100644 index a151a0a205..0000000000 --- a/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScans.kt +++ /dev/null @@ -1,130 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.lynxscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Request -import okhttp3.Response -import uy.kohesive.injekt.injectLazy - -class LynxScans : HttpSource() { - override val baseUrl: String = "https://lynxscans.com" - private val apiUrl: String = "https://api.lynxscans.com/api" - - override val lang: String = "en" - override val name: String = "LynxScans" - - override val versionId = 2 - - override val supportsLatest: Boolean = true - - private val json: Json by injectLazy() - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$apiUrl/comics?page=$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val data = json.decodeFromString<Popular>(response.body.string()) - - val titles = data.comics.data.map(PopularComicsData::toSManga) - - return MangasPage(titles, !data.comics.next_page_url.isNullOrEmpty()) - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiUrl/latest?page=$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val data = json.decodeFromString<Latest>(response.body.string()) - - val titles = data.chapters.data.distinctBy { it.comic_titleSlug }.map(LatestChaptersData::toSManga) - - return MangasPage(titles, !data.chapters.next_page_url.isNullOrEmpty()) - } - - // Search - override fun searchMangaParse(response: Response): MangasPage { - throw UnsupportedOperationException("Not used") - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - throw Exception("Search is not supported") - } - - // Details - override fun mangaDetailsParse(response: Response): SManga { - val data = json.decodeFromString<MangaDetails>(response.body.string()) - - return data.comic.toSManga() - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val titleId = manga.url.substringAfterLast("/") - - return GET("$apiUrl/comics/$titleId", headers) - } - - override fun getMangaUrl(manga: SManga): String { - val titleId = manga.url.substringAfterLast("/") - - return "$baseUrl/comics/$titleId" - } - - // Chapters - override fun chapterListRequest(manga: SManga): Request { - return mangaDetailsRequest(manga) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val data = json.decodeFromString<MangaDetails>(response.body.string()) - - val chapters: MutableList<SChapter> = mutableListOf() - - data.comic.volumes.forEach { volume -> - volume.chapters.forEach { chapter -> - chapters.add( - SChapter.create().apply { - url = "/comics/${data.comic.titleSlug}/volume/${volume.number}/chapter/${chapter.number}" - name = volume.name + " " + (if (!chapter.name.contains("chapter", true)) "Chapter ${chapter.number} " else "") + chapter.name - }, - ) - } - } - - return chapters - } - - override fun getChapterUrl(chapter: SChapter): String { - val chapterPath = chapter.url.substringAfter("/") - - return "$baseUrl/$chapterPath" - } - - // Page - override fun pageListRequest(chapter: SChapter): Request { - val chapterPath = chapter.url.substringAfter("/") - - return GET("$apiUrl/$chapterPath", headers) - } - - override fun pageListParse(response: Response): List<Page> { - val data = json.decodeFromString<PageList>(response.body.string()) - - return data.pages.mapIndexed { idx, it -> - Page(idx, imageUrl = it.thumb) - } - } - - // Unused - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScansDto.kt b/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScansDto.kt deleted file mode 100644 index 81780f5c56..0000000000 --- a/src/en/lynxscans/src/eu/kanade/tachiyomi/extension/en/lynxscans/LynxScansDto.kt +++ /dev/null @@ -1,110 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.lynxscans - -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.Serializable - -@Serializable -data class Latest( - val chapters: LatestChapters, -) - -@Serializable -data class LatestChapters( - val next_page_url: String?, - val data: List<LatestChaptersData>, -) - -@Serializable -data class LatestChaptersData( - val comic_title: String, - val comic_thumb: String, - val comic_titleSlug: String, -) { - fun toSManga(): SManga = SManga.create().apply { - title = this@LatestChaptersData.comic_title - thumbnail_url = this@LatestChaptersData.comic_thumb - url = "/comics/" + this@LatestChaptersData.comic_titleSlug - } -} - -@Serializable -data class Popular( - val comics: PopularComics, -) - -@Serializable -data class PopularComics( - val next_page_url: String?, - val data: List<PopularComicsData>, -) - -@Serializable -data class PopularComicsData( - val title: String, - val thumb: String, - val titleSlug: String, -) { - fun toSManga(): SManga = SManga.create().apply { - title = this@PopularComicsData.title - thumbnail_url = this@PopularComicsData.thumb - url = "/comics/" + this@PopularComicsData.titleSlug - } -} - -@Serializable -data class MangaDetails( - val comic: MangaDetailsComicData, -) - -@Serializable -data class MangaDetailsComicData( - val title: String, - val thumb: String, - val titleSlug: String, - val artist: String, - val author: String, - val description: String, - val tags: List<MangaDetailsTag>, - - val volumes: List<MangaDetailsVolume>, - -) { - fun toSManga(): SManga = SManga.create().apply { - title = this@MangaDetailsComicData.title - thumbnail_url = this@MangaDetailsComicData.thumb - url = "/comics/" + this@MangaDetailsComicData.titleSlug - author = if (this@MangaDetailsComicData.author != "blank") this@MangaDetailsComicData.author else null - artist = if (this@MangaDetailsComicData.artist != "blank") this@MangaDetailsComicData.artist else null - description = this@MangaDetailsComicData.description - genre = this@MangaDetailsComicData.tags.joinToString { it.name } - status = SManga.UNKNOWN - } -} - -@Serializable -data class MangaDetailsTag( - val name: String, -) - -@Serializable -data class MangaDetailsVolume( - val chapters: List<MangaDetailsChapter>, - val name: String, - val number: Int, -) - -@Serializable -data class MangaDetailsChapter( - val name: String, - val number: Int, -) - -@Serializable -data class PageList( - val pages: List<Page>, -) - -@Serializable -data class Page( - val thumb: String, -) diff --git a/src/en/madokami/AndroidManifest.xml b/src/en/madokami/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/madokami/AndroidManifest.xml +++ b/src/en/madokami/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/manga1s/AndroidManifest.xml b/src/en/manga1s/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/manga1s/AndroidManifest.xml +++ b/src/en/manga1s/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangademon/AndroidManifest.xml b/src/en/mangademon/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangademon/AndroidManifest.xml +++ b/src/en/mangademon/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangademon/CHANGELOG.md b/src/en/mangademon/CHANGELOG.md new file mode 100644 index 0000000000..30ef52390a --- /dev/null +++ b/src/en/mangademon/CHANGELOG.md @@ -0,0 +1,40 @@ +# 1.4.6 + +### Fix + +* Refresh URL suffix when required + +# 1.4.5 + +### Features + +* Dynamically update URL suffix + +# 1.4.4 + +### Features + +* Filters + +### Fix + +* Update base URL + +# 1.4.3 + +### Fix + +* Update selector + +# 1.4.2 + +### Fix + +* Fetch all chapter images +* Use GET on search, same as site + +# 1.4.1 + +### Features + +* First version diff --git a/src/en/mangademon/build.gradle b/src/en/mangademon/build.gradle index 36737d11c2..b72cd076c0 100644 --- a/src/en/mangademon/build.gradle +++ b/src/en/mangademon/build.gradle @@ -5,7 +5,8 @@ ext { extName = 'Manga Demon' pkgNameSuffix = 'en.mangademon' extClass = '.MangaDemon' - extVersionCode = 1 + extVersionCode = 7 + isNsfw = false } apply from: "$rootDir/common.gradle" diff --git a/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemon.kt b/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemon.kt index 52967697d5..7bc58ff3b1 100644 --- a/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemon.kt +++ b/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemon.kt @@ -1,17 +1,25 @@ package eu.kanade.tachiyomi.extension.en.mangademon import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers +import eu.kanade.tachiyomi.util.asJsoup import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import rx.Observable +import java.io.IOException +import java.net.URLEncoder import java.text.SimpleDateFormat import java.util.Locale @@ -20,10 +28,79 @@ class MangaDemon : ParsedHttpSource() { override val lang = "en" override val supportsLatest = true override val name = "Manga Demon" - override val baseUrl = "https://mangademon.org" + override val baseUrl = "https://manga-demon.org" - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("referrer", "origin") + override val client = network.cloudflareClient.newBuilder() + .rateLimit(1) + .addInterceptor { chain -> + val request = chain.request() + val headers = request.headers.newBuilder() + .removeAll("Accept-Encoding") + .build() + chain.proceed(request.newBuilder().headers(headers).build()) + } + .addInterceptor(::dynamicUrlInterceptor) + .build() + + // Cache suffix + private var dynamicUrlSuffix = "" + private var dynamicUrlSuffixUpdated: Long = 0 + private val dynamicUrlSuffixValidity: Long = 10 * 60 // 10 minutes + + private fun dynamicUrlInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val timeNow = System.currentTimeMillis() / 1000 + + // Check if request requires an up-to-date suffix + if (request.url.pathSegments[0] == "manga") { + // Force update suffix if required + if (timeNow - dynamicUrlSuffixUpdated > dynamicUrlSuffixValidity) { + client.newCall(GET(baseUrl)).execute() + if (timeNow - dynamicUrlSuffixUpdated > dynamicUrlSuffixValidity) { + throw IOException("Failed to update dynamic url suffix") + } + } + + val newPath = request.url + .encodedPath + .replaceAfterLast("-", dynamicUrlSuffix) + + val newUrl = request.url.newBuilder() + .encodedPath(newPath) + .build() + + val newRequest = request.newBuilder() + .url(newUrl) + .build() + + return chain.proceed(newRequest) + } + + // Always update suffix + val response = chain.proceed(request) + val document = Jsoup.parse( + response.peekBody(Long.MAX_VALUE).string(), + request.url.toString(), + ) + + val links = document.select("a[href^='/manga/']") + + // Get the most popular suffix after last `-` + val suffix = links.map { it.attr("href").substringAfterLast("-") } + .groupBy { it } + .maxByOrNull { it.value.size } + ?.key + + if (suffix != null) { + dynamicUrlSuffix = suffix + dynamicUrlSuffixUpdated = timeNow + } + + return response + } + + override fun headersBuilder() = super.headersBuilder() + .add("Referer", baseUrl) // latest override fun latestUpdatesRequest(page: Int): Request { @@ -34,14 +111,13 @@ class MangaDemon : ParsedHttpSource() { override fun latestUpdatesSelector() = "div.leftside" - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a").apply() { - title = attr("title").dropLast(4) - url = attr("href") - } - thumbnail_url = element.select("img").attr("abs:src") + override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { + element.select("a").apply { + title = attr("title") + val url = URLEncoder.encode(attr("href"), "UTF-8") + setUrlWithoutDomain(url) } + thumbnail_url = element.select("img").attr("abs:src") } // Popular @@ -56,27 +132,62 @@ class MangaDemon : ParsedHttpSource() { override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) // Search + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return if (query.isNotEmpty()) { + super.fetchSearchManga(page, query, filters) + } else { + client.newCall(filterSearchRequest(page, filters)) + .asObservableSuccess() + .map(::filterSearchParse) + } + } + + private fun filterSearchRequest(page: Int, filters: FilterList): Request { + val url = "$baseUrl/browse.php".toHttpUrl().newBuilder().apply { + addQueryParameter("list", page.toString()) + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + filter.checked.forEach { genre -> + addQueryParameter("genre[]", genre) + } + } + is StatusFilter -> { + addQueryParameter("status", filter.selected) + } + is SortFilter -> { + addQueryParameter("orderby", filter.selected) + } + else -> {} + } + } + }.build() + + return GET(url, headers) + } + + private fun filterSearchParse(response: Response) = popularMangaParse(response) + + override fun getFilterList() = getFilters() + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = "$baseUrl/search.php".toHttpUrl().newBuilder() .addQueryParameter("manga", query) .build() - return POST("$url", headers) + return GET(url, headers) } override fun searchMangaSelector() = "a.boxsizing" - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a").first().let { - title = element.select("li.boxsizing").text() - url = (element.attr("href")) - val urlsorter = title.replace(":", "%20") - thumbnail_url = ("https://readermc.org/images/thumbnails/$urlsorter.webp") - } - } + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + title = element.text() + val url = URLEncoder.encode(element.attr("href"), "UTF-8") + setUrlWithoutDomain(url) + val urlSorter = title.replace(":", "%20") + thumbnail_url = ("https://readermc.org/images/thumbnails/$urlSorter.webp") } - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() + override fun searchMangaNextPageSelector() = null // Manga details override fun mangaDetailsParse(document: Document): SManga { @@ -105,7 +216,8 @@ class MangaDemon : ParsedHttpSource() { override fun chapterFromElement(element: Element): SChapter { return SChapter.create().apply { element.select("a").let { urlElement -> - url = (urlElement.attr("href")) + val url = URLEncoder.encode(urlElement.attr("href"), "UTF-8") + setUrlWithoutDomain(url) name = element.select("strong.chapter-title").text() } val date = element.select("time.chapter-update").text() @@ -122,11 +234,43 @@ class MangaDemon : ParsedHttpSource() { private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } + + private val loadMoreEndpointRegex by lazy { Regex("""GET[^/]+([^=]+)""") } } override fun pageListParse(document: Document): List<Page> { - return document.select("img.imgholder") - .mapIndexed { i, el -> Page(i, "", el.attr("src")) } + val baseImages = document.select("img.imgholder") + .map { it.attr("abs:src") } + .toMutableList() + + baseImages.addAll(loadMoreImages(document)) + + return baseImages.mapIndexed { i, img -> Page(i, "", img) } + } + + private fun loadMoreImages(document: Document): List<String> { + val buttonHtml = document.selectFirst("img.imgholder ~ button") + ?.attr("onclick")?.replace("\"", "\'") + ?: return emptyList() + + val id = buttonHtml.substringAfter("\'").substringBefore("\'").trim() + val funcName = buttonHtml.substringBefore("(").trim() + + val endpoint = document.selectFirst("script:containsData($funcName)") + ?.data() + ?.let { loadMoreEndpointRegex.find(it)?.groupValues?.get(1) } + ?: return emptyList() + + val response = client.newCall(GET("$baseUrl$endpoint=$id", headers)).execute() + + if (!response.isSuccessful) { + response.close() + return emptyList() + } + + return response.use { it.asJsoup() } + .select("img") + .map { it.attr("abs:src") } } override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") diff --git a/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemonFilters.kt b/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemonFilters.kt new file mode 100644 index 0000000000..62a88395f2 --- /dev/null +++ b/src/en/mangademon/src/eu/kanade/tachiyomi/extension/en/mangademon/MangaDemonFilters.kt @@ -0,0 +1,93 @@ +package eu.kanade.tachiyomi.extension.en.mangademon + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList + +abstract class SelectFilter( + name: String, + private val options: List<Pair<String, String>>, +) : Filter.Select<String>( + name, + options.map { it.first }.toTypedArray(), +) { + val selected get() = options[state].second +} + +class StatusFilter : SelectFilter("Status", status) { + companion object { + private val status = listOf( + Pair("All", "all"), + Pair("Ongoing", "ongoing"), + Pair("Completed", "completed"), + ) + } +} + +class SortFilter : SelectFilter("Sort", sort) { + companion object { + private val sort = listOf( + Pair("Top Views", "VIEWS DESC"), + Pair("A To Z", "NAME ASC"), + ) + } +} + +class CheckBoxFilter( + name: String, + val value: String, +) : Filter.CheckBox(name) + +class GenreFilter : Filter.Group<CheckBoxFilter>( + "Genre", + genres.map { CheckBoxFilter(it.first, it.second) }, +) { + val checked get() = state.filter { it.state }.map { it.value } + + companion object { + private val genres = listOf( + Pair("All", "all"), + Pair("Action", "1"), + Pair("Adventure", "2"), + Pair("Comedy", "3"), + Pair("Cooking", "34"), + Pair("Doujinshi", "25"), + Pair("Drama", "4"), + Pair("Ecchi", "19"), + Pair("Fantasy", "5"), + Pair("Gender Bender", "30"), + Pair("Harem", "10"), + Pair("Historical", "28"), + Pair("Horror", "8"), + Pair("Isekai", "33"), + Pair("Josei", "31"), + Pair("Martial Arts", "6"), + Pair("Mature", "22"), + Pair("Mecha", "32"), + Pair("Mystery", "15"), + Pair("One Shot", "26"), + Pair("Psychological", "11"), + Pair("Romance", "12"), + Pair("School Life", "13"), + Pair("Sci-fi", "16"), + Pair("Seinen", "17"), + Pair("Shoujo", "14"), + Pair("Shoujo Ai", "23"), + Pair("Shounen", "7"), + Pair("Shounen Ai", "29"), + Pair("Slice of Life", "21"), + Pair("Smut", "27"), + Pair("Sports", "20"), + Pair("Supernatural", "9"), + Pair("Tragedy", "18"), + Pair("Webtoons", "24"), + ) + } +} + +fun getFilters() = FilterList( + Filter.Header("Ignored when using text search"), + Filter.Separator(), + SortFilter(), + StatusFilter(), + GenreFilter(), +) diff --git a/src/en/mangadoom/AndroidManifest.xml b/src/en/mangadoom/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangadoom/AndroidManifest.xml +++ b/src/en/mangadoom/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangafox/AndroidManifest.xml b/src/en/mangafox/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/mangafox/AndroidManifest.xml +++ b/src/en/mangafox/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/mangafreak/AndroidManifest.xml b/src/en/mangafreak/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangafreak/AndroidManifest.xml +++ b/src/en/mangafreak/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangago/AndroidManifest.xml b/src/en/mangago/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangago/AndroidManifest.xml +++ b/src/en/mangago/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangago/build.gradle b/src/en/mangago/build.gradle index 01b6e02b9d..b6beb0dda8 100644 --- a/src/en/mangago/build.gradle +++ b/src/en/mangago/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Mangago' pkgNameSuffix = 'en.mangago' extClass = '.Mangago' - extVersionCode = 12 + extVersionCode = 13 isNsfw = true } @@ -13,4 +13,4 @@ apply from: "$rootDir/common.gradle" dependencies { implementation(project(':lib-cryptoaes')) -} \ No newline at end of file +} diff --git a/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt b/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt index 8dd753fb83..71d2afb932 100644 --- a/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt +++ b/src/en/mangago/src/eu/kanade/tachiyomi/extension/en/mangago/Mangago.kt @@ -46,10 +46,11 @@ class Mangago : ParsedHttpSource() { .addInterceptor { chain -> val response = chain.proceed(chain.request()) - val key = - response.request.url.queryParameter("desckey") ?: return@addInterceptor response - val cols = response.request.url.queryParameter("cols")?.toIntOrNull() - ?: return@addInterceptor response + val fragment = response.request.url.fragment ?: return@addInterceptor response + + // desckey=...&cols=... + val key = fragment.substringAfter("desckey=").substringBefore("&") + val cols = fragment.substringAfter("&cols=").toIntOrNull() ?: return@addInterceptor response val image = unscrambleImage(response.body.byteStream(), key, cols) val body = image.toResponseBody("image/jpeg".toMediaTypeOrNull()) @@ -172,13 +173,14 @@ class Mangago : ParsedHttpSource() { override fun chapterListSelector() = "table#chapter_table > tbody > tr, table.uk-table > tbody > tr" override fun chapterFromElement(element: Element) = SChapter.create().apply { - val link = element.getElementsByTag("a") + val link = element.select("a.chico") setUrlWithoutDomain(link.attr("href")) name = link.text().trim() date_upload = runCatching { dateFormat.parse(element.select("td:last-child").text().trim())?.time }.getOrNull() ?: 0L + scanlator = element.selectFirst("td.no a, td.uk-table-shrink a")?.text()?.trim() } override fun pageListParse(document: Document): List<Page> { @@ -231,7 +233,7 @@ class Mangago : ParsedHttpSource() { .split(",") .mapIndexed { idx, it -> val url = if (it.contains("cspiclink")) { - "$it?desckey=${getDescramblingKey(deobfChapterJs, it)}&cols=$cols" + "$it#desckey=${getDescramblingKey(deobfChapterJs, it)}&cols=$cols" } else { it } diff --git a/src/en/mangahasu/AndroidManifest.xml b/src/en/mangahasu/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangahasu/AndroidManifest.xml +++ b/src/en/mangahasu/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangahere/AndroidManifest.xml b/src/en/mangahere/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangahere/AndroidManifest.xml +++ b/src/en/mangahere/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangajar/AndroidManifest.xml b/src/en/mangajar/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangajar/AndroidManifest.xml +++ b/src/en/mangajar/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangakatana/AndroidManifest.xml b/src/en/mangakatana/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangakatana/AndroidManifest.xml +++ b/src/en/mangakatana/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangakatana/build.gradle b/src/en/mangakatana/build.gradle index 787785bf67..c88175c334 100644 --- a/src/en/mangakatana/build.gradle +++ b/src/en/mangakatana/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MangaKatana' pkgNameSuffix = 'en.mangakatana' extClass = '.MangaKatana' - extVersionCode = 9 + extVersionCode = 10 } apply from: "$rootDir/common.gradle" diff --git a/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt b/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt index bf834fcc5f..ba59635c93 100644 --- a/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt +++ b/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt @@ -264,6 +264,7 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() { Genre("doujinshi", "Doujinshi"), Genre("drama", "Drama"), Genre("ecchi", "Ecchi"), + Genre("erotica", "Erotica"), Genre("fantasy", "Fantasy"), Genre("gender-bender", "Gender Bender"), Genre("gore", "Gore"), @@ -295,7 +296,6 @@ class MangaKatana : ConfigurableSource, ParsedHttpSource() { Genre("shounen", "Shounen"), Genre("shounen-ai", "Shounen Ai"), Genre("slice-of-life", "Slice of Life"), - Genre("smut", "Smut"), Genre("sports", "Sports"), Genre("super-power", "Super power"), Genre("supernatural", "Supernatural"), diff --git a/src/en/mangamiso/AndroidManifest.xml b/src/en/mangamiso/AndroidManifest.xml index e0616d4fc7..6e274ca355 100644 --- a/src/en/mangamiso/AndroidManifest.xml +++ b/src/en/mangamiso/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/mangapill/AndroidManifest.xml b/src/en/mangapill/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangapill/AndroidManifest.xml +++ b/src/en/mangapill/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt b/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt index 9414c4e86d..c1e8fcda21 100644 --- a/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt +++ b/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt @@ -22,7 +22,7 @@ class MangaPill : ParsedHttpSource() { override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/") - + override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) override fun latestUpdatesRequest(page: Int): Request { diff --git a/src/en/mangaplex/AndroidManifest.xml b/src/en/mangaplex/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangaplex/AndroidManifest.xml +++ b/src/en/mangaplex/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangarawclub/AndroidManifest.xml b/src/en/mangarawclub/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangarawclub/AndroidManifest.xml +++ b/src/en/mangarawclub/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangarawclub/build.gradle b/src/en/mangarawclub/build.gradle index 358fe3ad13..51bb1837da 100644 --- a/src/en/mangarawclub/build.gradle +++ b/src/en/mangarawclub/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MangaGeko' pkgNameSuffix = 'en.mangarawclub' extClass = '.MangaRawClub' - extVersionCode = 18 + extVersionCode = 20 isNsfw = true } diff --git a/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt index 3809036032..dc30b06400 100644 --- a/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt +++ b/src/en/mangarawclub/src/eu/kanade/tachiyomi/extension/en/mangarawclub/MangaRawClub.kt @@ -46,7 +46,7 @@ class MangaRawClub : ParsedHttpSource() { override fun searchMangaSelector() = "ul.novel-list > li.novel-item" override fun popularMangaSelector() = searchMangaSelector() - override fun latestUpdatesSelector() = searchMangaSelector() + override fun latestUpdatesSelector() = "ul.novel-list.chapters > li.novel-item" override fun searchMangaFromElement(element: Element): SManga { val manga = SManga.create() diff --git a/src/en/mangasail/AndroidManifest.xml b/src/en/mangasail/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangasail/AndroidManifest.xml +++ b/src/en/mangasail/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/mangatown/AndroidManifest.xml b/src/en/mangatown/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/mangatown/AndroidManifest.xml +++ b/src/en/mangatown/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/manta/AndroidManifest.xml b/src/en/manta/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/manta/AndroidManifest.xml +++ b/src/en/manta/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/megatokyo/AndroidManifest.xml b/src/en/megatokyo/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/megatokyo/AndroidManifest.xml +++ b/src/en/megatokyo/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/multporn/AndroidManifest.xml b/src/en/multporn/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/multporn/AndroidManifest.xml +++ b/src/en/multporn/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/myhentaicomics/AndroidManifest.xml b/src/en/myhentaicomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/myhentaicomics/AndroidManifest.xml +++ b/src/en/myhentaicomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/myhentaigallery/AndroidManifest.xml b/src/en/myhentaigallery/AndroidManifest.xml index 47c9f73022..ab175f15ef 100644 --- a/src/en/myhentaigallery/AndroidManifest.xml +++ b/src/en/myhentaigallery/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/nineanime/AndroidManifest.xml b/src/en/nineanime/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/nineanime/AndroidManifest.xml +++ b/src/en/nineanime/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle index 608f1014a6..94bcbe45c3 100644 --- a/src/en/nineanime/build.gradle +++ b/src/en/nineanime/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'NineAnime' pkgNameSuffix = 'en.nineanime' extClass = '.NineAnime' - extVersionCode = 3 + extVersionCode = 4 } apply from: "$rootDir/common.gradle" diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt index a9e6e89dae..ba431b0a38 100644 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt +++ b/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request @@ -31,6 +32,10 @@ class NineAnime : ParsedHttpSource() { .followRedirects(true) .build() + companion object { + private const val PAGES_URL = "https://www.glanceoflife.com" + } + // not necessary for normal usage but added in an attempt to fix usage with VPN (see #3476) override fun headersBuilder(): Headers.Builder = Headers.Builder() .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/77") @@ -118,6 +123,10 @@ class NineAnime : ParsedHttpSource() { return GET(baseUrl + "${manga.url}?waring=1", headers) } + override fun getChapterUrl(chapter: SChapter): String { + return baseUrl + chapter.url + } + override fun chapterListSelector() = "ul.detail-chlist li" override fun chapterFromElement(element: Element): SChapter { @@ -151,12 +160,21 @@ class NineAnime : ParsedHttpSource() { // Pages override fun pageListRequest(chapter: SChapter): Request { + val id: String = chapter.url.substring(chapter.url.lastIndexOf("/", chapter.url.length - 2)) + val pageListHeaders = headersBuilder().add("Referer", "$baseUrl/manga/").build() - return GET(baseUrl + chapter.url, pageListHeaders) + return GET("$PAGES_URL/c/nineanime$id", pageListHeaders) } override fun pageListParse(document: Document): List<Page> { - val script = document.select("script:containsData(all_imgs_url)").firstOrNull()?.data() + val pageListHeaders = headersBuilder().add("Referer", "$baseUrl/manga/").build() + + val scripturl = document.select("script").firstOrNull()?.data() + + val link = scripturl?.split("\"")?.get(1) + val pages = client.newCall(GET(PAGES_URL + link, pageListHeaders)).execute().asJsoup() + + val script = pages.select("script:containsData(all_imgs_url)").firstOrNull()?.data() ?: throw Exception("all_imgsurl not found") return Regex(""""(http.*)",""").findAll(script).mapIndexed { i, mr -> Page(i, "", mr.groupValues[1]) diff --git a/src/en/ninehentai/AndroidManifest.xml b/src/en/ninehentai/AndroidManifest.xml index caf8fce1c6..3c7172aa63 100644 --- a/src/en/ninehentai/AndroidManifest.xml +++ b/src/en/ninehentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/nuxscans/AndroidManifest.xml b/src/en/nuxscans/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/nuxscans/AndroidManifest.xml +++ b/src/en/nuxscans/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/oglaf/AndroidManifest.xml b/src/en/oglaf/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/oglaf/AndroidManifest.xml +++ b/src/en/oglaf/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/oots/AndroidManifest.xml b/src/en/oots/AndroidManifest.xml index 0d7e33d584..cd55ad9174 100644 --- a/src/en/oots/AndroidManifest.xml +++ b/src/en/oots/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/oppaistream/AndroidManifest.xml b/src/en/oppaistream/AndroidManifest.xml index 26a0a07c96..b6d4c3dd2d 100644 --- a/src/en/oppaistream/AndroidManifest.xml +++ b/src/en/oppaistream/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/patchfriday/AndroidManifest.xml b/src/en/patchfriday/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/patchfriday/AndroidManifest.xml +++ b/src/en/patchfriday/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/purplecress/AndroidManifest.xml b/src/en/purplecress/AndroidManifest.xml index 30310f4f7e..3fa2c02d5d 100644 --- a/src/en/purplecress/AndroidManifest.xml +++ b/src/en/purplecress/AndroidManifest.xml @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" - xmlns:android="http://schemas.android.com/apk/res/android"> - +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".en.purplecress.PurpleCressURLActivity" @@ -34,4 +32,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/en/pururin/AndroidManifest.xml b/src/en/pururin/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/pururin/AndroidManifest.xml +++ b/src/en/pururin/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/questionablecontent/AndroidManifest.xml b/src/en/questionablecontent/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/questionablecontent/AndroidManifest.xml +++ b/src/en/questionablecontent/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt b/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt index f34524ff94..e174ca68c2 100644 --- a/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt +++ b/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.internal.http.HTTP_UNAUTHORIZED import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable @@ -113,7 +112,6 @@ class QuestionableContent : ParsedHttpSource(), ConfigurableSource { title = "Show author's notes" summary = "Enable to see the author's notes at the end of chapters (if they're there)." setDefaultValue(false) - } screen.addPreference(authorsNotesPref) } diff --git a/src/en/rainofsnow/AndroidManifest.xml b/src/en/rainofsnow/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/rainofsnow/AndroidManifest.xml +++ b/src/en/rainofsnow/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/randowiz/AndroidManifest.xml b/src/en/randowiz/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/randowiz/AndroidManifest.xml +++ b/src/en/randowiz/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/rawmanga/AndroidManifest.xml b/src/en/rawmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/rawmanga/AndroidManifest.xml +++ b/src/en/rawmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/readcomiconline/AndroidManifest.xml b/src/en/readcomiconline/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/readcomiconline/AndroidManifest.xml +++ b/src/en/readcomiconline/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/readm/AndroidManifest.xml b/src/en/readm/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/readm/AndroidManifest.xml +++ b/src/en/readm/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/readm/build.gradle b/src/en/readm/build.gradle index cc15ba2cb4..439a767c98 100644 --- a/src/en/readm/build.gradle +++ b/src/en/readm/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'ReadM' pkgNameSuffix = 'en.readm' extClass = '.ReadM' - extVersionCode = 6 + extVersionCode = 7 } apply from: "$rootDir/common.gradle" diff --git a/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt b/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt index 1523598a81..c7f1ad045a 100644 --- a/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt +++ b/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt @@ -43,7 +43,7 @@ class ReadM : ParsedHttpSource() { override fun popularMangaNextPageSelector(): String = "div.pagination a:contains(»)" override fun popularMangaSelector(): String = "div#discover-response li" override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") + thumbnail_url = element.selectFirst("img")!!.imgAttr() element.select("div.subject-title a").first()!!.apply { title = this.text().trim() url = this.attr("href") @@ -53,10 +53,10 @@ class ReadM : ParsedHttpSource() { // Latest override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-releases/$page", headers) - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() + override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() override fun latestUpdatesSelector(): String = "ul.latest-updates > li" override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:data-src") + thumbnail_url = element.selectFirst("img")!!.imgAttr() element.select("h2 a").first()!!.apply { title = this.text().trim() url = this.attr("href") @@ -93,7 +93,7 @@ class ReadM : ParsedHttpSource() { // Details override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("img.series-profile-thumb").attr("abs:src") + thumbnail_url = document.selectFirst("img.series-profile-thumb")!!.imgAttr() title = document.select("h1.page-title").text().trim() author = document.select("span#first_episode a").text().trim() artist = document.select("span#last_episode a").text().trim() @@ -156,6 +156,13 @@ class ReadM : ParsedHttpSource() { override fun imageUrlParse(document: Document): String = throw Exception("Not Used") override fun pageListParse(document: Document): List<Page> = document.select("div.ch-images img").mapIndexed { index, element -> - Page(index, "", element.attr("abs:src")) + Page(index, "", element.imgAttr()) + } + + private fun Element.imgAttr(): String { + return when { + this.hasAttr("data-src") -> this.attr("abs:data-src") + else -> this.attr("abs:src") + } } } diff --git a/src/en/readmangatoday/AndroidManifest.xml b/src/en/readmangatoday/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/readmangatoday/AndroidManifest.xml +++ b/src/en/readmangatoday/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/reallifecomics/AndroidManifest.xml b/src/en/reallifecomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/reallifecomics/AndroidManifest.xml +++ b/src/en/reallifecomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/rmanga/AndroidManifest.xml b/src/en/rmanga/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/rmanga/AndroidManifest.xml +++ b/src/en/rmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/schlockmercenary/AndroidManifest.xml b/src/en/schlockmercenary/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/schlockmercenary/AndroidManifest.xml +++ b/src/en/schlockmercenary/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/silentmangaaudition/AndroidManifest.xml b/src/en/silentmangaaudition/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/silentmangaaudition/AndroidManifest.xml +++ b/src/en/silentmangaaudition/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/solarandsundry/AndroidManifest.xml b/src/en/solarandsundry/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/solarandsundry/AndroidManifest.xml +++ b/src/en/solarandsundry/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/soushiyofamiliar/AndroidManifest.xml b/src/en/soushiyofamiliar/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/en/soushiyofamiliar/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/soushiyofamiliar/build.gradle b/src/en/soushiyofamiliar/build.gradle deleted file mode 100644 index b7d74c4cc2..0000000000 --- a/src/en/soushiyofamiliar/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Familiar by Soushiyo' - pkgNameSuffix = 'en.soushiyofamiliar' - extClass = '.SoushiyoFamiliar' - extVersionCode = 1 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/soushiyofamiliar/res/mipmap-hdpi/ic_launcher.png b/src/en/soushiyofamiliar/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b9ae36fd8a..0000000000 Binary files a/src/en/soushiyofamiliar/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/res/mipmap-mdpi/ic_launcher.png b/src/en/soushiyofamiliar/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b52fe269ad..0000000000 Binary files a/src/en/soushiyofamiliar/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/res/mipmap-xhdpi/ic_launcher.png b/src/en/soushiyofamiliar/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9bc22e4f5a..0000000000 Binary files a/src/en/soushiyofamiliar/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/res/mipmap-xxhdpi/ic_launcher.png b/src/en/soushiyofamiliar/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8cdfff9fbb..0000000000 Binary files a/src/en/soushiyofamiliar/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/soushiyofamiliar/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d5f771f0a..0000000000 Binary files a/src/en/soushiyofamiliar/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/res/web_hi_res_512.png b/src/en/soushiyofamiliar/res/web_hi_res_512.png deleted file mode 100644 index 5c945809c7..0000000000 Binary files a/src/en/soushiyofamiliar/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/soushiyofamiliar/src/eu/kanade/tachiyomi/extension/en/soushiyofamiliar/SoushiyoFamiliar.kt b/src/en/soushiyofamiliar/src/eu/kanade/tachiyomi/extension/en/soushiyofamiliar/SoushiyoFamiliar.kt deleted file mode 100644 index 23eaeb9708..0000000000 --- a/src/en/soushiyofamiliar/src/eu/kanade/tachiyomi/extension/en/soushiyofamiliar/SoushiyoFamiliar.kt +++ /dev/null @@ -1,135 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.soushiyofamiliar - -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -class SoushiyoFamiliar : ParsedHttpSource() { - - override val name = "Soushiyo: Familiar" - - override val baseUrl = "https://familiar.soushiyo.com" - - override val lang = "en" - - override val supportsLatest = false - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create().apply { - title = "Familiar" - artist = "Soushiyo" - author = "Soushiyo" - status = SManga.ONGOING - url = "/familiar-chapter-list/" - description = "a witch. a cat. a contract.\n\nWhen career-driven editor Diana Vallejo accidentally summons a familiar whose specialty is soft domination, her life takes a turn for the better – but for how long?\n\nFamiliar is a modern-day, slice-of-life romcomic about magick, work/life balance, BDSM, and relationships. It is kinky, queer, sex-positive, and free to read online. It is also erotic, sexually explicit, and written for adult audiences only." - thumbnail_url = "https://i.redd.it/wbe6ewkjtz291.jpg" - } - - return Observable.just(MangasPage(arrayListOf(manga), false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val chapterList = super.chapterListParse(response).distinct() - val chapterListNew = mutableListOf<SChapter>() - - for (i in chapterList.indices) { - /* Some chapters are already listed on the site but are not yet out (have no link) --> Skip them */ - if (chapterList[i].url.isNotEmpty()) { - /* Add chapter to front of new list --> Reverse order so that newest chapter is first */ - chapterListNew.add(0, chapterList[i]) - } - } - - return chapterListNew - } - - override fun chapterListSelector() = "tbody > tr[class^='row-']" - - override fun chapterFromElement(element: Element): SChapter { - val textAct = element.select(".column-1").text() - val textChapNum = element.select(".column-2").text() - val textChapName = element.select(".column-3").text() - var textDate = "" - - /* The date is in column 6 after a globe symbol --> Find/select the symbol */ - val dateSymbolElements = element.select(".column-6 > .fa-globe") - /* Unreleased entries don't have a symbol and date */ - if (dateSymbolElements.size != 0) { - /* Get the text after the symbol (if there are more occurrences then take the last, so the latest date is used in the app) */ - textDate = dateSymbolElements.last()!!.nextSibling().toString() - /* Filter out any characters that do not belong to the valid date format "M/d/yy" */ - textDate = textDate.replace("[^0-9/]".toRegex(), "") - } - - val chapter = SChapter.create() - chapter.url = element.select(".column-3 > a").attr("href").substringAfter(baseUrl) // This is empty if there is no link (e.g. for unreleased chapters) --> This is then handled in chapterListParse - chapter.name = "Act $textAct - Chapter $textChapNum: $textChapName" - chapter.date_upload = parseDate(textDate) - - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - val elements = document.select(".esg-entry-media > img") - for (i in 0 until elements.size) { - pages.add(Page(pages.size, "", elements[i].attr("abs:src"))) - } - - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") - - override fun popularMangaSelector(): String = throw UnsupportedOperationException("Not used.") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used.") - - override fun searchMangaNextPageSelector(): String = throw UnsupportedOperationException("Not used.") - - override fun searchMangaSelector(): String = throw UnsupportedOperationException("Not used.") - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used.") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used.") - - override fun popularMangaNextPageSelector(): String = throw UnsupportedOperationException("Not used.") - - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used.") - - override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used.") - - private fun parseDate(dateStr: String): Long { - return runCatching { DATE_FORMATTER.parse(dateStr)?.time } - .getOrNull() ?: 0L - } - - companion object { - private val DATE_FORMATTER by lazy { - SimpleDateFormat("M/d/yy", Locale.ENGLISH) - } - } -} diff --git a/src/en/swordscomic/AndroidManifest.xml b/src/en/swordscomic/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/swordscomic/AndroidManifest.xml +++ b/src/en/swordscomic/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/tapastic/AndroidManifest.xml b/src/en/tapastic/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/tapastic/AndroidManifest.xml +++ b/src/en/tapastic/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/tapastic/build.gradle b/src/en/tapastic/build.gradle index f2a387f54a..20f8c36925 100644 --- a/src/en/tapastic/build.gradle +++ b/src/en/tapastic/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Tapas' pkgNameSuffix = 'en.tapastic' extClass = '.Tapastic' - extVersionCode = 19 + extVersionCode = 20 isNsfw = true } diff --git a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt b/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt index fc0d826124..dcf39e8564 100644 --- a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt +++ b/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt @@ -155,7 +155,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { // Popular override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/comics?b=POPULAR&g=0&f=NONE&pageNumber=$page&pageSize=20&") + GET("$baseUrl/comics?b=POPULAR&g=0&f=NONE&pageNumber=$page&pageSize=20&", headers) override fun popularMangaNextPageSelector() = "div[data-has-next=true]" override fun popularMangaSelector() = "li.js-list-item" @@ -199,7 +199,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { // Latest override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/comics?b=FRESH&g=0&f=NONE&pageNumber=$page&pageSize=20&") + GET("$baseUrl/comics?b=FRESH&g=0&f=NONE&pageNumber=$page&pageSize=20&", headers) override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() override fun latestUpdatesSelector(): String = popularMangaSelector() @@ -244,7 +244,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { } // Append page number url.addQueryParameter("pageNumber", page.toString()) - return GET(url.toString()) + return GET(url.toString(), headers) } override fun searchMangaNextPageSelector() = diff --git a/src/en/tcbscans/AndroidManifest.xml b/src/en/tcbscans/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/tcbscans/AndroidManifest.xml +++ b/src/en/tcbscans/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/theduckwebcomics/AndroidManifest.xml b/src/en/theduckwebcomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/theduckwebcomics/AndroidManifest.xml +++ b/src/en/theduckwebcomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/thepropertyofhate/AndroidManifest.xml b/src/en/thepropertyofhate/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/thepropertyofhate/AndroidManifest.xml +++ b/src/en/thepropertyofhate/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/tsumino/AndroidManifest.xml b/src/en/tsumino/AndroidManifest.xml index 74061a8571..5c0ee55538 100644 --- a/src/en/tsumino/AndroidManifest.xml +++ b/src/en/tsumino/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/vgperson/AndroidManifest.xml b/src/en/vgperson/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/vgperson/AndroidManifest.xml +++ b/src/en/vgperson/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/vizshonenjump/AndroidManifest.xml b/src/en/vizshonenjump/AndroidManifest.xml index 604e24eba3..c0cbab96e0 100644 --- a/src/en/vizshonenjump/AndroidManifest.xml +++ b/src/en/vizshonenjump/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/en/vizshonenjump/build.gradle b/src/en/vizshonenjump/build.gradle index efb96094a2..eb5bb9609d 100644 --- a/src/en/vizshonenjump/build.gradle +++ b/src/en/vizshonenjump/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'VIZ' pkgNameSuffix = 'en.vizshonenjump' extClass = '.VizFactory' - extVersionCode = 19 + extVersionCode = 20 } dependencies { diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/Viz.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/Viz.kt index cc5a87b1d8..23a5c12f76 100644 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/Viz.kt +++ b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/Viz.kt @@ -40,6 +40,7 @@ open class Viz( override val supportsLatest = true override val client: OkHttpClient = network.client.newBuilder() + .addInterceptor(::headersIntercept) .addInterceptor(::authCheckIntercept) .addInterceptor(::authChapterCheckIntercept) .addInterceptor(VizImageInterceptor()) @@ -293,6 +294,14 @@ open class Viz( loginCheckResponse.close() } + private fun headersIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val headers = request.headers.newBuilder() + .removeAll("Accept-Encoding") + .build() + return chain.proceed(request.newBuilder().headers(headers).build()) + } + private fun authCheckIntercept(chain: Interceptor.Chain): Response { if (loggedIn == null) { checkIfIsLoggedIn(chain) diff --git a/src/en/voyceme/AndroidManifest.xml b/src/en/voyceme/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/voyceme/AndroidManifest.xml +++ b/src/en/voyceme/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/voyceme/build.gradle b/src/en/voyceme/build.gradle index b75a7a4804..9743e42cbd 100644 --- a/src/en/voyceme/build.gradle +++ b/src/en/voyceme/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Voyce.Me' pkgNameSuffix = 'en.voyceme' extClass = '.VoyceMe' - extVersionCode = 2 + extVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMe.kt b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMe.kt index 46b705a9b0..0e5a30e2b7 100644 --- a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMe.kt +++ b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMe.kt @@ -2,41 +2,32 @@ package eu.kanade.tachiyomi.extension.en.voyceme import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.parser.Parser -import rx.Observable import uy.kohesive.injekt.injectLazy -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale import java.util.concurrent.TimeUnit class VoyceMe : HttpSource() { - override val name = "Voyce.Me" + // Renamed from "Voyce.Me" to "VoyceMe" as the site uses. + override val id = 4815322300278778429 + + override val name = "VoyceMe" override val baseUrl = "http://voyce.me" @@ -45,7 +36,8 @@ class VoyceMe : HttpSource() { override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(2, 1, TimeUnit.SECONDS) + .rateLimitHost(GRAPHQL_URL.toHttpUrl(), 1, 1, TimeUnit.SECONDS) + .rateLimitHost(STATIC_URL.toHttpUrl(), 2, 1, TimeUnit.SECONDS) .build() private val json: Json by injectLazy() @@ -55,23 +47,16 @@ class VoyceMe : HttpSource() { .add("Origin", baseUrl) .add("Referer", "$baseUrl/") - private fun genericComicBookFromObject(comic: VoyceMeComic): SManga = - SManga.create().apply { - title = comic.title - url = "/series/${comic.slug}" - thumbnail_url = STATIC_URL + comic.thumbnail - } - override fun popularMangaRequest(page: Int): Request { - val payload = buildJsonObject { - put("query", POPULAR_QUERY) - putJsonObject("variables") { - put("offset", (page - 1) * POPULAR_PER_PAGE) - put("limit", POPULAR_PER_PAGE) - } - } + val payload = GraphQlQuery( + query = POPULAR_QUERY, + variables = PopularQueryVariables( + offset = (page - 1) * POPULAR_PER_PAGE, + limit = POPULAR_PER_PAGE, + ), + ) - val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", body.contentLength().toString()) @@ -82,26 +67,24 @@ class VoyceMe : HttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - val result = json.parseToJsonElement(response.body.string()).jsonObject + val comicList = response.parseAs<VoyceMeSeriesResponse>() + .data.series.map(VoyceMeComic::toSManga) - val comicList = result["data"]!!.jsonObject["voyce_series"]!! - .let { json.decodeFromJsonElement<List<VoyceMeComic>>(it) } - .map(::genericComicBookFromObject) val hasNextPage = comicList.size == POPULAR_PER_PAGE return MangasPage(comicList, hasNextPage) } override fun latestUpdatesRequest(page: Int): Request { - val payload = buildJsonObject { - put("query", LATEST_QUERY) - putJsonObject("variables") { - put("offset", (page - 1) * POPULAR_PER_PAGE) - put("limit", POPULAR_PER_PAGE) - } - } + val payload = GraphQlQuery( + query = LATEST_QUERY, + variables = LatestQueryVariables( + offset = (page - 1) * POPULAR_PER_PAGE, + limit = POPULAR_PER_PAGE, + ), + ) - val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", body.contentLength().toString()) @@ -111,28 +94,19 @@ class VoyceMe : HttpSource() { return POST(GRAPHQL_URL, newHeaders, body) } - override fun latestUpdatesParse(response: Response): MangasPage { - val result = json.parseToJsonElement(response.body.string()).jsonObject - - val comicList = result["data"]!!.jsonObject["voyce_series"]!! - .let { json.decodeFromJsonElement<List<VoyceMeComic>>(it) } - .map(::genericComicBookFromObject) - val hasNextPage = comicList.size == POPULAR_PER_PAGE - - return MangasPage(comicList, hasNextPage) - } + override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val payload = buildJsonObject { - put("query", SEARCH_QUERY) - putJsonObject("variables") { - put("searchTerm", "%$query%") - put("offset", (page - 1) * POPULAR_PER_PAGE) - put("limit", POPULAR_PER_PAGE) - } - } + val payload = GraphQlQuery( + query = SEARCH_QUERY, + variables = SearchQueryVariables( + searchTerm = "%$query%", + offset = (page - 1) * POPULAR_PER_PAGE, + limit = POPULAR_PER_PAGE, + ), + ) - val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", body.contentLength().toString()) @@ -142,39 +116,19 @@ class VoyceMe : HttpSource() { return POST(GRAPHQL_URL, newHeaders, body) } - override fun searchMangaParse(response: Response): MangasPage { - val result = json.parseToJsonElement(response.body.string()).jsonObject - - val comicList = result["data"]!!.jsonObject["voyce_series"]!! - .let { json.decodeFromJsonElement<List<VoyceMeComic>>(it) } - .map(::genericComicBookFromObject) - val hasNextPage = comicList.size == POPULAR_PER_PAGE - - return MangasPage(comicList, hasNextPage) - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsApiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } + override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - private fun mangaDetailsApiRequest(manga: SManga): Request { + override fun mangaDetailsRequest(manga: SManga): Request { val comicSlug = manga.url .substringAfter("/series/") .substringBefore("/") - val payload = buildJsonObject { - put("query", DETAILS_QUERY) - putJsonObject("variables") { - put("slug", comicSlug) - } - } + val payload = GraphQlQuery( + query = DETAILS_QUERY, + variables = DetailsQueryVariables(slug = comicSlug), + ) - val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", body.contentLength().toString()) @@ -185,18 +139,11 @@ class VoyceMe : HttpSource() { return POST(GRAPHQL_URL, newHeaders, body) } - override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { - val result = json.parseToJsonElement(response.body.string()).jsonObject - val comic = result["data"]!!.jsonObject["voyce_series"]!!.jsonArray[0].jsonObject - .let { json.decodeFromJsonElement<VoyceMeComic>(it) } - - title = comic.title - author = comic.author?.username.orEmpty() - description = Parser.unescapeEntities(comic.description.orEmpty(), true) - .let { Jsoup.parse(it).text() } - status = comic.status.orEmpty().toStatus() - genre = comic.genres.mapNotNull { it.genre?.title }.joinToString(", ") - thumbnail_url = STATIC_URL + comic.thumbnail + override fun getMangaUrl(manga: SManga) = baseUrl + manga.url + + override fun mangaDetailsParse(response: Response): SManga { + return response.parseAs<VoyceMeSeriesResponse>() + .data.series.first().toSManga() } override fun chapterListRequest(manga: SManga): Request { @@ -204,14 +151,12 @@ class VoyceMe : HttpSource() { .substringAfter("/series/") .substringBefore("/") - val payload = buildJsonObject { - put("query", CHAPTERS_QUERY) - putJsonObject("variables") { - put("slug", comicSlug) - } - } + val payload = GraphQlQuery( + query = CHAPTERS_QUERY, + variables = ChaptersQueryVariables(slug = comicSlug), + ) - val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) val newHeaders = headersBuilder() .add("Content-Length", body.contentLength().toString()) @@ -223,72 +168,41 @@ class VoyceMe : HttpSource() { } override fun chapterListParse(response: Response): List<SChapter> { - val result = json.parseToJsonElement(response.body.string()).jsonObject - val comicBook = result["data"]!!.jsonObject["voyce_series"]!!.jsonArray[0].jsonObject - .let { json.decodeFromJsonElement<VoyceMeComic>(it) } + val comic = response.parseAs<VoyceMeSeriesResponse>().data.series.first() - return comicBook.chapters - .map { chapter -> chapterFromObject(chapter, comicBook) } - .distinctBy { chapter -> chapter.name } + return comic.chapters + .map { it.toSChapter(comic.slug) } + .distinctBy(SChapter::name) } - private fun chapterFromObject(chapter: VoyceMeChapter, comic: VoyceMeComic): SChapter = - SChapter.create().apply { - name = chapter.title - date_upload = chapter.createdAt.toDate() - url = "/series/${comic.slug}/${chapter.id}#comic" - } - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapter.url.substringBeforeLast("/")) - .build() - - return GET(baseUrl + chapter.url, newHeaders) - } - - private fun pageListApiRequest(buildId: String, chapterUrl: String): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapterUrl) - .build() - - val comicSlug = chapterUrl - .substringAfter("/series/") - .substringBefore("/") - val chapterId = chapterUrl + val chapterId = chapter.url .substringAfterLast("/") .substringBefore("#") + .toInt() - return GET("$baseUrl/_next/data/$buildId/series/$comicSlug/$chapterId.json", newHeaders) - } - - override fun pageListParse(response: Response): List<Page> { - // GraphQL endpoints do not have the chapter images, so we need - // to get the buildId to fetch the chapter from NextJS static data. - val document = response.asJsoup() - val nextData = document.selectFirst("script#__NEXT_DATA__")!!.data() - val nextJson = json.parseToJsonElement(nextData).jsonObject + val payload = GraphQlQuery( + query = PAGES_QUERY, + variables = PagesQueryVariables(chapterId = chapterId), + ) - val buildId = nextJson["buildId"]!!.jsonPrimitive.content - val chapterUrl = response.request.url.toString().substringAfter(baseUrl) + val body = json.encodeToString(payload).toRequestBody(JSON_MEDIA_TYPE) - val dataRequest = pageListApiRequest(buildId, chapterUrl) - val dataResponse = client.newCall(dataRequest).execute() - val dataJson = json.parseToJsonElement(dataResponse.body.string()).jsonObject + val newHeaders = headersBuilder() + .add("Content-Length", body.contentLength().toString()) + .add("Content-Type", body.contentType().toString()) + .build() - val comic = dataJson["pageProps"]!!.jsonObject["series"]!! - .let { json.decodeFromJsonElement<VoyceMeComic>(it) } + return POST(GRAPHQL_URL, newHeaders, body) + } - val chapterId = response.request.url.toString() - .substringAfterLast("/") - .substringBefore("#") - .toInt() - val chapter = comic.chapters.firstOrNull { it.id == chapterId } - ?: throw Exception(CHAPTER_DATA_NOT_FOUND) + override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url - return chapter.images.mapIndexed { i, page -> - Page(i, baseUrl, STATIC_URL + page.image) - } + override fun pageListParse(response: Response): List<Page> { + return response.parseAs<VoyceMeChapterImagesResponse>().data.images + .mapIndexed { i, page -> + Page(i, baseUrl, STATIC_URL + page.image) + } } override fun imageUrlParse(response: Response): String = "" @@ -302,33 +216,19 @@ class VoyceMe : HttpSource() { return GET(page.imageUrl!!, newHeaders) } - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus(): Int = when (this) { - "completed" -> SManga.COMPLETED - "ongoing" -> SManga.ONGOING - else -> SManga.UNKNOWN + private inline fun <reified T> Response.parseAs(): T = use { + json.decodeFromString(it.body.string()) } companion object { private const val ACCEPT_ALL = "*/*" private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val STATIC_URL = "https://dlkfxmdtxtzpb.cloudfront.net/" + const val STATIC_URL = "https://dlkfxmdtxtzpb.cloudfront.net/" private const val GRAPHQL_URL = "https://graphql.voyce.me/v1/graphql" private const val POPULAR_PER_PAGE = 10 private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - - private const val CHAPTER_DATA_NOT_FOUND = "Chapter data not found in website." } } diff --git a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeDto.kt b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeDto.kt index 45308fc4f2..0a04f3e7fc 100644 --- a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeDto.kt +++ b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeDto.kt @@ -1,7 +1,13 @@ package eu.kanade.tachiyomi.extension.en.voyceme +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import org.jsoup.Jsoup +import org.jsoup.parser.Parser +import java.text.SimpleDateFormat +import java.util.Locale @Serializable data class VoyceMeComic( @@ -14,7 +20,24 @@ data class VoyceMeComic( val status: String? = "", val thumbnail: String = "", val title: String = "", -) +) { + + fun toSManga(): SManga = SManga.create().apply { + title = this@VoyceMeComic.title + author = this@VoyceMeComic.author?.username.orEmpty() + description = Parser + .unescapeEntities(this@VoyceMeComic.description.orEmpty(), true) + .let { Jsoup.parseBodyFragment(it).text() } + status = when (this@VoyceMeComic.status.orEmpty()) { + "completed" -> SManga.COMPLETED + "ongoing" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + genre = genres.mapNotNull { it.genre?.title }.joinToString(", ") + url = "/series/$slug" + thumbnail_url = VoyceMe.STATIC_URL + thumbnail + } +} @Serializable data class VoyceMeAuthor( @@ -37,9 +60,67 @@ data class VoyceMeChapter( val id: Int = -1, val images: List<VoyceMePage> = emptyList(), val title: String = "", -) +) { + + fun toSChapter(comicSlug: String): SChapter = SChapter.create().apply { + name = title + date_upload = runCatching { DATE_FORMATTER.parse(createdAt)?.time } + .getOrNull() ?: 0L + url = "/series/$comicSlug/$id#comic" + } + + companion object { + private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } + } +} @Serializable data class VoyceMePage( val image: String = "", ) + +@Serializable +data class GraphQlQuery<T>( + val variables: T, + val query: String, +) + +@Serializable +data class GraphQlResponse<T>(val data: T) + +typealias VoyceMeSeriesResponse = GraphQlResponse<VoyceMeSeriesCollection> +typealias VoyceMeChapterImagesResponse = GraphQlResponse<VoyceChapterImagesCollection> + +@Serializable +data class VoyceMeSeriesCollection( + @SerialName("voyce_series") + val series: List<VoyceMeComic> = emptyList(), +) + +@Serializable +data class VoyceChapterImagesCollection( + @SerialName("voyce_chapter_images") + val images: List<VoyceMePage> = emptyList(), +) + +@Serializable +data class PopularQueryVariables( + val offset: Int, + val limit: Int, +) + +@Serializable +data class SearchQueryVariables( + val offset: Int, + val limit: Int, + val searchTerm: String, +) + +@Serializable +data class DetailsQueryVariables(val slug: String) + +@Serializable +data class PagesQueryVariables(val chapterId: Int) + +typealias LatestQueryVariables = PopularQueryVariables +typealias ChaptersQueryVariables = DetailsQueryVariables diff --git a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeQueries.kt b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeQueries.kt index 2a0326cfd9..18d62a7a43 100644 --- a/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeQueries.kt +++ b/src/en/voyceme/src/eu/kanade/tachiyomi/extension/en/voyceme/VoyceMeQueries.kt @@ -113,3 +113,16 @@ val CHAPTERS_QUERY: String = buildQuery { } """.trimIndent() } + +val PAGES_QUERY: String = buildQuery { + """ + query(%chapterId: Int!) { + voyce_chapter_images( + where: { chapter_id: { _eq: %chapterId } }, + order_by: { sort_order: asc } + ) { + image + } + } + """.trimIndent() +} diff --git a/src/en/vyvymanga/AndroidManifest.xml b/src/en/vyvymanga/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/en/vyvymanga/AndroidManifest.xml +++ b/src/en/vyvymanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/en/warforrayuba/AndroidManifest.xml b/src/en/warforrayuba/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/warforrayuba/AndroidManifest.xml +++ b/src/en/warforrayuba/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/webcomics/AndroidManifest.xml b/src/en/webcomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/webcomics/AndroidManifest.xml +++ b/src/en/webcomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/webnovel/AndroidManifest.xml b/src/en/webnovel/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/webnovel/AndroidManifest.xml +++ b/src/en/webnovel/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/en/zeroscans/AndroidManifest.xml b/src/en/zeroscans/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/en/zeroscans/AndroidManifest.xml +++ b/src/en/zeroscans/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/brakeout/AndroidManifest.xml b/src/es/brakeout/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/es/brakeout/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/es/brakeout/build.gradle b/src/es/brakeout/build.gradle new file mode 100644 index 0000000000..aab7789099 --- /dev/null +++ b/src/es/brakeout/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Brakeout' + pkgNameSuffix = 'es.brakeout' + extClass = '.Brakeout' + extVersionCode = 1 + isNsfw = false +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/brakeout/res/mipmap-hdpi/ic_launcher.png b/src/es/brakeout/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..dc81be6226 Binary files /dev/null and b/src/es/brakeout/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/brakeout/res/mipmap-mdpi/ic_launcher.png b/src/es/brakeout/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..669a1dffa6 Binary files /dev/null and b/src/es/brakeout/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/brakeout/res/mipmap-xhdpi/ic_launcher.png b/src/es/brakeout/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..995fba252d Binary files /dev/null and b/src/es/brakeout/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/brakeout/res/mipmap-xxhdpi/ic_launcher.png b/src/es/brakeout/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..db75c7495f Binary files /dev/null and b/src/es/brakeout/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/brakeout/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/brakeout/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..c303c6392a Binary files /dev/null and b/src/es/brakeout/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/brakeout/res/web_hi_res_512.png b/src/es/brakeout/res/web_hi_res_512.png new file mode 100644 index 0000000000..2d73619c88 Binary files /dev/null and b/src/es/brakeout/res/web_hi_res_512.png differ diff --git a/src/es/brakeout/src/eu/kanade/tachiyomi/extension/es/brakeout/Brakeout.kt b/src/es/brakeout/src/eu/kanade/tachiyomi/extension/es/brakeout/Brakeout.kt new file mode 100644 index 0000000000..038effd2eb --- /dev/null +++ b/src/es/brakeout/src/eu/kanade/tachiyomi/extension/es/brakeout/Brakeout.kt @@ -0,0 +1,182 @@ +package eu.kanade.tachiyomi.extension.es.brakeout + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.lang.IllegalArgumentException +import java.util.Calendar + +class Brakeout : ParsedHttpSource() { + + override val name = "Brakeout" + + override val baseUrl = "https://brakeout.xyz" + + override val lang = "es" + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + private val json: Json by injectLazy() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector(): String = "div#div-diario figure, div#div-semanal figure, div#div-mensual figure" + + override fun popularMangaNextPageSelector(): String? = null + + override fun popularMangaParse(response: Response): MangasPage { + val mangasPage = super.popularMangaParse(response) + val distinctList = mangasPage.mangas.distinctBy { it.url } + + return MangasPage(distinctList, mangasPage.hasNextPage) + } + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector(): String = "section.flex > div.grid > figure" + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + if (query.length > 1) return GET("$baseUrl/comics#$query", headers) + throw Exception("La búsqueda debe tener al menos 2 caracteres") + } + return GET("$baseUrl/comics?page=$page", headers) + } + + override fun searchMangaSelector(): String = "section.flex > div.grid > figure" + + override fun searchMangaNextPageSelector(): String = "main.container section.flex > div > a:containsOwn(Siguiente)" + + override fun searchMangaParse(response: Response): MangasPage { + val query = response.request.url.fragment ?: return super.searchMangaParse(response) + val document = response.asJsoup() + val mangas = parseMangaList(document, query) + return MangasPage(mangas, false) + } + + private fun parseMangaList(document: Document, query: String): List<SManga> { + val docString = document.toString() + val mangaListJson = JSON_PROJECT_LIST.find(docString)?.destructured?.toList()?.get(0).orEmpty() + + return try { + json.decodeFromString<List<SerieDto>>(mangaListJson) + .filter { it.title.contains(query, ignoreCase = true) } + .map { + SManga.create().apply { + title = it.title + thumbnail_url = it.thumbnail + url = "/ver/${it.id}/${it.slug}" + } + } + } catch (_: IllegalArgumentException) { + emptyList() + } + } + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + with(document.select("section#section-sinopsis")) { + description = select("p").text() + genre = select("div.flex:has(div:containsOwn(Géneros)) > div > a > span").joinToString { it.text() } + } + } + + override fun chapterListSelector(): String = "section#section-list-cap div.grid-capitulos > div > a.group" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.selectFirst("div#name")!!.text() + date_upload = parseRelativeDate(element.selectFirst("time")!!.text()) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select("section > div > img.readImg").mapIndexed { i, element -> + Page(i, "", element.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!") + + override fun getFilterList(): FilterList { + return FilterList( + Filter.Header("Limpie la barra de búsqueda y haga click en 'Filtrar' para mostrar todas las series."), + ) + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("día", "dia").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + class WordSet(private vararg val words: String) { + fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + } + + @Serializable + data class SerieDto( + val id: Int, + @SerialName("nombre") val title: String, + val slug: String, + @SerialName("portada") val thumbnail: String, + ) + + companion object { + private val JSON_PROJECT_LIST = """proyectos\s*=\s*(\[[\s\S]+?\])\s*;""".toRegex() + } +} diff --git a/src/es/cerberusseries/AndroidManifest.xml b/src/es/cerberusseries/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/es/cerberusseries/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/es/cerberusseries/build.gradle b/src/es/cerberusseries/build.gradle new file mode 100644 index 0000000000..c477aa0185 --- /dev/null +++ b/src/es/cerberusseries/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Cerberus Series' + pkgNameSuffix = 'es.cerberusseries' + extClass = '.CerberusSeries' + extVersionCode = 1 + isNsfw = false +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/cerberusseries/res/mipmap-hdpi/ic_launcher.png b/src/es/cerberusseries/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4ffd95cbc4 Binary files /dev/null and b/src/es/cerberusseries/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/cerberusseries/res/mipmap-mdpi/ic_launcher.png b/src/es/cerberusseries/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..bd313c13c3 Binary files /dev/null and b/src/es/cerberusseries/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/cerberusseries/res/mipmap-xhdpi/ic_launcher.png b/src/es/cerberusseries/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..cee3986387 Binary files /dev/null and b/src/es/cerberusseries/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/cerberusseries/res/mipmap-xxhdpi/ic_launcher.png b/src/es/cerberusseries/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..fdadd57fa8 Binary files /dev/null and b/src/es/cerberusseries/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/cerberusseries/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/cerberusseries/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..e0e7d69069 Binary files /dev/null and b/src/es/cerberusseries/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/cerberusseries/res/web_hi_res_512.png b/src/es/cerberusseries/res/web_hi_res_512.png new file mode 100644 index 0000000000..2c354d8973 Binary files /dev/null and b/src/es/cerberusseries/res/web_hi_res_512.png differ diff --git a/src/es/cerberusseries/src/eu/kanade/tachiyomi/extension/es/cerberusseries/CerberusSeries.kt b/src/es/cerberusseries/src/eu/kanade/tachiyomi/extension/es/cerberusseries/CerberusSeries.kt new file mode 100644 index 0000000000..5302a71ae0 --- /dev/null +++ b/src/es/cerberusseries/src/eu/kanade/tachiyomi/extension/es/cerberusseries/CerberusSeries.kt @@ -0,0 +1,107 @@ +package eu.kanade.tachiyomi.extension.es.cerberusseries + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.io.IOException +import java.util.Calendar + +class CerberusSeries : ParsedHttpSource() { + + override val name = "Cerberus Series" + + override val baseUrl = "https://cerberuseries.xyz" + + override val lang = "es" + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/comics?page=$page", headers) + + override fun popularMangaSelector(): String = "div.grid > div:has(> div.c-iZMlIN)" + + override fun popularMangaNextPageSelector(): String = "nav[role=navigation] a:contains(»), nav[role=navigation] a:contains(Next)" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + setUrlWithoutDomain(element.select("div.c-hCLgme a").attr("href")) + title = element.select("div.c-hCLgme a").text() + thumbnail_url = element.selectFirst("div.c-iZMlIN img")?.attr("abs:src") + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector(): String = popularMangaSelector() + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw IOException("Esta funcionalidad aún no esta implementada.") + + override fun searchMangaSelector(): String = throw UnsupportedOperationException("Not used!") + + override fun searchMangaNextPageSelector(): String = throw UnsupportedOperationException("Not used!") + + override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used!") + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + thumbnail_url = document.selectFirst("div.thumb-wrapper img")!!.attr("abs:src") + title = document.selectFirst("div.series-title")!!.text() + genre = document.select("div.tags-container span").joinToString { it.text() } + description = document.selectFirst("div.description-container")!!.text() + author = document.select("div.useful-container p:containsOwn(Autor) strong").text() + } + + override fun chapterListSelector(): String = "div.chapters-list-wrapper ul a" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.selectFirst("li span")!!.text() + date_upload = parseRelativeDate(element.selectFirst("li p")!!.text()) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select("div.main-content p > img").mapIndexed { i, element -> + Page(i, "", element.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!") + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("día", "dia").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + class WordSet(private vararg val words: String) { + fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + } +} diff --git a/src/es/heavenmanga/AndroidManifest.xml b/src/es/heavenmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/heavenmanga/AndroidManifest.xml +++ b/src/es/heavenmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/ikuhentai/AndroidManifest.xml b/src/es/ikuhentai/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/ikuhentai/AndroidManifest.xml +++ b/src/es/ikuhentai/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/inmanga/AndroidManifest.xml b/src/es/inmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/inmanga/AndroidManifest.xml +++ b/src/es/inmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/kingsofdarkness/AndroidManifest.xml b/src/es/kingsofdarkness/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/kingsofdarkness/AndroidManifest.xml +++ b/src/es/kingsofdarkness/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/kumanga/AndroidManifest.xml b/src/es/kumanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/kumanga/AndroidManifest.xml +++ b/src/es/kumanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/lectormanga/AndroidManifest.xml b/src/es/lectormanga/AndroidManifest.xml index 93055d399f..9ebcab8550 100644 --- a/src/es/lectormanga/AndroidManifest.xml +++ b/src/es/lectormanga/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/es/lectormanga/build.gradle b/src/es/lectormanga/build.gradle index 889eb2d08b..2bb4e184f1 100755 --- a/src/es/lectormanga/build.gradle +++ b/src/es/lectormanga/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'LectorManga' pkgNameSuffix = 'es.lectormanga' extClass = '.LectorManga' - extVersionCode = 29 + extVersionCode = 31 isNsfw = true } diff --git a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt index 4f478e1513..6593f1d374 100755 --- a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt +++ b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt @@ -2,6 +2,11 @@ package eu.kanade.tachiyomi.extension.es.lectormanga import android.app.Application import android.content.SharedPreferences +import android.os.Handler +import android.os.Looper +import android.view.View +import android.webkit.WebView +import android.webkit.WebViewClient import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess @@ -28,6 +33,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.CountDownLatch class LectorManga : ConfigurableSource, ParsedHttpSource() { @@ -44,38 +50,72 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() { .add("Referer", "$baseUrl/") } - private val imageCDNUrls = arrayOf("https://img1.followmanga.com", "https://img1.biggestchef.com", "https://img1.indalchef.com", "https://img1.recipesandcook.com") + private val imageCDNUrls = arrayOf( + "https://img1.followmanga.com", + "https://img1.biggestchef.com", + "https://img1.indalchef.com", + "https://img1.recipesandcook.com", + "https://img1.cyclingte.com", + "https://img1.japanreader.com", + "https://japanreader.com", + ) private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } + private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array<String>, permits: Int, period: Long): OkHttpClient.Builder { + hosts.forEach { host -> + rateLimitHost(host.toHttpUrlOrNull()!!, permits, period) + } + return this + } + + private var loadWebView = true override val client: OkHttpClient = network.client.newBuilder() .rateLimitHost( baseUrl.toHttpUrlOrNull()!!, preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), 60, ) - .rateLimitHost( - imageCDNUrls[0].toHttpUrlOrNull()!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .rateLimitHost( - imageCDNUrls[1].toHttpUrlOrNull()!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .rateLimitHost( - imageCDNUrls[2].toHttpUrlOrNull()!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .rateLimitHost( - imageCDNUrls[3].toHttpUrlOrNull()!!, + .rateLimitImageCDNs( + imageCDNUrls, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), 60, ) + .addInterceptor { chain -> + val request = chain.request() + val url = request.url + if (url.host.contains("japanreader.com") && loadWebView) { + val handler = Handler(Looper.getMainLooper()) + val latch = CountDownLatch(1) + var webView: WebView? = null + handler.post { + val webview = WebView(Injekt.get<Application>()) + webView = webview + webview.settings.domStorageEnabled = true + webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + webview.settings.useWideViewPort = false + webview.settings.loadWithOverviewMode = false + + webview.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + latch.countDown() + } + } + + val headers = mutableMapOf<String, String>() + headers["Referer"] = baseUrl + + webview.loadUrl(url.toString(), headers) + } + + latch.await() + loadWebView = false + handler.post { webView?.destroy() } + } + chain.proceed(request) + } .build() override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&type=&filter_by=title&page=$page", headers) diff --git a/src/es/leermangasxyz/AndroidManifest.xml b/src/es/leermangasxyz/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/es/leermangasxyz/AndroidManifest.xml +++ b/src/es/leermangasxyz/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/es/mangamx/AndroidManifest.xml b/src/es/mangamx/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/mangamx/AndroidManifest.xml +++ b/src/es/mangamx/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/mangatigre/AndroidManifest.xml b/src/es/mangatigre/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/es/mangatigre/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/mangatigre/build.gradle b/src/es/mangatigre/build.gradle deleted file mode 100644 index 88aa9fdcdd..0000000000 --- a/src/es/mangatigre/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'MangaTigre' - pkgNameSuffix = 'es.mangatigre' - extClass = '.MangaTigre' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/mangatigre/res/mipmap-hdpi/ic_launcher.png b/src/es/mangatigre/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f96cfabf12..0000000000 Binary files a/src/es/mangatigre/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangatigre/res/mipmap-mdpi/ic_launcher.png b/src/es/mangatigre/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index cdd4454db2..0000000000 Binary files a/src/es/mangatigre/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangatigre/res/mipmap-xhdpi/ic_launcher.png b/src/es/mangatigre/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e4fb3a1dec..0000000000 Binary files a/src/es/mangatigre/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangatigre/res/mipmap-xxhdpi/ic_launcher.png b/src/es/mangatigre/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 130f029b0b..0000000000 Binary files a/src/es/mangatigre/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangatigre/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/mangatigre/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a7b64891ed..0000000000 Binary files a/src/es/mangatigre/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangatigre/res/web_hi_res_512.png b/src/es/mangatigre/res/web_hi_res_512.png deleted file mode 100644 index a509921670..0000000000 Binary files a/src/es/mangatigre/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigre.kt b/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigre.kt deleted file mode 100644 index 56a730c0ee..0000000000 --- a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigre.kt +++ /dev/null @@ -1,389 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangatigre - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonObject -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import okio.Buffer -import org.jsoup.nodes.Element -import uy.kohesive.injekt.injectLazy -import java.util.Calendar - -class MangaTigre : HttpSource() { - - override val name = "MangaTigre" - - override val baseUrl = "https://www.mangatigre.net" - - override val lang = "es" - - override val supportsLatest = true - - private val json: Json by injectLazy() - - private val imgCDNUrl = "https://i2.mtcdn.xyz" - - private var mtToken = "" - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor { chain -> - val request = chain.request() - - if (request.method == "POST") { - val response = chain.proceed(request) - - if (response.code == 419) { - response.close() - setToken() - - val newBody = json.parseToJsonElement(request.bodyString).jsonObject.toMutableMap().apply { - this["_token"] = JsonPrimitive(mtToken) - } - - val payload = Json.encodeToString(JsonObject(newBody)).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - val newRequest = request.newBuilder() - .headers(apiHeaders) - .method(request.method, payload) - .build() - - return@addInterceptor chain.proceed(newRequest) - } - return@addInterceptor response - } - chain.proceed(request) - } - .rateLimitHost(baseUrl.toHttpUrl(), 1, 2) - .build() - - private fun setToken() { - val document = client.newCall(GET(baseUrl, headers)).execute().asJsoup() - mtToken = document.selectFirst("input.input-search[data-csrf]")!!.attr("data-csrf") - } - - override fun popularMangaRequest(page: Int): Request { - val payloadObj = PayloadManga( - page = page, - token = mtToken, - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$baseUrl/mangas?sort=views", apiHeaders, payload) - } - - override fun popularMangaParse(response: Response): MangasPage { - val jsonString = response.body.string() - - val result = json.decodeFromString<MangasDto>(jsonString) - - val mangas = result.mangas.map { - SManga.create().apply { - setUrlWithoutDomain("$baseUrl/manga/${it.slug}") - title = it.title - thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}" - } - } - val hasNextPage = result.totalPages > result.page - - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesRequest(page: Int): Request { - val payloadObj = PayloadManga( - page = page, - token = mtToken, - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$baseUrl/mangas?sort=date", apiHeaders, payload) - } - - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - if (query.length < 2) throw Exception("La cadena de búsqueda debe tener por lo menos 2 caracteres") - - val payloadObj = PayloadSearch( - query = query, - token = mtToken, - ) - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$baseUrl/mangas/search#$query", apiHeaders, payload) - } - - val url = "$baseUrl/mangas".toHttpUrlOrNull()!!.newBuilder() - - filters.forEach { filter -> - when (filter) { - is OrderFilter -> { - url.addQueryParameter("sort", filter.toUriPart()) - } - is TypeFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("type[]", content.id) - } - } - is StatusFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("status[]", content.id) - } - } - is DemographicFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("demographic[]", content.id) - } - } - is ContentFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("content[]", content.id) - } - } - is FormatFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("format[]", content.id) - } - } - is GenreFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("genre[]", content.id) - } - } - is ThemeFilter -> { - filter.state.forEach { content -> - if (content.state) url.addQueryParameter("theme[]", content.id) - } - } - else -> {} - } - } - - val payloadObj = PayloadManga( - page = page, - token = mtToken, - ) - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST(url.build().toString(), apiHeaders, payload) - } - - override fun searchMangaParse(response: Response): MangasPage { - val query = response.request.url.fragment - val jsonString = response.body.string() - - if (!query.isNullOrEmpty()) { - val result = json.decodeFromString<SearchDto>(jsonString) - - val mangas = result.result.map { - SManga.create().apply { - setUrlWithoutDomain("$baseUrl/manga/${it.slug}") - title = it.title - thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}" - } - } - - return MangasPage(mangas, false) - } - - val result = json.decodeFromString<MangasDto>(jsonString) - - val mangas = result.mangas.map { - SManga.create().apply { - setUrlWithoutDomain("$baseUrl/manga/${it.slug}") - title = it.title - thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}" - } - } - val hasNextPage = result.totalPages > result.page - - return MangasPage(mangas, hasNextPage) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - return SManga.create().apply { - document.selectFirst("div.mangas-content")!!.let { mangasContent -> - thumbnail_url = mangasContent.selectFirst("div.manga-image > img")!!.attr("abs:data-src") - val summary = mangasContent.selectFirst("div.synopsis > p")?.ownText()?.trim() ?: "" - with(mangasContent.selectFirst("ul.list-group")!!) { - description = createDescription(this, summary) - genre = createGenres(this) - author = selectFirst("li:has(strong:contains(Autor)) > a")?.ownText()?.trim() - artist = selectFirst("li:has(strong:contains(Artista)) > a")?.ownText()?.trim() - status = selectFirst("li:has(strong:contains(Estado))")?.ownText()?.trim()!!.toStatus() - } - } - } - } - - private fun createGenres(element: Element): String { - val demographic = element.select("li:has(strong:contains(Demografía)) a").joinToString { it.text() } - val genres = element.select("li:has(strong:contains(Géneros)) a").joinToString { it.text() } - val themes = element.select("li:has(strong:contains(Temas)) a").joinToString { it.text() } - val content = element.select("li:has(strong:contains(Contenido)) a").joinToString { it.text() } - return listOf(demographic, genres, themes, content).joinToString(", ") - } - - private fun createDescription(element: Element, summary: String): String { - val originalName = element.selectFirst("li:has(strong:contains(Original))")?.ownText()?.trim() ?: "" - val alternativeName = element.select("li:has(strong:contains(Alternativo)) span.alter-name").text() - val year = element.selectFirst("li:has(strong:contains(Año))")?.ownText()?.trim() ?: "" - val animeAdaptation = element.selectFirst("li:has(strong:contains(Anime))")?.ownText()?.trim() ?: "" - val country = element.selectFirst("li:has(strong:contains(País))")?.ownText()?.trim() ?: "" - return StringBuilder() - .appendLine("Nombre Original: $originalName") - .appendLine("Títulos Alternativos: $alternativeName") - .appendLine("Año: $year") - .appendLine("Adaptación al Anime: $animeAdaptation") - .appendLine("País: $country") - .appendLine() - .appendLine("Sinopsis: $summary") - .toString() - } - - override fun chapterListRequest(manga: SManga): Request { - val payloadObj = PayloadChapter( - token = mtToken, - ) - - val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) - - val apiHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Content-Type", payload.contentType().toString()) - .build() - - return POST("$baseUrl${manga.url}", apiHeaders, payload) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return response.asJsoup().select("li").map { - SChapter.create().apply { - setUrlWithoutDomain(it.select("a").attr("href")) - name = it.selectFirst("a")!!.ownText().trim() - date_upload = parseRelativeDate(it.selectFirst("span")!!.ownText().trim()) - } - } - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - val script = document.selectFirst("script:containsData(window.chapter)")!!.data() - val jsonString = CHAPTERS_REGEX.find(script)!!.groupValues[1] - - val result = json.decodeFromString<ChapterDto>(jsonString) - val slug = result.manga.slug - val number = result.number.toString() - - return result.images.map { - val imageUrl = "$imgCDNUrl/chapters/$slug/$number/${it.value.name}.${it.value.format}" - Page(it.key.toInt(), "", imageUrl) - }.sortedBy { it.index } - } - - override fun getFilterList() = FilterList( - Filter.Header("Los filtros serán ignorados si se realiza una búsqueda textual"), - Filter.Separator(), - OrderFilter(), - Filter.Separator(), - TypeFilter(getFilterTypeList()), - Filter.Separator(), - StatusFilter(getFilterStatusList()), - Filter.Separator(), - DemographicFilter(getFilterDemographicList()), - Filter.Separator(), - ContentFilter(getFilterContentList()), - Filter.Separator(), - FormatFilter(getFilterFormatList()), - Filter.Separator(), - GenreFilter(getFilterGenreList()), - Filter.Separator(), - ThemeFilter(getFilterThemeList()), - ) - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.") - - private fun parseRelativeDate(date: String): Long { - val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 - val cal = Calendar.getInstance() - - return when { - WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - WordSet("minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis - WordSet("hora").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - WordSet("día").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - WordSet("semana").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis - WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis - WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis - else -> 0 - } - } - class WordSet(private vararg val words: String) { - fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } - } - - private val Request.bodyString: String - get() { - val requestCopy = newBuilder().build() - val buffer = Buffer() - - return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() } - .getOrNull() ?: "" - } - - companion object { - private const val ACCEPT_JSON = "application/json, text/plain, */*" - private val JSON_MEDIA_TYPE = "application/json".toMediaType() - - private val CHAPTERS_REGEX = """window\.chapter\s*=\s*'(.+?)';""".toRegex() - } -} diff --git a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreDto.kt b/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreDto.kt deleted file mode 100644 index 7a82d9ce30..0000000000 --- a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreDto.kt +++ /dev/null @@ -1,76 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangatigre - -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonPrimitive - -@Serializable -data class PayloadManga( - val page: Int, - @SerialName("_token") val token: String, -) - -@Serializable -data class PayloadChapter( - @SerialName("_token") val token: String, -) - -@Serializable -data class PayloadSearch( - val query: String, - @SerialName("_token") val token: String, -) - -@Serializable -data class MangasDto( - @SerialName("current_page") val page: Int, - @SerialName("last_page") val totalPages: Int, - @SerialName("data") val mangas: List<MangasDataDto>, -) - -@Serializable -data class MangasDataDto( - @SerialName("name") val title: String, - val slug: String, - @SerialName("image") val thumbnailFileName: String, -) - -@Serializable -data class ChapterDto( - val manga: ChapterMangaInfoDto, - val number: JsonPrimitive, // Can be Int or Float - val images: Map<String, ChapterImagesDto>, -) - -@Serializable -data class ChapterMangaInfoDto( - val slug: String, -) - -@Serializable -data class ChapterImagesDto( - val name: String, - val format: String, -) - -@Serializable -data class SearchDto( - val result: List<SearchDataDto>, -) - -@Serializable -data class SearchDataDto( - val id: Int, - @SerialName("name") val title: String, - val slug: String, - @SerialName("image")val thumbnailFileName: String, -) - -fun String.toStatus(): Int = when (this) { - "En Marcha" -> SManga.ONGOING - "Terminado" -> SManga.COMPLETED - "Detenido" -> SManga.ON_HIATUS - "Pausado" -> SManga.ON_HIATUS - else -> SManga.UNKNOWN -} diff --git a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreFilters.kt b/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreFilters.kt deleted file mode 100644 index 6d6a12d906..0000000000 --- a/src/es/mangatigre/src/eu/kanade/tachiyomi/extension/es/mangatigre/MangaTigreFilters.kt +++ /dev/null @@ -1,148 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangatigre - -import eu.kanade.tachiyomi.source.model.Filter - -class Type(name: String, val id: String) : Filter.CheckBox(name) -class TypeFilter(values: List<Type>) : Filter.Group<Type>("Tipos", values) - -class Status(name: String, val id: String) : Filter.CheckBox(name) -class StatusFilter(values: List<Status>) : Filter.Group<Status>("Estado", values) - -class Demographic(name: String, val id: String) : Filter.CheckBox(name) -class DemographicFilter(values: List<Demographic>) : Filter.Group<Demographic>("Demografía", values) - -class Content(name: String, val id: String) : Filter.CheckBox(name) -class ContentFilter(values: List<Content>) : Filter.Group<Content>("Contenido", values) - -class Format(name: String, val id: String) : Filter.CheckBox(name) -class FormatFilter(values: List<Format>) : Filter.Group<Format>("Formato", values) - -class Genre(name: String, val id: String) : Filter.CheckBox(name) -class GenreFilter(values: List<Genre>) : Filter.Group<Genre>("Géneros", values) - -class Theme(name: String, val id: String) : Filter.CheckBox(name) -class ThemeFilter(values: List<Theme>) : Filter.Group<Theme>("Temas", values) - -open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second -} - -class OrderFilter() : UriPartFilter( - "Ordenar por", - arrayOf( - Pair("Alfabético", "name"), - Pair("Vistas", "views"), - Pair("Fecha Estreno", "date"), - ), -) - -fun getFilterTypeList() = listOf( - Type("Manga", "1"), - Type("Manhwa", "2"), - Type("Manhua", "3"), -) - -fun getFilterStatusList() = listOf( - Status("En Marcha", "1"), - Status("Terminado", "2"), - Status("Detenido", "3"), - Status("Pausado", "4"), -) - -fun getFilterDemographicList() = listOf( - Demographic("Shonen", "1"), - Demographic("Seinen", "2"), - Demographic("Shojo", "3"), - Demographic("Josei", "4"), -) - -fun getFilterContentList() = listOf( - Content("Ecchi", "1"), - Content("Gore", "2"), - Content("Smut", "3"), - Content("Violencia Sexual", "4"), -) - -fun getFilterFormatList() = listOf( - Format("Adaptación", "14"), - Format("Antalogía", "9"), - Format("Color Completo", "18"), - Format("Coloreado Oficial", "19"), - Format("Coloreado Por Fan", "15"), - Format("Creado Por Usuario", "20"), - Format("Delincuencia", "16"), - Format("Doujinshi", "10"), - Format("Galardonado", "13"), - Format("One Shot", "11"), - Format("Tira Larga", "17"), - Format("Webcomic", "12"), - Format("YonKoma", "8"), -) - -fun getFilterGenreList() = listOf( - Genre("Acción", "49"), - Genre("Aventura", "50"), - Genre("Boys Love", "75"), - Genre("Chicas Mágicas", "73"), - Genre("Ciencia-Ficción", "64"), - Genre("Comedia", "51"), - Genre("Crimen", "52"), - Genre("Deporte", "65"), - Genre("Drama", "53"), - Genre("Fantasía", "54"), - Genre("Filosófico", "61"), - Genre("Girls Love", "76"), - Genre("Guerra", "74"), - Genre("Histórico", "55"), - Genre("Horror", "56"), - Genre("Isekai", "57"), - Genre("Mecha", "58"), - Genre("Médica", "59"), - Genre("Misterio", "60"), - Genre("Psicológico", "62"), - Genre("Recuentos De La Vida", "72"), - Genre("Romance", "63"), - Genre("Superhéroe", "66"), - Genre("Thriller", "67"), - Genre("Tragedia", "68"), - Genre("Wuxia", "69"), - Genre("Yaoi", "70"), - Genre("Yuri", "71"), -) - -fun getFilterThemeList() = listOf( - Theme("Animales", "52"), - Theme("Apocalíptico", "50"), - Theme("Artes Marciales", "60"), - Theme("Chicas Monstruo", "77"), - Theme("Cocinando", "53"), - Theme("Crossdressing", "79"), - Theme("Delincuencia", "78"), - Theme("Demonios", "54"), - Theme("Extranjeros", "51"), - Theme("Fantasma", "55"), - Theme("Género Bender", "81"), - Theme("Gyaru", "56"), - Theme("Harén", "57"), - Theme("Incesto", "58"), - Theme("Lolicon", "59"), - Theme("Mafia", "64"), - Theme("Magia", "65"), - Theme("Militar", "61"), - Theme("Monstruos", "62"), - Theme("Música", "63"), - Theme("Ninja", "66"), - Theme("Policía", "67"), - Theme("Realidad Virtual", "74"), - Theme("Reencarnación", "68"), - Theme("Samurái", "73"), - Theme("Shotacon", "71"), - Theme("Sobrenatural", "69"), - Theme("Superpoderes", "82"), - Theme("Supervivencia", "72"), - Theme("Vampiros", "75"), - Theme("Vida Escolar", "70"), - Theme("Videojuegos", "80"), - Theme("Zombis", "76"), -) diff --git a/src/es/manhwasnet/AndroidManifest.xml b/src/es/manhwasnet/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/manhwasnet/AndroidManifest.xml +++ b/src/es/manhwasnet/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/es/manhwasnet/build.gradle b/src/es/manhwasnet/build.gradle index d993e87a40..b9fb36213e 100644 --- a/src/es/manhwasnet/build.gradle +++ b/src/es/manhwasnet/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Manhwas.net' pkgNameSuffix = 'es.manhwasnet' extClass = '.ManhwasNet' - extVersionCode = 6 + extVersionCode = 10 isNsfw = true } diff --git a/src/es/manhwasnet/src/eu/kanade/tachiyomi/extension/es/manhwasnet/ManhwasNet.kt b/src/es/manhwasnet/src/eu/kanade/tachiyomi/extension/es/manhwasnet/ManhwasNet.kt index ed3ff9ab3a..219d22b4c8 100644 --- a/src/es/manhwasnet/src/eu/kanade/tachiyomi/extension/es/manhwasnet/ManhwasNet.kt +++ b/src/es/manhwasnet/src/eu/kanade/tachiyomi/extension/es/manhwasnet/ManhwasNet.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.extension.es.manhwasnet +import app.cash.quickjs.QuickJs import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList @@ -7,11 +8,17 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor import okhttp3.Request +import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import java.io.IOException import java.util.Calendar +import java.util.concurrent.TimeUnit class ManhwasNet : ParsedHttpSource() { @@ -20,17 +27,42 @@ class ManhwasNet : ParsedHttpSource() { override val name: String = "Manhwas.net" override val supportsLatest: Boolean = true - override val client = network.cloudflareClient.newBuilder() - .addInterceptor { chain -> - val originalRequest = chain.request() - val url = originalRequest.url.toString() - val response = chain.proceed(originalRequest) - if (response.headers["x-sucuri-cache"].isNullOrEmpty() && url.startsWith(baseUrl) && response.headers["x-sucuri-id"] != null) { - throw Exception("Sitio protegido - Abra en WebView para desbloquear.") + override val client = network.client.newBuilder() + .connectTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .addInterceptor { sucuriInterceptor(it) } + .build() + + private fun sucuriInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val url = request.url + val response = try { + chain.proceed(request) + } catch (e: Exception) { + // Try to clear cookies and retry + client.cookieJar.saveFromResponse(url, emptyList()) + val clearHeaders = request.headers.newBuilder().removeAll("Cookie").build() + chain.proceed(request.newBuilder().headers(clearHeaders).build()) + } + if (response.headers["x-sucuri-cache"].isNullOrEmpty() && response.headers["x-sucuri-id"] != null && url.toString().startsWith(baseUrl)) { + val script = response.asJsoup().selectFirst("script")?.data() + if (script != null) { + val a = script.split("(r)")[0].dropLast(1) + "r=r.replace('document.cookie','cookie');" + QuickJs.create().use { + val b = it.evaluate(a) as String + val sucuriCookie = it.evaluate(b.replace("location.", "").replace("reload();", "")) as String + val cookieName = sucuriCookie.split("=")[0] + val cookieValue = sucuriCookie.split("=")[1].replace(";path", "") + client.cookieJar.saveFromResponse(url, listOf(Cookie.parse(url, "$cookieName=$cookieValue")!!)) + } + val newResponse = chain.proceed(request) + if (!newResponse.headers["x-sucuri-cache"].isNullOrEmpty()) return newResponse } - return@addInterceptor response + throw IOException("Sitio protegido - Abra en WebView para intentar desbloquear.") } - .build() + return response + } override fun headersBuilder() = super.headersBuilder() .set("Referer", "$baseUrl/") @@ -52,7 +84,7 @@ class ManhwasNet : ParsedHttpSource() { } override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/esp") + return GET(baseUrl, headers) } override fun latestUpdatesSelector() = popularMangaSelector() @@ -84,7 +116,7 @@ class ManhwasNet : ParsedHttpSource() { } } url.addQueryParameter("page", page.toString()) - return GET(url.build().toString()) + return GET(url.build().toString(), headers) } override fun searchMangaSelector() = popularMangaSelector() @@ -113,7 +145,7 @@ class ManhwasNet : ParsedHttpSource() { } override fun pageListParse(document: Document): List<Page> { - return document.select("#chapter_imgs img").mapIndexed { i, img -> + return document.select("#chapter_imgs img[src][src!=\"\"]").mapIndexed { i, img -> val url = img.attr("abs:src") Page(i, imageUrl = url) } diff --git a/src/es/olympusscanlation/AndroidManifest.xml b/src/es/olympusscanlation/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/es/olympusscanlation/AndroidManifest.xml +++ b/src/es/olympusscanlation/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/es/olympusscanlation/build.gradle b/src/es/olympusscanlation/build.gradle index 7b45e02081..27196e61a2 100644 --- a/src/es/olympusscanlation/build.gradle +++ b/src/es/olympusscanlation/build.gradle @@ -7,7 +7,7 @@ ext { extName = 'Olympus Scanlation' pkgNameSuffix = 'es.olympusscanlation' extClass = '.OlympusScanlation' - extVersionCode = 3 + extVersionCode = 6 } -apply from: "$rootDir/common.gradle" \ No newline at end of file +apply from: "$rootDir/common.gradle" diff --git a/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlation.kt b/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlation.kt index 826bce9868..45b8cf5b5c 100644 --- a/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlation.kt +++ b/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlation.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.es.olympusscanlation import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page @@ -15,29 +17,41 @@ import okhttp3.Response import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale +import java.util.TimeZone class OlympusScanlation : HttpSource() { - override val baseUrl: String = "https://olympusscans.com" - private val apiBaseUrl: String = "https://dashboard.olympusscans.com" + override val versionId = 2 + + override val baseUrl: String = "https://olympusvisor.com" + private val apiBaseUrl: String = "https://dashboard.olympusvisor.com" + override val lang: String = "es" override val name: String = "Olympus Scanlation" - override val versionId = 2 + override val supportsLatest: Boolean = true + + override val client = super.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 1, 2) + .rateLimitHost(apiBaseUrl.toHttpUrl(), 2, 1) + .build() + private val json: Json by injectLazy() - private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.US) + private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val apiUrl = "$apiBaseUrl/api/search".toHttpUrl().newBuilder() - .addQueryParameter("name", query) + override fun popularMangaRequest(page: Int): Request { + val apiUrl = "$apiBaseUrl/api/series?page=1&direction=asc&type=comic".toHttpUrl().newBuilder() .build() return GET(apiUrl, headers) } - override fun searchMangaParse(response: Response): MangasPage { - val result = json.decodeFromString<PayloadMangaDto>(response.body.string()) - val mangaList = result.data.map { + override fun popularMangaParse(response: Response): MangasPage { + runCatching { fetchFilters() } + val result = json.decodeFromString<PayloadSeriesDto>(response.body.string()) + val resultMangaList = json.decodeFromString<List<MangaDto>>(result.data.recommended_series) + val mangaList = resultMangaList.filter { it.type == "comic" }.map { SManga.create().apply { url = "/series/comic-${it.slug}" title = it.name @@ -47,28 +61,95 @@ class OlympusScanlation : HttpSource() { return MangasPage(mangaList, hasNextPage = false) } - // Latest - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(1) + override fun latestUpdatesRequest(page: Int): Request { + val apiUrl = "$apiBaseUrl/api/sf/new-chapters?page=$page".toHttpUrl().newBuilder() + .build() + return GET(apiUrl, headers) + } + override fun latestUpdatesParse(response: Response): MangasPage { - val result = json.decodeFromString<PayloadHomeDto>(response.body.string()) - val mangaList = result.data.new_chapters.map { + runCatching { fetchFilters() } + val result = json.decodeFromString<NewChaptersDto>(response.body.string()) + val mangaList = result.data.filter { it.type == "comic" }.map { SManga.create().apply { url = "/series/comic-${it.slug}" title = it.name thumbnail_url = it.cover } } - return MangasPage(mangaList, hasNextPage = false) + val hasNextPage = result.current_page < result.last_page + return MangasPage(mangaList, hasNextPage) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + val apiUrl = "$apiBaseUrl/api/search".toHttpUrl().newBuilder() + .addQueryParameter("name", query) + .build() + return GET(apiUrl, headers) + } + + val url = "$apiBaseUrl/api/series".toHttpUrl().newBuilder() + filters.forEach { filter -> + when (filter) { + is SortFilter -> { + if (filter.state?.ascending == true) { + url.addQueryParameter("direction", "desc") + } else { + url.addQueryParameter("direction", "asc") + } + } + is GenreFilter -> { + if (filter.toUriPart() != 9999) { + url.addQueryParameter("genres", filter.toUriPart().toString()) + } + } + is StatusFilter -> { + if (filter.toUriPart() != 9999) { + url.addQueryParameter("status", filter.toUriPart().toString()) + } + } + else -> {} + } + } + url.addQueryParameter("type", "comic") + url.addQueryParameter("page", page.toString()) + return GET(url.build().toString(), headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + runCatching { fetchFilters() } + if (response.request.url.toString().startsWith("$apiBaseUrl/api/search")) { + val result = json.decodeFromString<PayloadMangaDto>(response.body.string()) + val mangaList = result.data.filter { it.type == "comic" }.map { + SManga.create().apply { + url = "/series/comic-${it.slug}" + title = it.name + thumbnail_url = it.cover + } + } + return MangasPage(mangaList, hasNextPage = false) + } + + val result = json.decodeFromString<PayloadSeriesDto>(response.body.string()) + val mangaList = result.data.series.data.map { + SManga.create().apply { + url = "/series/comic-${it.slug}" + title = it.name + thumbnail_url = it.cover + } + } + val hasNextPage = result.data.series.current_page < result.data.series.last_page + return MangasPage(mangaList, hasNextPage) } - // Details override fun mangaDetailsParse(response: Response): SManga { val slug = response.request.url .toString() .substringAfter("/series/comic-") .substringBefore("/chapters") - val urla = "$apiBaseUrl/api/series/$slug?type=comic" - val newRequest = GET(url = urla, headers = headers) + val apiUrl = "$apiBaseUrl/api/series/$slug?type=comic" + val newRequest = GET(url = apiUrl, headers = headers) val newResponse = client.newCall(newRequest).execute() val result = json.decodeFromString<MangaDetailDto>(newResponse.body.string()) return SManga.create().apply { @@ -76,12 +157,13 @@ class OlympusScanlation : HttpSource() { title = result.data.name thumbnail_url = result.data.cover description = result.data.summary + status = parseStatus(result.data.status?.id) + genre = result.data.genres?.joinToString { it.name.trim() } } } - override fun imageUrlParse(response: Response): String = throw Exception("Not used") + override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url - // Chapters override fun chapterListRequest(manga: SManga): Request { return paginatedChapterListRequest( manga.url @@ -124,9 +206,6 @@ class OlympusScanlation : HttpSource() { .getOrNull() ?: 0L } - override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url - - // Pages override fun pageListRequest(chapter: SChapter): Request { val id = chapter.url .substringAfter("/capitulo/") @@ -139,28 +218,94 @@ class OlympusScanlation : HttpSource() { return GET("$apiBaseUrl/api/series/$slug/chapters/$id?type=comic") } - override fun pageListParse(response: Response): List<Page> = - json.decodeFromString<PayloadPagesDto>(response.body.string()).chapter.pages.mapIndexed { i, img -> + override fun pageListParse(response: Response): List<Page> { + return json.decodeFromString<PayloadPagesDto>(response.body.string()).chapter.pages.mapIndexed { i, img -> Page(i, "", img) } + } - // Popular - override fun popularMangaParse(response: Response): MangasPage { - val result = json.decodeFromString<PayloadHomeDto>(response.body.string()) - val resultMangaList = json.decodeFromString<List<MangaDto>>(result.data.popular_comics) - val mangaList = resultMangaList.map { - SManga.create().apply { - url = "/series/comic-${it.slug}" - title = it.name - thumbnail_url = it.cover + override fun imageUrlParse(response: Response): String = throw Exception("Not used") + + private fun parseStatus(statusId: Int?) = when (statusId) { + 1 -> SManga.ONGOING + 3 -> SManga.ON_HIATUS + 4 -> SManga.COMPLETED + 5 -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + + private class SortFilter : Filter.Sort( + "Ordenar", + arrayOf("Alfabético"), + Selection(0, false), + ) + + private class GenreFilter(genres: List<Pair<String, Int>>) : UriPartFilter( + "Género", + arrayOf( + Pair("Todos", 9999), + *genres.toTypedArray(), + ), + ) + + private class StatusFilter(statuses: List<Pair<String, Int>>) : UriPartFilter( + "Estado", + arrayOf( + Pair("Todos", 9999), + *statuses.toTypedArray(), + ), + ) + + override fun getFilterList(): FilterList { + val filters = mutableListOf<Filter<*>>( + Filter.Header("Los filtros no funcionan en la búsqueda por texto"), + Filter.Separator(), + SortFilter(), + ) + + if (genresList.isNotEmpty() || statusesList.isNotEmpty()) { + filters += listOf( + Filter.Separator(), + Filter.Header("Filtrar por género"), + GenreFilter(genresList), + ) + + filters += listOf( + Filter.Separator(), + Filter.Header("Filtrar por estado"), + StatusFilter(statusesList), + ) + } else { + filters += listOf( + Filter.Separator(), + Filter.Header("Presione 'Reiniciar' para intentar cargar los filtros"), + ) + } + + return FilterList(filters) + } + + private var genresList: List<Pair<String, Int>> = emptyList() + private var statusesList: List<Pair<String, Int>> = emptyList() + private var fetchFiltersAttemps = 0 + private var fetchFiltersFailed = false + + private fun fetchFilters() { + if (fetchFiltersAttemps <= 3 && ((genresList.isEmpty() && statusesList.isEmpty()) || fetchFiltersFailed)) { + val filters = runCatching { + val response = client.newCall(GET("$apiBaseUrl/api/genres-statuses", headers)).execute() + json.decodeFromString<GenresStatusesDto>(response.body.string()) } + + fetchFiltersFailed = filters.isFailure + genresList = filters.getOrNull()?.genres?.map { it.name.trim() to it.id } ?: emptyList() + statusesList = filters.getOrNull()?.statuses?.map { it.name.trim() to it.id } ?: emptyList() + fetchFiltersAttemps++ } - return MangasPage(mangaList, hasNextPage = false) } - override fun popularMangaRequest(page: Int): Request { - val apiUrl = "$apiBaseUrl/api/home".toHttpUrl().newBuilder() - .build() - return GET(apiUrl, headers) + open class UriPartFilter(displayName: String, val vals: Array<Pair<String, Int>>) : + Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second } } diff --git a/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlationDto.kt b/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlationDto.kt index 7d3ffdba2f..afb609bd98 100644 --- a/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlationDto.kt +++ b/src/es/olympusscanlation/src/eu/kanade/tachiyomi/extension/es/olympusscanlation/OlympusScanlationDto.kt @@ -4,12 +4,24 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ChapterDto( - val id: Int, - val name: String, - @SerialName("published_at") val date: String, +data class PayloadSeriesDto(val data: PayloadSeriesDataDto) + +@Serializable +data class PayloadSeriesDataDto( + val series: SeriesDto, + val recommended_series: String, ) +@Serializable +data class SeriesDto( + val current_page: Int, + val data: List<MangaDto>, + val last_page: Int, +) + +@Serializable +data class PayloadMangaDto(val data: List<MangaDto>) + @Serializable data class MangaDto( val id: Int, @@ -18,35 +30,64 @@ data class MangaDto( val cover: String? = null, val type: String? = null, val summary: String? = null, + val status: MangaStatusDto? = null, + val genres: List<FilterDto>? = null, ) @Serializable -data class MangaDetailDto( - var data: MangaDto, +data class NewChaptersDto( + val data: List<LatestMangaDto>, + val current_page: Int, + val last_page: Int, ) @Serializable -data class MetaDto(val total: Int) +data class LatestMangaDto( + val id: Int, + val name: String, + val slug: String, + val cover: String? = null, + val type: String? = null, +) @Serializable -data class PageDto(val pages: List<String>) +data class MangaDetailDto( + var data: MangaDto, +) @Serializable data class PayloadChapterDto(var data: List<ChapterDto>, val meta: MetaDto) @Serializable -data class PayloadMangaDto(val data: List<MangaDto>) +data class ChapterDto( + val id: Int, + val name: String, + @SerialName("published_at") val date: String, +) + +@Serializable +data class MetaDto(val total: Int) @Serializable data class PayloadPagesDto(val chapter: PageDto) @Serializable -data class HomeDto( - val popular_comics: String, - val new_chapters: List<MangaDto>, +data class PageDto(val pages: List<String>) + +@Serializable +data class MangaStatusDto( + val id: Int, + val name: String, ) @Serializable -data class PayloadHomeDto( - val data: HomeDto, +data class GenresStatusesDto( + val genres: List<FilterDto>, + val statuses: List<FilterDto>, +) + +@Serializable +data class FilterDto( + val id: Int, + val name: String, ) diff --git a/src/es/ravenmanga/AndroidManifest.xml b/src/es/ravenmanga/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/es/ravenmanga/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/es/ravenmanga/build.gradle b/src/es/ravenmanga/build.gradle new file mode 100644 index 0000000000..861a5247b2 --- /dev/null +++ b/src/es/ravenmanga/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'RavenManga' + pkgNameSuffix = 'es.ravenmanga' + extClass = '.RavenManga' + extVersionCode = 1 + isNsfw = false +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/ravenmanga/res/mipmap-hdpi/ic_launcher.png b/src/es/ravenmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..46f37c0a3c Binary files /dev/null and b/src/es/ravenmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/ravenmanga/res/mipmap-mdpi/ic_launcher.png b/src/es/ravenmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..df44bb397d Binary files /dev/null and b/src/es/ravenmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/ravenmanga/res/mipmap-xhdpi/ic_launcher.png b/src/es/ravenmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..db4dbd1d63 Binary files /dev/null and b/src/es/ravenmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/ravenmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/es/ravenmanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2eed501f43 Binary files /dev/null and b/src/es/ravenmanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/ravenmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/ravenmanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..24eae058ed Binary files /dev/null and b/src/es/ravenmanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/ravenmanga/res/web_hi_res_512.png b/src/es/ravenmanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..22b66699a2 Binary files /dev/null and b/src/es/ravenmanga/res/web_hi_res_512.png differ diff --git a/src/es/ravenmanga/src/eu/kanade/tachiyomi/extension/es/ravenmanga/RavenManga.kt b/src/es/ravenmanga/src/eu/kanade/tachiyomi/extension/es/ravenmanga/RavenManga.kt new file mode 100644 index 0000000000..235977859c --- /dev/null +++ b/src/es/ravenmanga/src/eu/kanade/tachiyomi/extension/es/ravenmanga/RavenManga.kt @@ -0,0 +1,181 @@ +package eu.kanade.tachiyomi.extension.es.ravenmanga + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.lang.IllegalArgumentException +import java.util.Calendar + +class RavenManga : ParsedHttpSource() { + + override val name = "RavenManga" + + override val baseUrl = "https://ravenseries.lat" + + override val lang = "es" + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + private val json: Json by injectLazy() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector(): String = "div#div-diario figure, div#div-semanal figure, div#div-mensual figure" + + override fun popularMangaNextPageSelector(): String? = null + + override fun popularMangaParse(response: Response): MangasPage { + val mangasPage = super.popularMangaParse(response) + val distinctList = mangasPage.mangas.distinctBy { it.url } + + return MangasPage(distinctList, mangasPage.hasNextPage) + } + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector(): String = "section.flex > div.grid > figure" + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + if (query.length > 1) return GET("$baseUrl/comics#$query", headers) + throw Exception("La búsqueda debe tener al menos 2 caracteres") + } + return GET("$baseUrl/comics?page=$page", headers) + } + + override fun searchMangaSelector(): String = "section.flex > div.grid > figure" + + override fun searchMangaNextPageSelector(): String = "nav > ul.pagination > li > a[rel=next]" + + override fun searchMangaParse(response: Response): MangasPage { + val query = response.request.url.fragment ?: return super.searchMangaParse(response) + val document = response.asJsoup() + val mangas = parseMangaList(document, query) + return MangasPage(mangas, false) + } + + private fun parseMangaList(document: Document, query: String): List<SManga> { + val docString = document.toString() + val mangaListJson = JSON_PROJECT_LIST.find(docString)?.destructured?.toList()?.get(0).orEmpty() + + return try { + json.decodeFromString<List<SerieDto>>(mangaListJson) + .filter { it.title.contains(query, ignoreCase = true) } + .map { + SManga.create().apply { + title = it.title + thumbnail_url = it.thumbnail + url = "/sr2/${it.slug}" + } + } + } catch (_: IllegalArgumentException) { + emptyList() + } + } + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + with(document.select("section#section-sinopsis")) { + description = select("p").text() + genre = select("div.flex:has(div:containsOwn(Géneros)) > div > a > span").joinToString { it.text() } + } + } + + override fun chapterListSelector(): String = "section#section-list-cap div.grid > a" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.selectFirst("div#name")!!.text() + date_upload = parseRelativeDate(element.selectFirst("time")!!.text()) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select("main.contenedor-imagen > section img[src]").mapIndexed { i, element -> + Page(i, "", element.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!") + + override fun getFilterList(): FilterList { + return FilterList( + Filter.Header("Limpie la barra de búsqueda y haga click en 'Filtrar' para mostrar todas las series."), + ) + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("día", "dia").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + class WordSet(private vararg val words: String) { + fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + } + + @Serializable + data class SerieDto( + @SerialName("nombre") val title: String, + val slug: String, + @SerialName("portada") val thumbnail: String, + ) + + companion object { + private val JSON_PROJECT_LIST = """proyectos\s*=\s*(\[[\s\S]+?\])\s*;""".toRegex() + } +} diff --git a/src/es/templescanesp/AndroidManifest.xml b/src/es/templescanesp/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/es/templescanesp/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/es/templescanesp/build.gradle b/src/es/templescanesp/build.gradle new file mode 100644 index 0000000000..e6d2f50b13 --- /dev/null +++ b/src/es/templescanesp/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Temple Scan' + pkgNameSuffix = 'es.templescanesp' + extClass = '.TempleScanEsp' + extVersionCode = 33 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..0e3fb7a52a Binary files /dev/null and b/src/es/templescanesp/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..282c12a4de Binary files /dev/null and b/src/es/templescanesp/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..50ea886d77 Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..6cd4a9e3fd Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3ec4e450e3 Binary files /dev/null and b/src/es/templescanesp/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/templescanesp/res/web_hi_res_512.png b/src/es/templescanesp/res/web_hi_res_512.png new file mode 100644 index 0000000000..58e3613f91 Binary files /dev/null and b/src/es/templescanesp/res/web_hi_res_512.png differ diff --git a/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt b/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt new file mode 100644 index 0000000000..610b63b40e --- /dev/null +++ b/src/es/templescanesp/src/eu/kanade/tachiyomi/extension/es/templescanesp/TempleScanEsp.kt @@ -0,0 +1,185 @@ +package eu.kanade.tachiyomi.extension.es.templescanesp + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.lang.IllegalArgumentException +import java.util.Calendar + +class TempleScanEsp : ParsedHttpSource() { + + override val name = "Temple Scan" + + override val baseUrl = "https://templescanesp.net" + + override val lang = "es" + + // Moved from Madara to individual extension + override val versionId: Int = 2 + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + private val json: Json by injectLazy() + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector(): String = "div#div-diario figure, div#div-semanal figure, div#div-mensual figure" + + override fun popularMangaNextPageSelector(): String? = null + + override fun popularMangaParse(response: Response): MangasPage { + val mangasPage = super.popularMangaParse(response) + val distinctList = mangasPage.mangas.distinctBy { it.url } + + return MangasPage(distinctList, mangasPage.hasNextPage) + } + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector(): String = "section.flex > div.grid > figure" + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.isNotEmpty()) { + if (query.length > 1) return GET("$baseUrl/comics#$query", headers) + throw Exception("La búsqueda debe tener al menos 2 caracteres") + } + return GET("$baseUrl/comics?page=$page", headers) + } + + override fun searchMangaSelector(): String = "section.flex > div.grid > figure" + + override fun searchMangaNextPageSelector(): String = "nav > ul.pagination > li > a[rel=next]" + + override fun searchMangaParse(response: Response): MangasPage { + val query = response.request.url.fragment ?: return super.searchMangaParse(response) + val document = response.asJsoup() + val mangas = parseMangaList(document, query) + return MangasPage(mangas, false) + } + + private fun parseMangaList(document: Document, query: String): List<SManga> { + val docString = document.toString() + val mangaListJson = JSON_PROJECT_LIST.find(docString)?.destructured?.toList()?.get(0).orEmpty() + + return try { + json.decodeFromString<List<SerieDto>>(mangaListJson) + .filter { it.title.contains(query, ignoreCase = true) } + .map { + SManga.create().apply { + title = it.title + thumbnail_url = it.thumbnail + url = "/comic/${it.slug}" + } + } + } catch (_: IllegalArgumentException) { + emptyList() + } + } + + override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.selectFirst("img")!!.attr("abs:src") + title = element.selectFirst("figcaption")!!.text() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + with(document.select("section#section-sinopsis")) { + description = select("p").text() + genre = select("div.flex:has(div:containsOwn(Genders)) > div > a > span").joinToString { it.text() } + author = select("div.flex:has(div:containsOwn(Autor)) > div").text() + } + } + + override fun chapterListSelector(): String = "section#section-list-cap div.grid-capitulos > div" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + name = element.selectFirst("div#name")!!.text() + date_upload = parseRelativeDate(element.selectFirst("time")!!.text()) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select("main.contenedor-imagen > section img[src]").mapIndexed { i, element -> + Page(i, "", element.attr("abs:src")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used!") + + override fun getFilterList(): FilterList { + return FilterList( + Filter.Header("Limpie la barra de búsqueda y haga click en 'Filtrar' para mostrar todas las series."), + ) + } + + private fun parseRelativeDate(date: String): Long { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 + val cal = Calendar.getInstance() + + return when { + WordSet("segundo", "second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis + WordSet("minuto", "minute").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis + WordSet("hora", "hour").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis + WordSet("día", "dia", "day").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + WordSet("semana", "week").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis + WordSet("mes", "month").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis + WordSet("año", "year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis + else -> 0 + } + } + + class WordSet(private vararg val words: String) { + fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + } + + @Serializable + data class SerieDto( + @SerialName("nombre") val title: String, + val slug: String, + @SerialName("portada") val thumbnail: String, + ) + + companion object { + private val JSON_PROJECT_LIST = """proyectos\s*=\s*(\[[\s\S]+?\])\s*;""".toRegex() + } +} diff --git a/src/es/tmohentai/AndroidManifest.xml b/src/es/tmohentai/AndroidManifest.xml index 0356323240..7d60875374 100644 --- a/src/es/tmohentai/AndroidManifest.xml +++ b/src/es/tmohentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/es/tmohentai/build.gradle b/src/es/tmohentai/build.gradle index ac39edd73e..4727b07ff4 100755 --- a/src/es/tmohentai/build.gradle +++ b/src/es/tmohentai/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'TMOHentai' pkgNameSuffix = 'es.tmohentai' extClass = '.TMOHentai' - extVersionCode = 7 + extVersionCode = 8 isNsfw = true } diff --git a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt b/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt index 493e9504f4..ab1cb9b2bd 100755 --- a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt +++ b/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt @@ -4,6 +4,7 @@ import android.app.Application import android.content.SharedPreferences import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList @@ -13,7 +14,10 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -31,6 +35,13 @@ class TMOHentai : ConfigurableSource, ParsedHttpSource() { override val supportsLatest = true + override val client: OkHttpClient = super.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 1, 2) + .build() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .set("Referer", "$baseUrl/") + private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } diff --git a/src/es/tumangaonline/AndroidManifest.xml b/src/es/tumangaonline/AndroidManifest.xml index 7bb9dc39d6..ec978927b5 100644 --- a/src/es/tumangaonline/AndroidManifest.xml +++ b/src/es/tumangaonline/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -15,7 +14,7 @@ <category android:name="android.intent.category.BROWSABLE" /> <data - android:host="lectortmo.com" + android:host="visortmo.com" android:pathPattern="/library/..*/..*/..*" android:scheme="https" /> </intent-filter> diff --git a/src/es/tumangaonline/build.gradle b/src/es/tumangaonline/build.gradle index 476dfdfa8c..8fa6d0c5ca 100644 --- a/src/es/tumangaonline/build.gradle +++ b/src/es/tumangaonline/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'TuMangaOnline' pkgNameSuffix = 'es.tumangaonline' extClass = '.TuMangaOnline' - extVersionCode = 44 + extVersionCode = 46 isNsfw = true } diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt index 07375360aa..7468ad9071 100644 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt +++ b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt @@ -39,7 +39,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { override val name = "TuMangaOnline" - override val baseUrl = "https://lectortmo.com" + override val baseUrl = "https://visortmo.com" override val lang = "es" @@ -50,18 +50,28 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { .add("Referer", "$baseUrl/") } - private val imageCDNUrl = "https://img1.japanreader.com" + private val imageCDNUrls = arrayOf( + "https://japanreader.com", + "https://img1.japanreader.com", + ) private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } + private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array<String>, permits: Int, period: Long): OkHttpClient.Builder { + hosts.forEach { host -> + rateLimitHost(host.toHttpUrlOrNull()!!, permits, period) + } + return this + } + private var loadWebView = true override val client: OkHttpClient = network.client.newBuilder() .addInterceptor { chain -> val request = chain.request() - val url = request.url.toString() - if (url.startsWith(imageCDNUrl) && loadWebView) { + val url = request.url + if (url.host.contains("japanreader.com") && loadWebView) { val handler = Handler(Looper.getMainLooper()) val latch = CountDownLatch(1) var webView: WebView? = null @@ -84,7 +94,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { val headers = mutableMapOf<String, String>() headers["Referer"] = baseUrl - webview.loadUrl(url, headers) + webview.loadUrl(url.toString(), headers) } latch.await() @@ -98,8 +108,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), 60, ) - .rateLimitHost( - imageCDNUrl.toHttpUrlOrNull()!!, + .rateLimitImageCDNs( + imageCDNUrls, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), 60, ) diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt index 486ff7133a..05607386c3 100644 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt +++ b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt @@ -8,7 +8,7 @@ import android.util.Log import kotlin.system.exitProcess /** - * Springboard that accepts https://lectortmo.com/library/:type/:id/:slug intents and redirects them to + * Springboard that accepts https://visortmo.com/library/:type/:id/:slug intents and redirects them to * the main Tachiyomi process. */ class TuMangaOnlineUrlActivity : Activity() { diff --git a/src/es/vcpvmp/AndroidManifest.xml b/src/es/vcpvmp/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/es/vcpvmp/AndroidManifest.xml +++ b/src/es/vcpvmp/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/animesama/AndroidManifest.xml b/src/fr/animesama/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/animesama/AndroidManifest.xml +++ b/src/fr/animesama/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/aralosbd/AndroidManifest.xml b/src/fr/aralosbd/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/aralosbd/AndroidManifest.xml +++ b/src/fr/aralosbd/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/aralosbd/build.gradle b/src/fr/aralosbd/build.gradle index 4f9d416b6d..a9dcce055b 100644 --- a/src/fr/aralosbd/build.gradle +++ b/src/fr/aralosbd/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'AralosBD' pkgNameSuffix = 'fr.aralosbd' extClass = '.AralosBD' - extVersionCode = 4 + extVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/fr/aralosbd/ic_launcher-playstore.png b/src/fr/aralosbd/ic_launcher-playstore.png deleted file mode 100644 index 19b77f665a..0000000000 Binary files a/src/fr/aralosbd/ic_launcher-playstore.png and /dev/null differ diff --git a/src/fr/aralosbd/res/mipmap-hdpi/ic_launcher.png b/src/fr/aralosbd/res/mipmap-hdpi/ic_launcher.png index 2830267296..56d4541c36 100644 Binary files a/src/fr/aralosbd/res/mipmap-hdpi/ic_launcher.png and b/src/fr/aralosbd/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/fr/aralosbd/res/mipmap-mdpi/ic_launcher.png b/src/fr/aralosbd/res/mipmap-mdpi/ic_launcher.png index 09fa504faa..21aa94f14e 100644 Binary files a/src/fr/aralosbd/res/mipmap-mdpi/ic_launcher.png and b/src/fr/aralosbd/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/fr/aralosbd/res/mipmap-xhdpi/ic_launcher.png b/src/fr/aralosbd/res/mipmap-xhdpi/ic_launcher.png index 47b43dad4f..2c0dadf79d 100644 Binary files a/src/fr/aralosbd/res/mipmap-xhdpi/ic_launcher.png and b/src/fr/aralosbd/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/fr/aralosbd/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/aralosbd/res/mipmap-xxhdpi/ic_launcher.png index 5a84b5e344..1b79c51394 100644 Binary files a/src/fr/aralosbd/res/mipmap-xxhdpi/ic_launcher.png and b/src/fr/aralosbd/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/fr/aralosbd/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/aralosbd/res/mipmap-xxxhdpi/ic_launcher.png index f8bcb7a52f..843c146e75 100644 Binary files a/src/fr/aralosbd/res/mipmap-xxxhdpi/ic_launcher.png and b/src/fr/aralosbd/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/fr/aralosbd/res/web_hi_res_512.png b/src/fr/aralosbd/res/web_hi_res_512.png index 6a45a326f6..a0d2b24aa0 100644 Binary files a/src/fr/aralosbd/res/web_hi_res_512.png and b/src/fr/aralosbd/res/web_hi_res_512.png differ diff --git a/src/fr/aralosbd/src/eu/kanade/tachiyomi/extension/fr/aralosbd/AralosBD.kt b/src/fr/aralosbd/src/eu/kanade/tachiyomi/extension/fr/aralosbd/AralosBD.kt index 4d2fe2e666..b281d55961 100644 --- a/src/fr/aralosbd/src/eu/kanade/tachiyomi/extension/fr/aralosbd/AralosBD.kt +++ b/src/fr/aralosbd/src/eu/kanade/tachiyomi/extension/fr/aralosbd/AralosBD.kt @@ -26,6 +26,7 @@ class AralosBD : HttpSource() { val BOLD_REGEX = "\\*+\\s*([^\\*]*)\\s*\\*+".toRegex() val ITALIC_REGEX = "_+\\s*([^_]*)\\s*_+".toRegex() val ICON_REGEX = ":+[a-zA-Z]+:".toRegex() + val PAGE_REGEX = "page:([0-9]+)".toRegex() } private fun cleanString(string: String): String { @@ -46,28 +47,60 @@ class AralosBD : HttpSource() { private val json: Json by injectLazy() override fun popularMangaRequest(page: Int): Request { - // This is the search query used for the 'recommandations' in the front page - return GET("$baseUrl/manga/search?s=sort:allviews;limit:16;-id:3", headers) + // Let's use a request that just query everything by page, sorted by total views (title + chapters) + return GET("$baseUrl/manga/search?s=sort:allviews;limit:24;-id:3;page:${page - 1};order:desc", headers) } override fun popularMangaParse(response: Response): MangasPage { val searchResult = json.decodeFromString<AralosBDSearchResult>(response.body.string()) - return MangasPage(searchResult.mangas.map(::searchMangaToSManga), false) + var hasNextPage = false + val pageMatch = PAGE_REGEX.find(response.request.url.toString()) + if (pageMatch != null && pageMatch.groupValues.count() > 1) { + val currentPage = pageMatch.groupValues[1].toInt() + hasNextPage = currentPage < searchResult.page_count - 1 + } + + return MangasPage(searchResult.mangas.map(::searchMangaToSManga), hasNextPage) } - override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used.") - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used.") + override fun latestUpdatesParse(response: Response): MangasPage { + val searchResult = json.decodeFromString<AralosBDSearchResult>(response.body.string()) + + var hasNextPage = false + val pageMatch = PAGE_REGEX.find(response.request.url.toString()) + if (pageMatch != null && pageMatch.groupValues.count() > 1) { + val currentPage = pageMatch.groupValues[1].toInt() + hasNextPage = currentPage < searchResult.page_count - 1 + } + + return MangasPage(searchResult.mangas.map(::searchMangaToSManga), hasNextPage) + } + + override fun latestUpdatesRequest(page: Int): Request { + // That's almost exactly the same stuff that the popular request, simply ordered differently + // A new title will always have a greater ID, so we can sort by ID. Using year would not be + // accurate because it's the release year + // It would be better to sort by last updated, but this is not yet in the API + return GET("$baseUrl/manga/search?s=sort:id;limit:24;-id:3;page:${page - 1};order:desc", headers) + } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { // For a basic search, we call the appropriate endpoint - return GET("$baseUrl/manga/search?s=text:$query", headers) + return GET("$baseUrl/manga/search?s=page:${page - 1};sort:id;order:desc;text:$query", headers) } override fun searchMangaParse(response: Response): MangasPage { val searchResult = json.decodeFromString<AralosBDSearchResult>(response.body.string()) - return MangasPage(searchResult.mangas.map(::searchMangaToSManga), false) + var hasNextPage = false + val pageMatch = PAGE_REGEX.find(response.request.url.toString()) + if (pageMatch != null && pageMatch.groupValues.count() > 1) { + val currentPage = pageMatch.groupValues[1].toInt() + hasNextPage = currentPage < searchResult.page_count - 1 + } + + return MangasPage(searchResult.mangas.map(::searchMangaToSManga), hasNextPage) } override fun mangaDetailsRequest(manga: SManga): Request { @@ -167,6 +200,8 @@ data class AralosBDSearchManga( @Serializable data class AralosBDSearchResult( val error: Int = 0, + val result_count: Int = 0, + val page_count: Int = 0, val mangas: List<AralosBDSearchManga> = emptyList(), ) diff --git a/src/fr/fmteam/AndroidManifest.xml b/src/fr/fmteam/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/fmteam/AndroidManifest.xml +++ b/src/fr/fmteam/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/fmteam/build.gradle b/src/fr/fmteam/build.gradle index a4f1c51338..2a3de48264 100644 --- a/src/fr/fmteam/build.gradle +++ b/src/fr/fmteam/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'FMTEAM' pkgNameSuffix = 'fr.fmteam' extClass = '.FMTEAM' - extVersionCode = 1 + extVersionCode = 2 } apply from: "$rootDir/common.gradle" diff --git a/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt index f496fc8a2f..bed5cc3783 100644 --- a/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt +++ b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt @@ -6,114 +6,124 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.Request import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat -import java.util.Date import java.util.Locale +import java.util.TimeZone -class FMTEAM : ParsedHttpSource() { +class FMTEAM : HttpSource() { override val name = "FMTEAM" override val baseUrl = "https://fmteam.fr" override val lang = "fr" override val supportsLatest = true + override val versionId = 2 - + private val json: Json by injectLazy() companion object { - private val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.FRENCH) - private val allPagesRegex = "\"url\":\"(.*?)\"".toRegex() - private val authorRegex = "Author: *(.*) Synopsis".toRegex() - private val descriptionRegex = "Synopsis: *(.*)".toRegex() + private val DATE_FORMATTER by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + } } // All manga - override fun popularMangaRequest(page: Int) = GET("$baseUrl/directory/") - override fun popularMangaSelector() = "#content .panel .list.series .group" - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst(".title a")!!.text().trim() - setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) - thumbnail_url = element.select(".preview").attr("src") + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/comics") + + override fun popularMangaParse(response: Response): MangasPage { + val results = json.decodeFromString<FmteamComicListPage>(response.body.string()) + + return MangasPage(results.comics.sortedByDescending { it.views }.map(::fmTeamComicToSManga), false) } // Latest - override fun latestUpdatesRequest(page: Int) = GET(baseUrl) - override fun latestUpdatesSelector() = ".panel .list .group" - override fun latestUpdatesNextPageSelector() = ".prevnext .next .gbutton.fright:last-child a" - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst(".title a")!!.text().trim() - setUrlWithoutDomain(element.select(".title a").attr("href")) - } + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/api/comics") + override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesFromElement(element) - }.distinctBy { it.title } - return MangasPage(mangas, false) + val results = json.decodeFromString<FmteamComicListPage>(response.body.string()) + + return MangasPage(results.comics.sortedByDescending { parseDate(it.last_chapter.published_on) }.map(::fmTeamComicToSManga), false) } // Search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/api/search/$query") + override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - var mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } - val query = response.request.headers["query"] - if (query != null) { - mangas = mangas.filter { it.title.contains(query, true) } - } - return MangasPage(mangas, false) - } - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun searchMangaNextPageSelector(): String? = null - override fun searchMangaSelector(): String = popularMangaSelector() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val headers = headersBuilder() - .add("query", query) - .build() - return GET("$baseUrl/directory/", headers) + val results = json.decodeFromString<FmteamComicListPage>(response.body.string()) + + return MangasPage(results.comics.map(::fmTeamComicToSManga), false) } // Manga details - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select(".comic.info .thumbnail img").attr("src") - val authorSynopsis = document.select(".large.comic .info").text().trim() - if (authorSynopsis.contains("Author")) { - author = authorRegex.find(authorSynopsis)!!.groups[1]!!.value.trim() - } - description = descriptionRegex.find(authorSynopsis)!!.groups[1]!!.value.trim() + override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl/api${manga.url}") + + override fun mangaDetailsParse(response: Response): SManga { + val results = json.decodeFromString<FmteamComicDetailPage>(response.body.string()) + + return fmTeamComicToSManga(results.comic) + } + + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl${manga.url}" } // Chapter list - override fun chapterListSelector() = ".list .group .element" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - setUrlWithoutDomain(element.select(".title a").attr("href")) - name = element.select(".title a").attr("title").trim() - val date = element.select(".meta_r").text().trim().split(", ")[1] - date_upload = if (date === "Aujourd'hui") { - dateFormat.format(Date()).toLong() - } else { - try { - dateFormat.parse(date)!!.time - } catch (e: ParseException) { - 0L - } - } + override fun chapterListRequest(manga: SManga): Request = GET("$baseUrl/api${manga.url}") + + override fun chapterListParse(response: Response): List<SChapter> { + val results = json.decodeFromString<FmteamComicDetailPage>(response.body.string()) + + return results.comic.chapters?.map(::fmTeamChapterToSChapter) ?: emptyList() + } + + override fun getChapterUrl(chapter: SChapter): String { + return "$baseUrl${chapter.url}" } // Pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val script = document.selectFirst("script:containsData(pages =)")!!.data() - val allPages = allPagesRegex.findAll(script) - allPages.asIterable().mapIndexed { i, it -> - pages.add(Page(i, "", it.groupValues[1].replace("\\/", "/"))) + override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api${chapter.url}") + + override fun pageListParse(response: Response): List<Page> { + val results = json.decodeFromString<FmteamChapterDetailPage>(response.body.string()) + + return results.chapter.pages.orEmpty() + .mapIndexed { i, page -> Page(i, "${results.chapter.url}#${i + 1}", page) } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.") + + // Utils + private fun fmTeamComicToSManga(comic: FmteamComic): SManga = SManga.create().apply { + url = comic.url + title = comic.title + artist = comic.artist + author = comic.author + description = comic.description + genre = comic.genres.joinToString { it.name } + status = when (comic.status) { + "En cours" -> SManga.ONGOING + "Termin\u00e9" -> SManga.COMPLETED + else -> SManga.UNKNOWN } - return pages + thumbnail_url = comic.thumbnail + initialized = true + } + + private fun fmTeamChapterToSChapter(chapter: FmteamChapter): SChapter = SChapter.create().apply { + url = chapter.url + name = chapter.full_title + date_upload = parseDate(chapter.published_on) + scanlator = chapter.teams.filterNotNull().joinToString { it.name } + } + + private fun parseDate(dateStr: String): Long { + return runCatching { DATE_FORMATTER.parse(dateStr)?.time } + .getOrNull() ?: 0L } - override fun imageUrlParse(document: Document): String = throw Exception("Not used") } diff --git a/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAMDto.kt b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAMDto.kt new file mode 100644 index 0000000000..cf6074a767 --- /dev/null +++ b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAMDto.kt @@ -0,0 +1,54 @@ +package eu.kanade.tachiyomi.extension.fr.fmteam + +import kotlinx.serialization.Serializable + +@Serializable +data class FmteamComicListPage( + val comics: List<FmteamComic>, +) + +@Serializable +data class FmteamComicDetailPage( + val comic: FmteamComic, +) + +@Serializable +data class FmteamChapterDetailPage( + val chapter: FmteamChapter, +) + +@Serializable +data class FmteamComic( + val title: String, + val thumbnail: String, + val description: String, + val author: String, + val artist: String?, + val genres: List<FmteamTag>, + val status: String, + val views: Int, + val url: String, + val last_chapter: FmteamChapter, + val chapters: List<FmteamChapter>?, +) + +@Serializable +data class FmteamTag( + val name: String, +) + +@Serializable +data class FmteamChapter( + val full_title: String, + val chapter: Int, + val subchapter: Int?, + val teams: List<FmteamTeam?>, + val published_on: String, + val url: String, + val pages: List<String>?, +) + +@Serializable +data class FmteamTeam( + val name: String, +) diff --git a/src/fr/furyosquad/AndroidManifest.xml b/src/fr/furyosquad/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/furyosquad/AndroidManifest.xml +++ b/src/fr/furyosquad/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/furyosquad/build.gradle b/src/fr/furyosquad/build.gradle index 12ac81a466..7b8505eaf2 100644 --- a/src/fr/furyosquad/build.gradle +++ b/src/fr/furyosquad/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'FuryoSquad' pkgNameSuffix = 'fr.furyosquad' extClass = '.FuryoSquad' - extVersionCode = 2 + extVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/fr/furyosquad/src/eu/kanade/tachiyomi/extension/fr/furyosquad/FuryoSquad.kt b/src/fr/furyosquad/src/eu/kanade/tachiyomi/extension/fr/furyosquad/FuryoSquad.kt index 358bfdb172..b1ebe54d7d 100644 --- a/src/fr/furyosquad/src/eu/kanade/tachiyomi/extension/fr/furyosquad/FuryoSquad.kt +++ b/src/fr/furyosquad/src/eu/kanade/tachiyomi/extension/fr/furyosquad/FuryoSquad.kt @@ -25,7 +25,7 @@ class FuryoSquad : ParsedHttpSource() { override val name = "FuryoSquad" - override val baseUrl = "https://www.furyosquad.com/" + override val baseUrl = "https://www.furyosociety.com/" override val lang = "fr" diff --git a/src/fr/japanread/AndroidManifest.xml b/src/fr/japanread/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/japanread/AndroidManifest.xml +++ b/src/fr/japanread/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/japscan/AndroidManifest.xml b/src/fr/japscan/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/fr/japscan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/japscan/build.gradle b/src/fr/japscan/build.gradle deleted file mode 100644 index 4733ebba08..0000000000 --- a/src/fr/japscan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Japscan' - pkgNameSuffix = 'fr.japscan' - extClass = '.Japscan' - extVersionCode = 41 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4659fbb8cb..0000000000 Binary files a/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 19a5531b02..0000000000 Binary files a/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7320f09ebd..0000000000 Binary files a/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 52e248fa63..0000000000 Binary files a/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 166cd3cfe2..0000000000 Binary files a/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/web_hi_res_512.png b/src/fr/japscan/res/web_hi_res_512.png deleted file mode 100644 index 3af8c2771f..0000000000 Binary files a/src/fr/japscan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt b/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt deleted file mode 100644 index 964daeb89e..0000000000 --- a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt +++ /dev/null @@ -1,383 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.japscan - -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.util.Base64 -import android.util.Log -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class Japscan : ConfigurableSource, ParsedHttpSource() { - - override val id: Long = 11 - - override val name = "Japscan" - - override val baseUrl = "https://www.japscan.lol" - - override val lang = "fr" - - override val supportsLatest = true - - private val json: Json by injectLazy() - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(1, 2) - .build() - - companion object { - val dateFormat by lazy { - SimpleDateFormat("dd MMM yyyy", Locale.US) - } - private const val SHOW_SPOILER_CHAPTERS_Title = "Les chapitres en Anglais ou non traduit sont upload en tant que \" Spoilers \" sur Japscan" - private const val SHOW_SPOILER_CHAPTERS = "JAPSCAN_SPOILER_CHAPTERS" - private val prefsEntries = arrayOf("Montrer uniquement les chapitres traduit en Français", "Montrer les chapitres spoiler") - private val prefsEntryValues = arrayOf("hide", "show") - } - - private fun chapterListPref() = preferences.getString(SHOW_SPOILER_CHAPTERS, "hide") - - override fun headersBuilder() = super.headersBuilder() - .add("referer", "$baseUrl/") - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mangas/", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - pageNumberDoc = document - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun popularMangaSelector() = "#top_mangas_week li" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first()!!.let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - manga.thumbnail_url = "$baseUrl/imgs/${it.attr("href").replace(Regex("/$"),".jpg").replace("manga","mangas")}".lowercase(Locale.ROOT) - } - return manga - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .distinctBy { element -> element.select("a").attr("href") } - .map { element -> - latestUpdatesFromElement(element) - } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun latestUpdatesSelector() = "#chapters h3.text-truncate, #chapters_list h3.text-truncate" - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isEmpty()) { - val uri = Uri.parse(baseUrl).buildUpon() - .appendPath("mangas") - filters.forEach { filter -> - when (filter) { - is TextField -> uri.appendPath(((page - 1) + filter.state.toInt()).toString()) - is PageList -> uri.appendPath(((page - 1) + filter.values[filter.state]).toString()) - else -> {} - } - } - return GET(uri.toString(), headers) - } else { - val formBody = FormBody.Builder() - .add("search", query) - .build() - val searchHeaders = headers.newBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .build() - - try { - val searchRequest = POST("$baseUrl/live-search/", searchHeaders, formBody) - val searchResponse = client.newCall(searchRequest).execute() - - if (!searchResponse.isSuccessful) { - throw Exception("Code ${searchResponse.code} inattendu") - } - - val jsonResult = json.parseToJsonElement(searchResponse.body.string()).jsonArray - - if (jsonResult.isEmpty()) { - Log.d("japscan", "Search not returning anything, using duckduckgo") - throw Exception("Pas de données") - } - - return searchRequest - } catch (e: Exception) { - // Fallback to duckduckgo if the search does not return any result - val uri = Uri.parse("https://duckduckgo.com/lite/").buildUpon() - .appendQueryParameter("q", "$query site:$baseUrl/manga/") - .appendQueryParameter("kd", "-1") - return GET(uri.toString(), headers) - } - } - } - - override fun searchMangaNextPageSelector(): String = "li.page-item:last-child:not(li.active),.next_form .navbutton" - - override fun searchMangaSelector(): String = "div.card div.p-2, a.result-link" - - override fun searchMangaParse(response: Response): MangasPage { - if ("live-search" in response.request.url.toString()) { - val jsonResult = json.parseToJsonElement(response.body.string()).jsonArray - - val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } - - return MangasPage(mangaList, hasNextPage = false) - } - - return super.searchMangaParse(response) - } - - override fun searchMangaFromElement(element: Element): SManga { - return if (element.attr("class") == "result-link") { - SManga.create().apply { - title = element.text().substringAfter(" ").substringBefore(" | JapScan") - setUrlWithoutDomain(element.attr("abs:href")) - } - } else { - SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") - element.select("p a").let { - title = it.text() - url = it.attr("href") - } - } - } - } - - private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { - title = jsonObj["name"]!!.jsonPrimitive.content - url = jsonObj["url"]!!.jsonPrimitive.content - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.selectFirst("#main .card-body")!! - - val manga = SManga.create() - manga.thumbnail_url = infoElement.select("img").attr("abs:src") - - val infoRows = infoElement.select(".row, .d-flex") - infoRows.select("p").forEach { el -> - when (el.select("span").text().trim()) { - "Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim() - "Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim() - "Genre(s):" -> manga.genre = el.text().replace("Genre(s):", "").trim() - "Statut:" -> manga.status = el.text().replace("Statut:", "").trim().let { - parseStatus(it) - } - } - } - manga.description = infoElement.select("div:contains(Synopsis) + p").text().orEmpty() - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("En Cours") -> SManga.ONGOING - status.contains("Terminé") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "#chapters_list > div.collapse > div.chapters_list" + - if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" } - // JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available. - // Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list. - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.selectFirst("a")!! - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.ownText() - // Using ownText() doesn't include childs' text, like "VUS" or "RAW" badges, in the chapter name. - chapter.date_upload = element.selectFirst("span")!!.text().trim().let { parseChapterDate(it) } - return chapter - } - - private fun parseChapterDate(date: String): Long { - return try { - dateFormat.parse(date)?.time ?: 0 - } catch (e: ParseException) { - 0L - } - } - - private val shortDecodingStringsRe: Regex = Regex("""'([\dA-Z]{2})'""", RegexOption.IGNORE_CASE) - private val longDecodingStringsRe: Regex = Regex("""'([\dA-Z]{20})'""", RegexOption.IGNORE_CASE) - - override fun pageListParse(document: Document): List<Page> { - /* - JapScan stores chapter metadata in a `#data` element, and in the `data-data` attribute. - - This data is scrambled base64, and to unscramble it this code searches in the ZJS for - two strings of length 62 (base64 minus `+` and `/`), creating a character map. - - Since figuring out how to properly map characters would be more effort than I want to - put in, this just flips around the charsets if the first attempt didn't succeed. - */ - val zjsurl = document.getElementsByTag("script").first { - it.attr("src").contains("zjs", ignoreCase = true) - }.attr("src") - Log.d("japscan", "ZJS at $zjsurl") - val zjs = client.newCall(GET(baseUrl + zjsurl, headers)).execute().body.string() - - // Japscan split its lookup table into 1 string of 2 chars + 2 * 3 strings of 20 chars - // Try to find the 2 set of 3 strings of 20 chars first - val rawLongStringLookupTables = longDecodingStringsRe.findAll(zjs).map { - it.groupValues[1] - }.toList() - - if (rawLongStringLookupTables.size != 6) { - throw Exception("Attendait 6 chaînes de recherche dans ZJS, a trouvé ${rawLongStringLookupTables.size}") - } - - // Then try to find the 2 strings of 2 chars (will output 3 values the first is something else) - val rawShortStringLookupTables = shortDecodingStringsRe.findAll(zjs).map { - it.groupValues[1] - }.toList() - - if (rawShortStringLookupTables.size != 3) { - throw Exception("Attendait 3 chaînes de recherche dans ZJS, a trouvé ${rawShortStringLookupTables.size}") - } - - // Once we found the 8 strings, assuming they are always in the same order - // Since Japscan reverse the char order, reverse the strings - val stringLookupTables = listOf( - rawShortStringLookupTables[1].reversed() + rawLongStringLookupTables[5].reversed() + rawLongStringLookupTables[2].reversed() + rawLongStringLookupTables[0].reversed(), - rawShortStringLookupTables[2].reversed() + rawLongStringLookupTables[3].reversed() + rawLongStringLookupTables[4].reversed() + rawLongStringLookupTables[1].reversed(), - ) - - val scrambledData = document.getElementById("data")!!.attr("data-data") - - for (i in 0..1) { - Log.d("japscan", "descramble attempt $i") - val otherIndice = if (i == 0) 1 else 0 - val lookupTable = stringLookupTables[i].zip(stringLookupTables[otherIndice]).toMap() - try { - val unscrambledData = scrambledData.map { lookupTable[it] ?: it }.joinToString("") - if (!unscrambledData.startsWith("ey")) { - // `ey` is the Base64 representation of a curly bracket. Since we're expecting a - // JSON object, we're counting this attempt as failed if it doesn't start with a - // curly bracket. - continue - } - val decoded = Base64.decode(unscrambledData, Base64.DEFAULT).toString(Charsets.UTF_8) - - val data = json.parseToJsonElement(decoded).jsonObject - - return data["imagesLink"]!!.jsonArray.mapIndexed { idx, it -> - Page(idx, imageUrl = it.jsonPrimitive.content) - } - } catch (_: Throwable) {} - } - - throw Exception("Les deux tentatives de désembrouillage ont échoué") - } - - override fun imageUrlParse(document: Document): String = "" - - // Filters - private class TextField(name: String) : Filter.Text(name) - - private class PageList(pages: Array<Int>) : Filter.Select<Int>("Page #", arrayOf(0, *pages)) - - override fun getFilterList(): FilterList { - val totalPages = pageNumberDoc?.select("li.page-item:last-child a")?.text() - val pagelist = mutableListOf<Int>() - return if (!totalPages.isNullOrEmpty()) { - for (i in 0 until totalPages.toInt()) { - pagelist.add(i + 1) - } - FilterList( - Filter.Header("Page alphabétique"), - PageList(pagelist.toTypedArray()), - ) - } else { - FilterList( - Filter.Header("Page alphabétique"), - TextField("Page #"), - Filter.Header("Appuyez sur reset pour la liste"), - ) - } - } - - private var pageNumberDoc: Document? = null - - // Prefs - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val chapterListPref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_SPOILER_CHAPTERS_Title - title = SHOW_SPOILER_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } -} diff --git a/src/fr/lirescan/AndroidManifest.xml b/src/fr/lirescan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/lirescan/AndroidManifest.xml +++ b/src/fr/lirescan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/mangakawaii/AndroidManifest.xml b/src/fr/mangakawaii/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/mangakawaii/AndroidManifest.xml +++ b/src/fr/mangakawaii/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/scanmanga/AndroidManifest.xml b/src/fr/scanmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/scanmanga/AndroidManifest.xml +++ b/src/fr/scanmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/fr/scantradunion/AndroidManifest.xml b/src/fr/scantradunion/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/fr/scantradunion/AndroidManifest.xml +++ b/src/fr/scantradunion/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/KomikFan/AndroidManifest.xml b/src/id/KomikFan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/KomikFan/AndroidManifest.xml +++ b/src/id/KomikFan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/bacakomik/AndroidManifest.xml b/src/id/bacakomik/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/bacakomik/AndroidManifest.xml +++ b/src/id/bacakomik/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/bacakomik/build.gradle b/src/id/bacakomik/build.gradle index 9cb8023f73..c587ea170a 100644 --- a/src/id/bacakomik/build.gradle +++ b/src/id/bacakomik/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'BacaKomik' pkgNameSuffix = 'id.bacakomik' extClass = '.BacaKomik' - extVersionCode = 5 + extVersionCode = 7 } apply from: "$rootDir/common.gradle" diff --git a/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/BacaKomik.kt b/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/BacaKomik.kt index e89635e164..3e45177979 100644 --- a/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/BacaKomik.kt +++ b/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/BacaKomik.kt @@ -1,13 +1,13 @@ package eu.kanade.tachiyomi.extension.id.bacakomik import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request @@ -19,10 +19,9 @@ import java.util.Locale class BacaKomik : ParsedHttpSource() { override val name = "BacaKomik" - override val baseUrl = "https://bacakomik.co" + override val baseUrl = "https://bacakomik.me" override val lang = "id" override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US) // similar/modified theme of "https://komikindo.id" @@ -30,6 +29,10 @@ class BacaKomik : ParsedHttpSource() { // Formerly "Bacakomik" -> now "BacaKomik" override val id = 4383360263234319058 + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimit(20, 5) + .build() + override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/daftar-manga/page/$page/?order=popular", headers) } @@ -51,11 +54,10 @@ class BacaKomik : ParsedHttpSource() { override fun searchMangaFromElement(element: Element): SManga { val manga = SManga.create() + manga.setUrlWithoutDomain(element.select("div.animposx > a").first()!!.attr("href")) + manga.title = element.select(".animposx .tt h4").text() manga.thumbnail_url = element.select("div.limit img").attr("src") - element.select("div.animposx > a").first()!!.let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } + return manga } @@ -100,18 +102,19 @@ class BacaKomik : ParsedHttpSource() { val infoElement = document.select("div.infoanime").first()!! val descElement = document.select("div.desc > .entry-content.entry-content-single").first()!! val manga = SManga.create() + manga.title = document.select("#breadcrumbs li:last-child span").text() // need authorCleaner to take "pengarang:" string to remove it from author val authorCleaner = document.select(".infox .spe b:contains(Pengarang)").text() manga.author = document.select(".infox .spe span:contains(Pengarang)").text().substringAfter(authorCleaner) manga.artist = manga.author val genres = mutableListOf<String>() - infoElement.select(".infox > .genre-info > a").forEach { element -> + infoElement.select(".infox > .genre-info > a, .infox .spe span:contains(Jenis Komik) a").forEach { element -> val genre = element.text() genres.add(genre) } manga.genre = genres.joinToString(", ") manga.status = parseStatus(infoElement.select(".infox > .spe > span:nth-child(1)").text()) - manga.description = descElement.select("p").text() + manga.description = descElement.select("p").text().substringAfter("bercerita tentang ") manga.thumbnail_url = document.select(".thumb > img:nth-child(1)").attr("src") return manga } @@ -185,35 +188,25 @@ class BacaKomik : ParsedHttpSource() { override fun pageListParse(document: Document): List<Page> { val pages = mutableListOf<Page>() var i = 0 - val cdnUrl = "https://ttl.bakul.buzz/" // change with correct CDN url - document.getElementsByTag("img").forEach { element -> + document.select("div:has(>img[alt*=\"Chapter\"]) img").forEach { element -> val url = element.attr("onError").substringAfter("src='").substringBefore("';") - if (url.startsWith(cdnUrl)) { - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } + i++ + if (url.isNotEmpty()) { + pages.add(Page(i, "", url)) } } return pages } - override fun imageUrlParse(document: Document) = "" + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") override fun imageRequest(page: Page): Request { - return if (page.imageUrl!!.contains("i2.wp.com")) { - val headers = Headers.Builder() - headers.apply { - add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") - } - GET(page.imageUrl!!, headers.build()) - } else { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - GET(page.imageUrl!!, imgHeader) - } + val newHeaders = headersBuilder() + .set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*") + .set("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) } private class AuthorFilter : Filter.Text("Author") diff --git a/src/id/comicfx/AndroidManifest.xml b/src/id/comicfx/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/comicfx/AndroidManifest.xml +++ b/src/id/comicfx/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/doujindesu/AndroidManifest.xml b/src/id/doujindesu/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/doujindesu/AndroidManifest.xml +++ b/src/id/doujindesu/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/doujindesu/build.gradle b/src/id/doujindesu/build.gradle index 5465b3b504..6cd9db04f1 100644 --- a/src/id/doujindesu/build.gradle +++ b/src/id/doujindesu/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'DoujinDesu' pkgNameSuffix = 'id.doujindesu' extClass = '.DoujinDesu' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } diff --git a/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt b/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt index e9ed895f6d..e2769f5a2e 100644 --- a/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt +++ b/src/id/doujindesu/src/eu/kanade/tachiyomi/extension/id/doujindesu/DoujinDesu.kt @@ -1,7 +1,14 @@ package eu.kanade.tachiyomi.extension.id.doujindesu +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.AppInfo import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page @@ -16,19 +23,25 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale -class DoujinDesu : ParsedHttpSource() { +class DoujinDesu : ParsedHttpSource(), ConfigurableSource { // Information : DoujinDesu use EastManga WordPress Theme override val name = "Doujindesu" - override val baseUrl = "https://doujindesu.xxx" + override val baseUrl by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! } override val lang = "id" override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient // Private stuff + private val preferences: SharedPreferences by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } + private val DATE_FORMAT by lazy { SimpleDateFormat("EEEE, dd MMMM yyyy", Locale("id")) } @@ -258,7 +271,7 @@ class DoujinDesu : ParsedHttpSource() { return manga } - private fun imageFromElement(element: Element): String? { + private fun imageFromElement(element: Element): String { return when { element.hasAttr("data-src") -> element.attr("abs:data-src") element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src") @@ -422,4 +435,27 @@ class DoujinDesu : ParsedHttpSource() { Page(i, "", element.attr("src")) } } + + companion object { + private val PREF_DOMAIN_KEY = "preferred_domain_name_v${AppInfo.getVersionName()}" + private const val PREF_DOMAIN_TITLE = "Override BaseUrl" + private const val PREF_DOMAIN_DEFAULT = "https://doujindesu.tv" + private const val PREF_DOMAIN_SUMMARY = "Override default domain with a different one" + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + EditTextPreference(screen.context).apply { + key = PREF_DOMAIN_KEY + title = PREF_DOMAIN_TITLE + dialogTitle = PREF_DOMAIN_TITLE + summary = PREF_DOMAIN_SUMMARY + dialogMessage = "Default: $PREF_DOMAIN_DEFAULT" + setDefaultValue(PREF_DOMAIN_DEFAULT) + + setOnPreferenceChangeListener { _, newValue -> + Toast.makeText(screen.context, "Restart App to apply new setting.", Toast.LENGTH_LONG).show() + true + } + }.also(screen::addPreference) + } } diff --git a/src/id/komikindoid/AndroidManifest.xml b/src/id/komikindoid/AndroidManifest.xml index a46a5570e2..cd55ad9174 100644 --- a/src/id/komikindoid/AndroidManifest.xml +++ b/src/id/komikindoid/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/id/komikindoid/build.gradle b/src/id/komikindoid/build.gradle index f643d31d5c..7d05b80a79 100644 --- a/src/id/komikindoid/build.gradle +++ b/src/id/komikindoid/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'KomikIndoID' pkgNameSuffix = 'id.komikindoid' extClass = '.KomikIndoID' - extVersionCode = 11 + extVersionCode = 13 } apply from: "$rootDir/common.gradle" diff --git a/src/id/komikindoid/src/eu/kanade/tachiyomi/extension/id/komikindoid/KomikIndoID.kt b/src/id/komikindoid/src/eu/kanade/tachiyomi/extension/id/komikindoid/KomikIndoID.kt index 60ce5f4015..422f8d7dd6 100644 --- a/src/id/komikindoid/src/eu/kanade/tachiyomi/extension/id/komikindoid/KomikIndoID.kt +++ b/src/id/komikindoid/src/eu/kanade/tachiyomi/extension/id/komikindoid/KomikIndoID.kt @@ -18,7 +18,7 @@ import java.util.Locale class KomikIndoID : ParsedHttpSource() { override val name = "KomikIndoID" - override val baseUrl = "https://komikindo.one" + override val baseUrl = "https://komikindo.tv" override val lang = "id" override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient @@ -132,13 +132,18 @@ class KomikIndoID : ParsedHttpSource() { val artistCleaner = document.select(".infox .spe b:contains(Ilustrator)").text() manga.artist = document.select(".infox .spe span:contains(Ilustrator)").text().substringAfter(artistCleaner) val genres = mutableListOf<String>() - infoElement.select(".infox > .genre-info > a").forEach { element -> + infoElement.select(".infox .genre-info a, .infox .spe span:contains(Grafis:) a, .infox .spe span:contains(Tema:) a, .infox .spe span:contains(Konten:) a, .infox .spe span:contains(Jenis Komik:) a").forEach { element -> val genre = element.text() genres.add(genre) } manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select(".infox > .spe > span:nth-child(1)").text()) + manga.status = parseStatus(infoElement.select(".infox > .spe > span:nth-child(2)").text()) manga.description = descElement.select("p").text().substringAfter("bercerita tentang ") + // Add alternative name to manga description + val altName = document.selectFirst(".infox > .spe > span:nth-child(1)")?.text().takeIf { it.isNullOrBlank().not() } + altName?.let { + manga.description = manga.description + "\n\n$altName" + } manga.thumbnail_url = document.select(".thumb > img:nth-child(1)").attr("src").substringBeforeLast("?") return manga } diff --git a/src/id/komiku/AndroidManifest.xml b/src/id/komiku/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/komiku/AndroidManifest.xml +++ b/src/id/komiku/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/komiku/build.gradle b/src/id/komiku/build.gradle index 5819d5f68a..2362cd266b 100644 --- a/src/id/komiku/build.gradle +++ b/src/id/komiku/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Komiku' pkgNameSuffix = 'id.komiku' extClass = '.Komiku' - extVersionCode = 16 + extVersionCode = 17 } apply from: "$rootDir/common.gradle" diff --git a/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt b/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt index 58c9737ff3..713763d27f 100644 --- a/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt +++ b/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt @@ -67,9 +67,9 @@ class Komiku : ParsedHttpSource() { override fun latestUpdatesRequest(page: Int): Request { return if (page == 1) { - GET("$baseUrl/pustaka/?orderby=modified", headers) + GET("$baseUrlData/cari/?post_type=manga&s=&orderby=modified", headers) } else { - GET("$baseUrl/pustaka/page/$page/?orderby=modified", headers) + GET("$baseUrlData/cari/page/$page/?post_type=manga&s=&orderby=modified", headers) } } @@ -253,7 +253,7 @@ class Komiku : ParsedHttpSource() { // add series type(manga/manhwa/manhua/other) thinggy to genre val seriesTypeSelector = "table.inftable tr:contains(Jenis) a, table.inftable tr:has(a[href*=category\\/]) a, a[href*=category\\/]" - document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { + document.select(seriesTypeSelector).firstOrNull()?.text()?.let { if (it.isEmpty().not() && genre!!.contains(it, true).not()) { genre += if (genre!!.isEmpty()) it else ", $it" } diff --git a/src/id/mangaku/AndroidManifest.xml b/src/id/mangaku/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/id/mangaku/AndroidManifest.xml +++ b/src/id/mangaku/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/id/mangaku/build.gradle b/src/id/mangaku/build.gradle index 6a72c4a58a..fb7bf95af3 100644 --- a/src/id/mangaku/build.gradle +++ b/src/id/mangaku/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Mangaku' pkgNameSuffix = 'id.mangaku' extClass = '.Mangaku' - extVersionCode = 4 + extVersionCode = 6 } apply from: "$rootDir/common.gradle" diff --git a/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt b/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt index 271b70389d..d6c0344b06 100644 --- a/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt +++ b/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt @@ -28,7 +28,7 @@ class Mangaku : ParsedHttpSource() { override val name = "Mangaku" - override val baseUrl = "https://mangaku.vip" + override val baseUrl = "https://mangaku.blog" override val lang = "id" diff --git a/src/id/mangalay/AndroidManifest.xml b/src/id/mangalay/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/id/mangalay/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/id/mangalay/build.gradle b/src/id/mangalay/build.gradle new file mode 100644 index 0000000000..6e8debce8d --- /dev/null +++ b/src/id/mangalay/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Mangalay' + pkgNameSuffix = 'id.mangalay' + extClass = '.Mangalay' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/id/mangalay/res/mipmap-hdpi/ic_launcher.png b/src/id/mangalay/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..f0dae3fd09 Binary files /dev/null and b/src/id/mangalay/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/id/mangalay/res/mipmap-mdpi/ic_launcher.png b/src/id/mangalay/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ea1e66528a Binary files /dev/null and b/src/id/mangalay/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/id/mangalay/res/mipmap-xhdpi/ic_launcher.png b/src/id/mangalay/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..113afdfe83 Binary files /dev/null and b/src/id/mangalay/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/id/mangalay/res/mipmap-xxhdpi/ic_launcher.png b/src/id/mangalay/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9b794185be Binary files /dev/null and b/src/id/mangalay/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/id/mangalay/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/mangalay/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f6b6275095 Binary files /dev/null and b/src/id/mangalay/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/id/mangalay/res/web_hi_res_512.png b/src/id/mangalay/res/web_hi_res_512.png new file mode 100644 index 0000000000..1b880d32ad Binary files /dev/null and b/src/id/mangalay/res/web_hi_res_512.png differ diff --git a/src/id/mangalay/src/eu/kanade/tachiyomi/extension/id/mangalay/Mangalay.kt b/src/id/mangalay/src/eu/kanade/tachiyomi/extension/id/mangalay/Mangalay.kt new file mode 100644 index 0000000000..43e25d7de6 --- /dev/null +++ b/src/id/mangalay/src/eu/kanade/tachiyomi/extension/id/mangalay/Mangalay.kt @@ -0,0 +1,68 @@ +package eu.kanade.tachiyomi.extension.id.mangalay + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class Mangalay : ParsedHttpSource() { + override val name = "Mangalay" + override val baseUrl = "http://mangalay.blogspot.com" + override val lang = "id" + override val supportsLatest = false + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/2013/04/daftar-baca-komik_20.html", headers) + } + + override fun popularMangaSelector() = ".post-body table" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + setUrlWithoutDomain(element.select("a").first()!!.attr("href")) + title = element.select(".tr-caption").text() + thumbnail_url = element.select("img").attr("src") + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun mangaDetailsParse(document: Document): SManga = SManga.create() + + override fun chapterListSelector() = ".post-body span > a" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.select("b").text() + } + + override fun pageListParse(document: Document): List<Page> { + return document.select(".separator img") + .dropLast(1) // :last-child not working somehow + .mapIndexed { index, element -> + val url = element.attr("src") + Page(index, "", url) + } + } + + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not Used") + + override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not Used") + + override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not Used") + + override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not Used") + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not Used") + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") + + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not Used") + + override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not Used") + + override fun searchMangaSelector() = throw UnsupportedOperationException("Not Used") +} diff --git a/src/id/onepieceberwarna/AndroidManifest.xml b/src/id/onepieceberwarna/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/id/onepieceberwarna/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/id/onepieceberwarna/build.gradle b/src/id/onepieceberwarna/build.gradle new file mode 100644 index 0000000000..671ce1b89d --- /dev/null +++ b/src/id/onepieceberwarna/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'One Piece Berwarna' + pkgNameSuffix = 'id.onepieceberwarna' + extClass = '.OnePieceBerwarna' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" diff --git a/src/id/onepieceberwarna/res/mipmap-hdpi/ic_launcher.png b/src/id/onepieceberwarna/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..931bc053d5 Binary files /dev/null and b/src/id/onepieceberwarna/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/id/onepieceberwarna/res/mipmap-mdpi/ic_launcher.png b/src/id/onepieceberwarna/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f25243e9b6 Binary files /dev/null and b/src/id/onepieceberwarna/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/id/onepieceberwarna/res/mipmap-xhdpi/ic_launcher.png b/src/id/onepieceberwarna/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..54b24c6976 Binary files /dev/null and b/src/id/onepieceberwarna/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/id/onepieceberwarna/res/mipmap-xxhdpi/ic_launcher.png b/src/id/onepieceberwarna/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..254e42a86e Binary files /dev/null and b/src/id/onepieceberwarna/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/id/onepieceberwarna/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/onepieceberwarna/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ddfeae73d Binary files /dev/null and b/src/id/onepieceberwarna/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/id/onepieceberwarna/res/web_hi_res_512.png b/src/id/onepieceberwarna/res/web_hi_res_512.png new file mode 100644 index 0000000000..289ed79c87 Binary files /dev/null and b/src/id/onepieceberwarna/res/web_hi_res_512.png differ diff --git a/src/id/onepieceberwarna/src/eu/kanade/tachiyomi/extension/id/onepieceberwarna/OnePieceBerwarna.kt b/src/id/onepieceberwarna/src/eu/kanade/tachiyomi/extension/id/onepieceberwarna/OnePieceBerwarna.kt new file mode 100644 index 0000000000..740d43e191 --- /dev/null +++ b/src/id/onepieceberwarna/src/eu/kanade/tachiyomi/extension/id/onepieceberwarna/OnePieceBerwarna.kt @@ -0,0 +1,127 @@ +package eu.kanade.tachiyomi.extension.id.onepieceberwarna + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class OnePieceBerwarna : ParsedHttpSource() { + + override val name = "One Piece Berwarna" + + override val baseUrl = "https://onepieceberwarna.com" + + override val lang = "id" + + override val supportsLatest = false + + // one page contains all chapters + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector() = "section[data-id]:not(:first-child)" + + override fun popularMangaParse(response: Response): MangasPage { + val mainElements = response.asJsoup() + val list = mutableListOf<SManga>() + var saga: String? = null + var volume: String? = null + + val sections = mainElements.select(popularMangaSelector()) + + run loop@{ + sections.forEach { element -> + val dataID = element.attr("data-id") + if (dataID == "49afe94") { + // break on non-manga section + return@loop + } + + element.select(".elementor-col-100:not(:has(div[data-element_type=icon.default])) h1") + .first()?.let { + saga = it.text() + } + + element.select(".elementor-col-50 h1").first()?.let { + volume = it.text() + } + + element.select(".elementor-widget-container > img[data-src]").first()?.let { + list.add( + SManga.create().apply { + title = "$saga $volume" + thumbnail_url = it.attr("data-src") + url = dataID // just for identifier + }, + ) + } + } + } + return MangasPage(list, false) + } + + override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") + + override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used") + + override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl, headers) + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + author = "Eiichiro Oda" + description = "Bercerita tentang seorang laki-laki bernama Monkey D. Luffy, yang menentang arti dari gelar bajak laut. Daripada kesan nama besar, kejahatan, kekerasan, dia lebih terlihat seperti bajak laut rendahan yang suka bersenang-senang, alasan Luffy menjadi bajak laut adalah tekadnya untuk berpetualang di lautan yang menyenangkan dan bertemu orang-orang baru dan menarik, serta bersama-sama mencari One Piece." + genre = "Action, Adventure, Comedy, Fantasy, Martial Arts, Mystery, Shounen, Supernatural" + } + + override fun chapterListSelector(): String = "section[data-id=%s] .elementor-text-editor strong > a" + + override fun chapterListRequest(manga: SManga) = GET("$baseUrl/#${manga.url}", headers) + + override fun chapterListParse(response: Response): List<SChapter> { + val document = response.asJsoup() + + val dataId = response.request.url.fragment!! + val selector = chapterListSelector().format(dataId) + + return document.select(selector) + .map(::chapterFromElement) + .reversed() + } + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.text() + setUrlWithoutDomain(element.attr("href")) + } + + override fun pageListParse(document: Document): List<Page> { + return document.select(".entry-content img[data-src]:not(a img)") + .mapIndexed { index, img -> + Page(index, "", img.attr("data-src")) + } + } + + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used") + + override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") + + // website doesn't have search + override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") + + override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used") + + override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") +} diff --git a/src/it/animegdrclub/AndroidManifest.xml b/src/it/animegdrclub/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/animegdrclub/AndroidManifest.xml +++ b/src/it/animegdrclub/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/it/demoneceleste/AndroidManifest.xml b/src/it/demoneceleste/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/demoneceleste/AndroidManifest.xml +++ b/src/it/demoneceleste/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/it/digitalteam/AndroidManifest.xml b/src/it/digitalteam/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/digitalteam/AndroidManifest.xml +++ b/src/it/digitalteam/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/it/hentaifantasy/AndroidManifest.xml b/src/it/hentaifantasy/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/hentaifantasy/AndroidManifest.xml +++ b/src/it/hentaifantasy/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/it/novelleleggere/AndroidManifest.xml b/src/it/novelleleggere/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/novelleleggere/AndroidManifest.xml +++ b/src/it/novelleleggere/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/it/shingekinoshoujo/AndroidManifest.xml b/src/it/shingekinoshoujo/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/it/shingekinoshoujo/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/shingekinoshoujo/build.gradle b/src/it/shingekinoshoujo/build.gradle deleted file mode 100644 index 4add2bc458..0000000000 --- a/src/it/shingekinoshoujo/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Shingeki no Shoujo' - pkgNameSuffix = 'it.shingekinoshoujo' - extClass = '.ShingekiNoShoujo' - extVersionCode = 3 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/shingekinoshoujo/res/mipmap-hdpi/ic_launcher.png b/src/it/shingekinoshoujo/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 524250f616..0000000000 Binary files a/src/it/shingekinoshoujo/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/res/mipmap-mdpi/ic_launcher.png b/src/it/shingekinoshoujo/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1a6248fdd6..0000000000 Binary files a/src/it/shingekinoshoujo/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/res/mipmap-xhdpi/ic_launcher.png b/src/it/shingekinoshoujo/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 67abda1d2e..0000000000 Binary files a/src/it/shingekinoshoujo/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/res/mipmap-xxhdpi/ic_launcher.png b/src/it/shingekinoshoujo/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 00c4a52210..0000000000 Binary files a/src/it/shingekinoshoujo/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/shingekinoshoujo/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 529c550ce5..0000000000 Binary files a/src/it/shingekinoshoujo/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/res/web_hi_res_512.png b/src/it/shingekinoshoujo/res/web_hi_res_512.png deleted file mode 100644 index 10bd3aab85..0000000000 Binary files a/src/it/shingekinoshoujo/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/shingekinoshoujo/src/eu/kanade/tachiyomi/extension/it/shingekinoshoujo/ShingekiNoShoujo.kt b/src/it/shingekinoshoujo/src/eu/kanade/tachiyomi/extension/it/shingekinoshoujo/ShingekiNoShoujo.kt deleted file mode 100644 index 37538af913..0000000000 --- a/src/it/shingekinoshoujo/src/eu/kanade/tachiyomi/extension/it/shingekinoshoujo/ShingekiNoShoujo.kt +++ /dev/null @@ -1,213 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.shingekinoshoujo - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class ShingekiNoShoujo : ParsedHttpSource() { - - override val name = "Shingeki no Shoujo" - override val baseUrl = "https://shingekinoshoujo.it" - override val lang = "it" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - //region REQUESTS - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/", headers) - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/#recent-tab", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - return GET("$baseUrl/page/$page/?s=$query", headers) - } else { - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreSelez -> { - return GET( - "$baseUrl/tag/${getGenreList().filter { - filter.values[filter.state] == it.name - }.map { it.id }[0]}/page/$page", - ) - } - else -> {} - } - } - return GET(baseUrl, headers) - } - } - //endregion - - //region CONTENTS INFO - private fun mangasParse(response: Response, selector: String, num: Int): MangasPage { - val document = response.asJsoup() - if (document.select("#login > .custom-message").size > 0) throw Exception("Devi accedere al sito web con il tuo account!\nPremi WebView (il pulsante con il globo)\ne accedi normalmente") - - val mangas = document.select(selector).map { element -> - when (num) { - 1 -> popularMangaFromElement(element) - 2 -> latestUpdatesFromElement(element) - else -> searchMangaFromElement(element) - } - } - return MangasPage( - mangas, - !document.select(searchMangaNextPageSelector()).isEmpty(), - ) - } - override fun popularMangaParse(response: Response): MangasPage = mangasParse(response, popularMangaSelector(), 1) - override fun latestUpdatesParse(response: Response): MangasPage = mangasParse(response, latestUpdatesSelector(), 2) - override fun searchMangaParse(response: Response): MangasPage = mangasParse(response, searchMangaSelector(), 3) - - override fun popularMangaSelector() = "#content section .single-article:not([aria-hidden]) > figure" - override fun latestUpdatesSelector() = "#recent-tab > div > figure" - override fun searchMangaSelector() = "article.type-post" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - thumbnail_url = element.selectFirst("img")!!.attr("src") - element.selectFirst("a")!!.let { - setUrlWithoutDomain(it.attr("href")) - title = it.attr("title").let { l -> - l.ifEmpty { it.text() } - } - } - } - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun mangaDetailsParse(document: Document): SManga { - val statusElementText = document.select("blockquote").last()!!.text().lowercase() - return SManga.create().apply { - thumbnail_url = document.select(".wp-post-image").attr("src") - status = when { - statusElementText.contains("in corso") -> SManga.ONGOING - statusElementText.contains("fine") -> SManga.COMPLETED - statusElementText.contains("diritti") -> SManga.LICENSED - statusElementText.contains("sospeso") -> SManga.CANCELLED - statusElementText.contains("hiatus") -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - author = document.select(".entry-content span:has(strong:contains(Autore))").text().substringAfter("Autore:").trim() - genre = document.select(".entry-content span:has(strong:contains(Genere))").text().substringAfter("Genere:").replace(".", "").trim() - description = document.select(".entry-content p:has(strong:contains(Trama))").text().substringAfter("Trama:").trim() - } - } - //endregion - - //region NEXT SELECTOR - Not used - - override fun popularMangaNextPageSelector(): String? = null - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - override fun searchMangaNextPageSelector() = ".next.page-numbers" - //endregion - - //region CHAPTER and PAGES - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - if (document.select("#login > .custom-message").size > 0) throw Exception("Devi accedere al sito web con il tuo account!\nPremi WebView (il pulsante con il globo)\ne accedi normalmente") - val chapters = mutableListOf<SChapter>() - document.select(chapterListSelector()).forEachIndexed { i, it -> - chapters.add( - SChapter.create().apply { - setUrlWithoutDomain(it.attr("href")) - it.text().let { n -> - name = n - chapter_number = n.replace(Regex("OneShot|Prologo"), "0").replace("Capitolo", "").let { - if (!it.contains(Regex("[0-9]\\."))) { - it.filter { t -> t.isDigit() }.let { t -> - t.ifEmpty { "$i" } - } - } else { - it - } - }.toFloat() - } - }, - ) - } - - chapters.reverse() - return chapters - } - override fun chapterListSelector() = ".entry-content > blockquote span > strong a, .entry-content > ul > li a" - override fun chapterFromElement(element: Element) = throw Exception("Not used") - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("article > .entry-content img").forEachIndexed { i, it -> - pages.add(Page(i, "", it.attr("src"))) - } - if (document.toString().contains("che state leggendo")) { - pages.add(Page(1, "", "https://i.imgur.com/l0eZuoO.png")) - } - - return pages - } - - override fun imageUrlParse(document: Document) = "" - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - //endregion - - //region FILTERS - private class Genre(name: String, val id: String = name) : Filter.CheckBox(name) - private class GenreSelez(genres: List<Genre>) : Filter.Select<String>( - "Genere", - genres.map { - it.name - }.toTypedArray(), - 0, - ) - - override fun getFilterList() = FilterList( - Filter.Header("La ricerca testuale non accetta i filtri e viceversa"), - GenreSelez(getGenreList()), - ) - - private fun getGenreList() = listOf( - Genre("Josei", "josei"), - Genre("Sportivo", "sportivo"), - Genre("Harlequin", "harlequin"), - Genre("Ufficio", "ufficio"), - Genre("Shoujo", "shoujo"), - Genre("Fujitani Yoko", "fujitani-yoko"), - Genre("Tsukishima Haru", "tsukishima-haru"), - Genre("Scolastico", "scolastico"), - Genre("Viaggio nel Tempo", "viaggio-nel-tempo"), - Genre("Vita Scolastica", "vita-scolastica"), - Genre("Seinen", "seinen"), - Genre("Drammatico", "drammatico"), - Genre("Commedia", "commedia"), - Genre("Drama", "drama"), - Genre("Oneshot", "oneshot"), - Genre("Mistero", "mistero"), - Genre("Saori", "saori"), - Genre("Fuji Momo", "fuji-momo"), - Genre("Azione", "azione"), - Genre("Sovrannaturale", "sovrannaturale"), - Genre("Vita Quotidiana", "vita-quotidiana"), - Genre("Storico", "storico"), - Genre("Shibano Yuka", "shibano-yuka"), - Genre("Fantasy", "fantasy"), - Genre("Smut", "smut"), - Genre("Psicologico", "psicologico"), - ) - //endregion -} diff --git a/src/it/zeurelscan/AndroidManifest.xml b/src/it/zeurelscan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/it/zeurelscan/AndroidManifest.xml +++ b/src/it/zeurelscan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/comicnewtype/AndroidManifest.xml b/src/ja/comicnewtype/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/comicnewtype/AndroidManifest.xml +++ b/src/ja/comicnewtype/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/ganma/AndroidManifest.xml b/src/ja/ganma/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/ganma/AndroidManifest.xml +++ b/src/ja/ganma/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/mangacross/AndroidManifest.xml b/src/ja/mangacross/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/mangacross/AndroidManifest.xml +++ b/src/ja/mangacross/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/nicovideoseiga/AndroidManifest.xml b/src/ja/nicovideoseiga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/nicovideoseiga/AndroidManifest.xml +++ b/src/ja/nicovideoseiga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/nikkangecchan/AndroidManifest.xml b/src/ja/nikkangecchan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/nikkangecchan/AndroidManifest.xml +++ b/src/ja/nikkangecchan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/rawdevart/AndroidManifest.xml b/src/ja/rawdevart/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/rawdevart/AndroidManifest.xml +++ b/src/ja/rawdevart/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/senmanga/AndroidManifest.xml b/src/ja/senmanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ja/senmanga/AndroidManifest.xml +++ b/src/ja/senmanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ja/twi4/AndroidManifest.xml b/src/ja/twi4/AndroidManifest.xml index 6edd79bd5a..1ee0154dd6 100644 --- a/src/ja/twi4/AndroidManifest.xml +++ b/src/ja/twi4/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".ja.twi4.Twi4Activity" diff --git a/src/ja/twi4/build.gradle b/src/ja/twi4/build.gradle index 49f580e86b..747e2a7fd5 100644 --- a/src/ja/twi4/build.gradle +++ b/src/ja/twi4/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Twi4' pkgNameSuffix = 'ja.twi4' extClass = '.Twi4' - extVersionCode = 4 + extVersionCode = 5 } apply from: "$rootDir/common.gradle" diff --git a/src/ja/twi4/src/eu/kanade/tachiyomi/extension/ja/twi4/Twi4.kt b/src/ja/twi4/src/eu/kanade/tachiyomi/extension/ja/twi4/Twi4.kt index a5f43f187a..214e7cff60 100644 --- a/src/ja/twi4/src/eu/kanade/tachiyomi/extension/ja/twi4/Twi4.kt +++ b/src/ja/twi4/src/eu/kanade/tachiyomi/extension/ja/twi4/Twi4.kt @@ -27,7 +27,8 @@ class Twi4 : HttpSource() { override val name: String = "Twi4" override val supportsLatest: Boolean = false private val application: Application by injectLazy() - private val validPageTest: Regex = Regex("/comics/twi4/\\w+/works/\\d{4}\\.[0-9a-f]{32}\\.jpg") + private val validPageTest: Regex = + Regex("/comics/twi4/\\w+/works/\\d{4}\\.[0-9a-zA-Z]{32}\\.jpg") companion object Constants { const val SEARCH_PREFIX_SLUG = "SLUG:" @@ -40,47 +41,48 @@ class Twi4 : HttpSource() { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36", ).build() - // Popular manga == All manga in the site - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - parsePopularMangaRequest(response, page < 2) - } - } - - private fun parsePopularMangaRequest(response: Response, hasNextPage: Boolean): MangasPage { + // Both latest and popular only lists 4 manga in total + // As the full catalog is consists of less than 50 manga, it is not worth implementing + // We'll just list all manga in the catalog instead + override fun popularMangaParse(response: Response): MangasPage { val doc = Jsoup.parse(response.body.string()) val ret = mutableListOf<SManga>() - // One of the manga is a link to Twi4's zadankai, which is a platform for anyone to post oneshot 4-koma with judges to comment - // It has a completely different page layout and it is pretty much its own "manga site". - // Therefore, for simplicity sake. This extension (or at least this source) will not include that as a "Manga" - val mangas = doc.select("section:not(.zadankai):not([id])") - for (manga in mangas) { - ret.add( - SManga.create().apply { - thumbnail_url = - getUrlDomain() + manga.select("header > div.figgroup > figure > a > img") - .attr("src") - setUrlWithoutDomain( - getUrlDomain() + manga.select("header > div.hgroup > h3 > a").attr("href"), - ) - title = manga.select("header > div.hgroup > h3 > a > strong").text() - }, - ) + // Manga that are recently updated don't show up on the full catalog + // So we'll need to parse the recent updates section as well + val listings = arrayOf( + "#lineup_recent > div> section", + "#lineup > div > section:not(.zadankai):not([id])", + ) + for (listing in listings) { + val mangas = doc.select(listing) + for (manga in mangas) { + ret.add( + SManga.create().apply { + thumbnail_url = + getUrlDomain() + manga.select("div.figgroup > figure > a > img") + .attr("src") + setUrlWithoutDomain( + getUrlDomain() + manga.select("div.hgroup > h3 > a").attr("href"), + ) + title = manga.select("div.hgroup > h3 > a").text() + author = manga.select("div.hgroup > p").text() + status = + if (manga.select("ul:first-child > li:last-child > em.is-completed") + .isEmpty() + ) { + SManga.ONGOING + } else { + SManga.COMPLETED + } + }, + ) + } } - return MangasPage(ret, hasNextPage) + return MangasPage(ret, false) } - // We have to fetch all manga from two different pages - // One from the homepage (which contains all ongoing manga), one from the completed manga page - // The menu at the top relies on JS which JSoup doesn't load override fun popularMangaRequest(page: Int): Request { - return if (page == 1) { - GET(baseUrl, getChromeHeaders()) - } else { - GET(baseUrl + "completed.html", getChromeHeaders()) - } + return GET(baseUrl, getChromeHeaders()) } override fun mangaDetailsRequest(manga: SManga): Request = @@ -125,7 +127,7 @@ class Twi4 : HttpSource() { } } } - status = SManga.UNKNOWN + // While the status can be obtained at the home page, there is no such info at the details page } } @@ -203,16 +205,14 @@ class Twi4 : HttpSource() { .let { re.replace(it, "\"$1\":") } indexResponse.close() val indexElement = index.let { Json.parseToJsonElement(it) } - var suffix: String? = null - if (indexElement != null) { - // Each entry in the Items array corresponds to 1 chapter/page - suffix = indexElement.jsonObject["Items"]?.jsonArray?.get(chapterNum - 1)?.jsonObject?.get("Suffix")?.jsonPrimitive?.content - } + val suffix = + indexElement.jsonObject["Items"]?.jsonArray?.get(chapterNum - 1)?.jsonObject?.get("Suffix")?.jsonPrimitive?.content // Twi4's image links are a bit of a mess // Because in very rare cases, the image filename *doesn't* come with a suffix // So only attach the suffix if there is one if (suffix != null) { - imageUrl = getUrlDomain() + page.select("div > div > p > img").attr("src").dropLast(4) + suffix + ".jpg" + imageUrl = getUrlDomain() + page.select("div > div > p > img").attr("src") + .dropLast(4) + suffix + ".jpg" } } ret.add( @@ -239,7 +239,7 @@ class Twi4 : HttpSource() { // There will still be some urls that would accidentally activate the intent (like the news page), // but there's no way to avoid it. - if (slug.endsWith("html") || slug.startsWith("zadankai")) { + if (slug.endsWith("html") || slug.startsWith("zadankai") || slug.startsWith("others")) { return Observable.just(MangasPage(listOf(), false)) } return client.newCall(GET(baseUrl + slug)) @@ -268,9 +268,6 @@ class Twi4 : HttpSource() { override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - override fun popularMangaParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") diff --git a/src/ko/jmana/AndroidManifest.xml b/src/ko/jmana/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ko/jmana/AndroidManifest.xml +++ b/src/ko/jmana/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ko/navercomic/AndroidManifest.xml b/src/ko/navercomic/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ko/navercomic/AndroidManifest.xml +++ b/src/ko/navercomic/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ko/navercomic/build.gradle b/src/ko/navercomic/build.gradle index e11b5d4bb4..4f89e3d45b 100644 --- a/src/ko/navercomic/build.gradle +++ b/src/ko/navercomic/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Naver Comic' pkgNameSuffix = 'ko.navercomic' extClass = '.NaverComicFactory' - extVersionCode = 3 + extVersionCode = 4 } apply from: "$rootDir/common.gradle" diff --git a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt b/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt index 2614218aa2..57bd6e529d 100644 --- a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt +++ b/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt @@ -108,16 +108,18 @@ abstract class NaverComicBase(protected val mType: String) : ParsedHttpSource() override fun mangaDetailsParse(response: Response): SManga { val manga = json.decodeFromString<Manga>(response.body.string()) - val authors = manga.author.run { - setOf(writers, painters, originAuthors).flatten().map { it.name } - }.joinToString() + val authors = manga.communityArtists.joinToString { it.name } return SManga.create().apply { title = manga.titleName author = authors description = manga.synopsis thumbnail_url = manga.thumbnailUrl - status = if (manga.finished) SManga.COMPLETED else SManga.ONGOING + status = when { + manga.rest -> SManga.ON_HIATUS + manga.finished -> SManga.COMPLETED + else -> SManga.ONGOING + } } } @@ -215,7 +217,8 @@ data class Manga( val titleName: String, val titleId: Int, val finished: Boolean, - val author: AuthorList, + val rest: Boolean, + val communityArtists: List<Author>, val synopsis: String, ) @@ -228,15 +231,8 @@ data class MangaChallenge( val author: String, ) -@Serializable -data class AuthorList( - val writers: List<Author>, - val painters: List<Author>, - val originAuthors: List<Author>, -) - @Serializable data class Author( - val id: Int, + val artistId: Int, val name: String, ) diff --git a/src/ko/newtoki/AndroidManifest.xml b/src/ko/newtoki/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ko/newtoki/AndroidManifest.xml +++ b/src/ko/newtoki/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ko/toonkor/AndroidManifest.xml b/src/ko/toonkor/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ko/toonkor/AndroidManifest.xml +++ b/src/ko/toonkor/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/argosscan/AndroidManifest.xml b/src/pt/argosscan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/argosscan/AndroidManifest.xml +++ b/src/pt/argosscan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/bakai/AndroidManifest.xml b/src/pt/bakai/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/pt/bakai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/bakai/build.gradle b/src/pt/bakai/build.gradle deleted file mode 100644 index 5817d02723..0000000000 --- a/src/pt/bakai/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Bakai' - pkgNameSuffix = 'pt.bakai' - extClass = '.Bakai' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/bakai/res/mipmap-hdpi/ic_launcher.png b/src/pt/bakai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4dc069e591..0000000000 Binary files a/src/pt/bakai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bakai/res/mipmap-mdpi/ic_launcher.png b/src/pt/bakai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bf34eebcda..0000000000 Binary files a/src/pt/bakai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bakai/res/mipmap-xhdpi/ic_launcher.png b/src/pt/bakai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3ebae1078a..0000000000 Binary files a/src/pt/bakai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bakai/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/bakai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 22957bfab6..0000000000 Binary files a/src/pt/bakai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bakai/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/bakai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d637c67ea..0000000000 Binary files a/src/pt/bakai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bakai/res/web_hi_res_512.png b/src/pt/bakai/res/web_hi_res_512.png deleted file mode 100644 index 53a8f8dee2..0000000000 Binary files a/src/pt/bakai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/bakai/src/eu/kanade/tachiyomi/extension/pt/bakai/Bakai.kt b/src/pt/bakai/src/eu/kanade/tachiyomi/extension/pt/bakai/Bakai.kt deleted file mode 100644 index 576c450900..0000000000 --- a/src/pt/bakai/src/eu/kanade/tachiyomi/extension/pt/bakai/Bakai.kt +++ /dev/null @@ -1,138 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.bakai - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Bakai : ParsedHttpSource() { - - override val name = "Bakai" - - override val baseUrl = "https://bakai.org" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimitHost(baseUrl.toHttpUrl(), 1, 2) - .rateLimitHost(CDN_URL.toHttpUrl(), 1, 1) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") - - // Source doesn't have a popular list, so use latest instead. - override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) - - override fun popularMangaSelector(): String = latestUpdatesSelector() - - override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element) - - override fun popularMangaNextPageSelector(): String = latestUpdatesNextPageSelector() - - override fun latestUpdatesRequest(page: Int): Request { - val path = if (page > 1) "home/page/$page/" else "" - return GET("$baseUrl/$path", headers) - } - - override fun latestUpdatesSelector() = "#elCmsPageWrap ul.ipsGrid article.ipsBox" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("h2.ipsType_pageTitle a")!!.text().trim() - thumbnail_url = element.selectFirst("img.ipsImage[alt]")!!.attr("abs:src") - setUrlWithoutDomain(element.selectFirst("a[title]")!!.attr("href")) - } - - override fun latestUpdatesNextPageSelector() = - "#elCmsPageWrap ul.ipsPagination li.ipsPagination_next:not(.ipsPagination_inactive)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/search/".toHttpUrl().newBuilder() - .addQueryParameter("q", query) - .addQueryParameter("type", "cms_records1") - .addQueryParameter("search_in", "titles") - .addQueryParameter("sortby", "relevancy") - .toString() - - return GET(url, headers) - } - - override fun searchMangaSelector() = "#elSearch_main ol.ipsStream li.ipsStreamItem" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("h2.ipsStreamItem_title a")!!.text().trim() - thumbnail_url = element.selectFirst("span.ipsThumb img")!!.attr("abs:src") - setUrlWithoutDomain(element.selectFirst("h2.ipsStreamItem_title a")!!.attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.selectFirst(chapterListSelector())!! - - author = infoElement.select("p:contains(Artista:) a") - .joinToString { it.text().trim() } - genre = infoElement.selectFirst("p:contains(Tags:) span.ipsBadge + span")!!.text() - status = SManga.COMPLETED - description = infoElement.select("section.ipsType_richText p") - .joinToString("\n\n") { it.text().trim() } - thumbnail_url = infoElement.selectFirst("div.cCmsRecord_image img.ipsImage")!!.attr("abs:src") - } - - override fun chapterListSelector() = "#ipsLayout_contentWrapper article.ipsContained" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("p:contains(Tipo:) a")!!.text() - scanlator = element.select("p:contains(Tradução:) a").firstOrNull() - ?.text()?.trim() - date_upload = element.ownerDocument()!!.select("div.ipsPageHeader__meta time").firstOrNull() - ?.attr("datetime")?.toDate() ?: 0L - setUrlWithoutDomain(element.ownerDocument()!!.location()) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select(chapterListSelector() + " div.ipsGrid img.ipsImage") - .mapIndexed { i, element -> - Page(i, document.location(), element.attr("abs:data-src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun String.toDate(): Long { - return runCatching { DATE_FORMATTER.parse(trim())?.time } - .getOrNull() ?: 0L - } - - companion object { - private const val CDN_URL = "https://img.bakai.org" - - private const val ACCEPT_IMAGE = "image/webp,image/apng,image/*,*/*;q=0.8" - - private val DATE_FORMATTER by lazy { - SimpleDateFormat("yyyy-mm-dd'T'HH:mm:ss'Z'", Locale.ENGLISH) - } - } -} diff --git a/src/pt/brmangas/AndroidManifest.xml b/src/pt/brmangas/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/brmangas/AndroidManifest.xml +++ b/src/pt/brmangas/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/brmangas/build.gradle b/src/pt/brmangas/build.gradle index 8ad9c2a5e7..41f6fdab58 100644 --- a/src/pt/brmangas/build.gradle +++ b/src/pt/brmangas/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'BR Mangás' pkgNameSuffix = 'pt.brmangas' extClass = '.BrMangas' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } diff --git a/src/pt/brmangas/src/eu/kanade/tachiyomi/extension/pt/brmangas/BrMangas.kt b/src/pt/brmangas/src/eu/kanade/tachiyomi/extension/pt/brmangas/BrMangas.kt index 6d0c3f7605..701f655a7c 100644 --- a/src/pt/brmangas/src/eu/kanade/tachiyomi/extension/pt/brmangas/BrMangas.kt +++ b/src/pt/brmangas/src/eu/kanade/tachiyomi/extension/pt/brmangas/BrMangas.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -20,7 +20,7 @@ class BrMangas : ParsedHttpSource() { override val name = "BR Mangás" - override val baseUrl = "https://brmangas.com" + override val baseUrl = "https://www.brmangas.net" override val lang = "pt-BR" @@ -38,14 +38,15 @@ class BrMangas : ParsedHttpSource() { override fun popularMangaRequest(page: Int): Request { val listPath = if (page == 1) "" else "page/${page - 1}" val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/lista-de-mangas/$listPath") + .set("Referer", "$baseUrl/$listPath") .build() val pageStr = if (page != 1) "page/$page" else "" - return GET("$baseUrl/lista-de-mangas/$pageStr", newHeaders) + return GET("$baseUrl/$pageStr", newHeaders) } - override fun popularMangaSelector(): String = "div.listagem.row div.item a[title]" + override fun popularMangaSelector(): String = + "span.heading:contains(Todos os Mangás) ~ div.listagem.row div.item a[title]" override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { val thumbnailEl = element.select("img").first()!! @@ -77,13 +78,14 @@ class BrMangas : ParsedHttpSource() { override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = baseUrl.toHttpUrlOrNull()!!.newBuilder() + val url = baseUrl.toHttpUrl().newBuilder() .addQueryParameter("s", query) + .build() - return GET(url.toString(), headers) + return GET(url, headers) } - override fun searchMangaSelector() = popularMangaSelector() + override fun searchMangaSelector() = "div.listagem.row div.item a[title]" override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) diff --git a/src/pt/bruttal/AndroidManifest.xml b/src/pt/bruttal/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/bruttal/AndroidManifest.xml +++ b/src/pt/bruttal/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/goldenmangas/AndroidManifest.xml b/src/pt/goldenmangas/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/pt/goldenmangas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c3cb7433c6..0000000000 Binary files a/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 90472d8cc0..0000000000 Binary files a/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6fd5656e3f..0000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index be7df03711..0000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 54b898e077..0000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/web_hi_res_512.png b/src/pt/goldenmangas/res/web_hi_res_512.png deleted file mode 100644 index e909cd0a27..0000000000 Binary files a/src/pt/goldenmangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/hqnow/AndroidManifest.xml b/src/pt/hqnow/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/hqnow/AndroidManifest.xml +++ b/src/pt/hqnow/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/lermanga/AndroidManifest.xml b/src/pt/lermanga/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/pt/lermanga/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/pt/lermanga/build.gradle b/src/pt/lermanga/build.gradle new file mode 100644 index 0000000000..df4ba51113 --- /dev/null +++ b/src/pt/lermanga/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Ler Mangá' + pkgNameSuffix = 'pt.lermanga' + extClass = '.LerManga' + extVersionCode = 2 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/lermanga/res/mipmap-hdpi/ic_launcher.png b/src/pt/lermanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4b5b1db5c1 Binary files /dev/null and b/src/pt/lermanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/lermanga/res/mipmap-mdpi/ic_launcher.png b/src/pt/lermanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..4ec85d50f3 Binary files /dev/null and b/src/pt/lermanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/lermanga/res/mipmap-xhdpi/ic_launcher.png b/src/pt/lermanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..65aa209440 Binary files /dev/null and b/src/pt/lermanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/lermanga/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/lermanga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..cde412bd85 Binary files /dev/null and b/src/pt/lermanga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/lermanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/lermanga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..a4dbc9a97e Binary files /dev/null and b/src/pt/lermanga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/lermanga/res/web_hi_res_512.png b/src/pt/lermanga/res/web_hi_res_512.png new file mode 100644 index 0000000000..bd17f0d56d Binary files /dev/null and b/src/pt/lermanga/res/web_hi_res_512.png differ diff --git a/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt new file mode 100644 index 0000000000..2f12279a99 --- /dev/null +++ b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt @@ -0,0 +1,151 @@ +package eu.kanade.tachiyomi.extension.pt.lermanga + +import android.util.Base64 +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class LerManga : ParsedHttpSource() { + + override val name = "Ler Mangá" + + override val baseUrl = "https://lermanga.org" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + private val json: Json by injectLazy() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int): Request { + val path = if (page > 1) "page/$page/" else "" + return GET("$baseUrl/mangas/$path?orderby=views&order=desc", headers) + } + + override fun popularMangaSelector(): String = "div.film_list div.flw-item" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("h3.film-name")!!.text() + thumbnail_url = element.selectFirst("img.film-poster-img")!!.srcAttr() + setUrlWithoutDomain(element.selectFirst("a.dynamic-name")!!.attr("href")) + } + + override fun popularMangaNextPageSelector(): String = "div.wp-pagenavi > a:last-child" + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector() = "div.capitulo_recentehome" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("h3")!!.text() + thumbnail_url = element.selectFirst("img")!!.absUrl("data-src") + setUrlWithoutDomain(element.selectFirst("h3 > a")!!.attr("href")) + } + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val path = if (page > 1) "page/$page/" else "" + val url = "$baseUrl/$path".toHttpUrl().newBuilder() + .addQueryParameter("s", query) + .build() + + return GET(url, headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + val infoElement = document.selectFirst("div.capitulo_recente")!! + + title = document.select("title").text().substringBeforeLast(" - ") + genre = infoElement.select("ul.genre-list li a") + .joinToString { it.text() } + description = infoElement.selectFirst("div.boxAnimeSobreLast p:last-child")!!.ownText() + thumbnail_url = infoElement.selectFirst("div.capaMangaInfo img")!!.absUrl("src") + } + + override fun chapterListSelector() = "div.manga-chapters div.single-chapter" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.selectFirst("a")!!.text() + date_upload = element.selectFirst("small small")!!.text().toDate() + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + } + + override fun pageListParse(document: Document): List<Page> { + val pagesScript = document.selectFirst("h1.heading-header + script") + ?: return emptyList() + + val pagesJson = when { + pagesScript.hasAttr("src") -> { + pagesScript.attr("src") + .substringAfter("base64,") + .let { Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8) } + } + else -> pagesScript.data() + } + + return pagesJson + .replace(PAGES_VARIABLE_REGEX, "") + .substringBeforeLast(";") + .let { json.decodeFromString<List<String>>(it) } + .mapIndexed { index, imageUrl -> + Page(index, document.location(), imageUrl) + } + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + + private fun Element.srcAttr(): String = when { + hasAttr("data-src") -> absUrl("data-src") + else -> absUrl("src") + } + + private fun String.toDate(): Long { + return runCatching { DATE_FORMATTER.parse(trim())?.time } + .getOrNull() ?: 0L + } + + companion object { + private val PAGES_VARIABLE_REGEX = "var imagens_cap\\s*=\\s*".toRegex() + private val DATE_FORMATTER by lazy { + SimpleDateFormat("dd-MM-yyyy", Locale("pt", "BR")) + } + } +} diff --git a/src/pt/mangavibe/AndroidManifest.xml b/src/pt/mangavibe/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/pt/mangavibe/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mangavibe/build.gradle b/src/pt/mangavibe/build.gradle deleted file mode 100644 index ba098e6fa4..0000000000 --- a/src/pt/mangavibe/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'MangaVibe' - pkgNameSuffix = 'pt.mangavibe' - extClass = '.MangaVibe' - extVersionCode = 2 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mangavibe/res/mipmap-hdpi/ic_launcher.png b/src/pt/mangavibe/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f938f17d4d..0000000000 Binary files a/src/pt/mangavibe/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangavibe/res/mipmap-mdpi/ic_launcher.png b/src/pt/mangavibe/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index efcd145edf..0000000000 Binary files a/src/pt/mangavibe/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangavibe/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mangavibe/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bd5328abc5..0000000000 Binary files a/src/pt/mangavibe/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangavibe/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mangavibe/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2062d5391c..0000000000 Binary files a/src/pt/mangavibe/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangavibe/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mangavibe/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 723065302c..0000000000 Binary files a/src/pt/mangavibe/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangavibe/res/web_hi_res_512.png b/src/pt/mangavibe/res/web_hi_res_512.png deleted file mode 100644 index b003cc4354..0000000000 Binary files a/src/pt/mangavibe/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibe.kt b/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibe.kt deleted file mode 100644 index a3d6f74213..0000000000 --- a/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibe.kt +++ /dev/null @@ -1,343 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangavibe - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.Normalizer -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit -import kotlin.math.ceil - -class MangaVibe : HttpSource() { - - override val name = "MangaVibe" - - override val baseUrl = "https://mangavibe.top" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .addInterceptor(::directoryCacheIntercept) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") - - private val json: Json by injectLazy() - - private val directoryCache: MutableMap<String, String> = mutableMapOf() - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .set("Referer", "$baseUrl/mangas?Ordem=Populares") - .add("X-Page", page.toString()) - .build() - - return GET("$baseUrl/$API_PATH/data?page=medias", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = json.decodeFromString<MangaVibePopularDto>(response.body.string()) - - if (result.data.isNullOrEmpty()) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val totalPages = ceil(result.data.size.toDouble() / ITEMS_PER_PAGE) - val currentPage = response.request.header("X-Page")!!.toInt() - - val mangaList = result.data - .sortedByDescending { it.views } - .drop(ITEMS_PER_PAGE * (currentPage - 1)) - .take(ITEMS_PER_PAGE) - .map(::popularMangaFromObject) - - return MangasPage(mangaList, hasNextPage = currentPage < totalPages) - } - - private fun popularMangaFromObject(comic: MangaVibeComicDto): SManga = SManga.create().apply { - title = comic.title["romaji"] ?: comic.title["english"] ?: comic.title["native"]!! - thumbnail_url = comic.id.toThumbnailUrl() - url = "/manga/${comic.id}/${title.toSlug()}" - } - - override fun latestUpdatesRequest(page: Int): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .set("Referer", "$baseUrl/mangas?Ordem=Atualizados") - .add("X-Page", page.toString()) - .build() - - return GET("$baseUrl/$API_PATH/data?page=medias&Ordem=Atualizados", newHeaders) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = json.decodeFromString<MangaVibeLatestDto>(response.body.string()) - - if (result.data.isNullOrEmpty()) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val totalPages = ceil(result.data.size.toDouble() / ITEMS_PER_PAGE) - val currentPage = response.request.header("X-Page")!!.toInt() - - val mangaList = result.data - .asSequence() - .distinctBy { it.title } - .filter { it.mediaID.isNullOrBlank().not() } - .drop(ITEMS_PER_PAGE * (currentPage - 1)) - .take(ITEMS_PER_PAGE) - .map(::latestMangaFromObject) - .toList() - - return MangasPage(mangaList, hasNextPage = currentPage < totalPages) - } - - private fun latestMangaFromObject(chapter: MangaVibeLatestChapterDto): SManga = SManga.create().apply { - title = chapter.title!! - thumbnail_url = chapter.mediaID!!.toInt().toThumbnailUrl() - url = "/manga/${chapter.mediaID}/${chapter.title.toSlug()}" - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("X-Page", page.toString()) - .build() - - val apiUrl = "$baseUrl/$API_PATH/data".toHttpUrl().newBuilder() - .addQueryParameter("page", "medias") - .addQueryParameter("st", query) - .toString() - - return GET(apiUrl, newHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = json.decodeFromString<MangaVibePopularDto>(response.body.string()) - - if (result.data.isNullOrEmpty()) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val searchTerm = response.request.url.queryParameter("st")!! - - val mangaList = result.data - .filter { - it.title.values.any { title -> - title?.contains(searchTerm, ignoreCase = true) ?: false - } - } - .sortedByDescending { it.views } - .map(::searchMangaFromObject) - - return MangasPage(mangaList, hasNextPage = false) - } - - private fun searchMangaFromObject(comic: MangaVibeComicDto): SManga = popularMangaFromObject(comic) - - override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url - - override fun mangaDetailsRequest(manga: SManga): Request { - val comicId = manga.url.substringAfter("/manga/") - .substringBefore("/") - - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .set("Referer", "$baseUrl/mangas?Ordem=Populares") - .add("X-Id", comicId) - .build() - - return GET("$baseUrl/$API_PATH/data?page=medias", newHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = json.decodeFromString<MangaVibePopularDto>(response.body.string()) - - if (result.data.isNullOrEmpty()) { - throw Exception(COULD_NOT_PARSE_THE_MANGA) - } - - val comicId = response.request.header("X-Id")!!.toInt() - val comic = result.data.find { it.id == comicId } - ?: throw Exception(COULD_NOT_PARSE_THE_MANGA) - - return SManga.create().apply { - title = comic.title["romaji"] ?: comic.title["english"] ?: comic.title["native"]!! - description = comic.description.orEmpty() - genre = comic.genres?.joinToString(", ") - status = comic.status?.toStatus() ?: SManga.UNKNOWN - thumbnail_url = comic.id.toThumbnailUrl() - } - } - - // Chapters are available in the same url of the manga details. - override fun chapterListRequest(manga: SManga): Request { - val comicId = manga.url.substringAfter("/manga/") - .substringBefore("/") - - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_JSON) - .set("Referer", baseUrl + manga.url) - .build() - - return GET("$baseUrl/$API_PATH/data?page=chapter&mediaID=$comicId", newHeaders) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val result = json.decodeFromString<MangaVibeChapterListDto>(response.body.string()) - - if (result.data.isNullOrEmpty()) { - return emptyList() - } - - return result.data - .map(::chapterFromObject) - .reversed() - } - - private fun chapterFromObject(chapter: MangaVibeChapterDto): SChapter = SChapter.create().apply { - name = "Capítulo #" + chapter.number.toString().replace(".0", "") - chapter_number = chapter.number - date_upload = chapter.datePublished?.toDate() ?: 0L - - val chapterUrl = "$baseUrl/chapter".toHttpUrl().newBuilder() - .addPathSegment(chapter.mediaID.toString()) - .addPathSegment(chapter.title?.toSlug() ?: "null") - .addPathSegment(chapter.number.toString().replace(".0", "")) - .addQueryParameter("pgn", chapter.pages.toString()) - .toString() - setUrlWithoutDomain(chapterUrl) - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val chapterUrlPaths = chapter.url - .removePrefix("/") - .split("/") - - val comicId = chapterUrlPaths[1] - val chapterNumber = chapterUrlPaths[3].substringBefore("?") - val pageCount = chapter.url.substringAfterLast("?pgn=").toInt() - - val pages = List(pageCount) { i -> - val pageUrl = "$CDN_URL/img/media/$comicId/chapter/$chapterNumber/${i + 1}.jpg" - Page(i, baseUrl, pageUrl) - } - - return Observable.just(pages) - } - - override fun pageListParse(response: Response): List<Page> = - throw Exception("This method should not be called!") - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun directoryCacheIntercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().contains("data?page=medias")) { - return chain.proceed(chain.request()) - } - - val directoryType = if (chain.request().url.queryParameter("Ordem") == null) { - POPULAR_KEY - } else { - LATEST_KEY - } - val page = chain.request().header("X-Page")?.toInt() - - if (directoryCache.containsKey(directoryType) && page != null && page > 1) { - val jsonContentType = "application/json; charset=UTF-8".toMediaTypeOrNull() - val responseBody = directoryCache[directoryType]!!.toResponseBody(jsonContentType) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() - } - - val response = chain.proceed(chain.request()) - val responseContentType = response.body.contentType() - val responseString = response.body.string() - - directoryCache[directoryType] = responseString - - return response.newBuilder() - .body(responseString.toResponseBody(responseContentType)) - .build() - } - - private fun Int.toThumbnailUrl(): String = "$CDN_URL/img/media/$this/cover/l.jpg" - - private fun String.toDate(): Long { - return runCatching { DATE_FORMATTER.parse(substringBefore("T"))?.time } - .getOrNull() ?: 0L - } - - private fun String.toStatus(): Int = when (this) { - "Em lançamento" -> SManga.ONGOING - "Completo" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.toSlug(): String { - return Normalizer - .normalize(this, Normalizer.Form.NFD) - .replace("[^\\p{ASCII}]".toRegex(), "") - .replace("[^a-zA-Z0-9\\s]+".toRegex(), "").trim() - .replace("\\s+".toRegex(), "-") - .lowercase(Locale("pt", "BR")) - } - - companion object { - private const val API_PATH = "mangavibe/api/v1" - private const val CDN_URL = "https://cdn.mangavibe.top" - - private const val ACCEPT_JSON = "application/json, text/plain, */*" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - - private const val ITEMS_PER_PAGE = 24 - - private const val COULD_NOT_PARSE_THE_MANGA = "Ocorreu um erro ao obter as informações." - - private const val POPULAR_KEY = "popular" - private const val LATEST_KEY = "latest" - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - } -} diff --git a/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibeDto.kt b/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibeDto.kt deleted file mode 100644 index 7a1132fecb..0000000000 --- a/src/pt/mangavibe/src/eu/kanade/tachiyomi/extension/pt/mangavibe/MangaVibeDto.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangavibe - -import kotlinx.serialization.Serializable - -typealias MangaVibePopularDto = MangaVibeResultDto<List<MangaVibeComicDto>> -typealias MangaVibeLatestDto = MangaVibeResultDto<List<MangaVibeLatestChapterDto>> -typealias MangaVibeChapterListDto = MangaVibeResultDto<List<MangaVibeChapterDto>> - -@Serializable -data class MangaVibeResultDto<T>( - val data: T? = null, -) - -@Serializable -data class MangaVibeComicDto( - val description: String? = "", - val genres: List<String>? = emptyList(), - val id: Int, - val status: String? = "", - val title: Map<String, String?> = emptyMap(), - val views: Int = -1, -) - -@Serializable -data class MangaVibeLatestChapterDto( - val mediaID: String? = "", - val title: String? = "", -) - -@Serializable -data class MangaVibeChapterDto( - val datePublished: String? = "", - val mediaID: Int = -1, - val number: Float = -1f, - val pages: Int = -1, - val title: String? = "", -) diff --git a/src/pt/mizumangas/AndroidManifest.xml b/src/pt/mizumangas/AndroidManifest.xml deleted file mode 100644 index 389c51256f..0000000000 --- a/src/pt/mizumangas/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> - diff --git a/src/pt/mizumangas/build.gradle b/src/pt/mizumangas/build.gradle deleted file mode 100644 index 6e9b523106..0000000000 --- a/src/pt/mizumangas/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Mizu Mangás' - pkgNameSuffix = 'pt.mizumangas' - extClass = '.MizuMangas' - extVersionCode = 30 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mizumangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/mizumangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5d42062fe2..0000000000 Binary files a/src/pt/mizumangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mizumangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/mizumangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 329bc16f5a..0000000000 Binary files a/src/pt/mizumangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mizumangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mizumangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1d7acdfc34..0000000000 Binary files a/src/pt/mizumangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mizumangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mizumangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8c36f7fb2c..0000000000 Binary files a/src/pt/mizumangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mizumangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mizumangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9da1e00530..0000000000 Binary files a/src/pt/mizumangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mizumangas/res/web_hi_res_512.png b/src/pt/mizumangas/res/web_hi_res_512.png deleted file mode 100644 index 20ffda1c3e..0000000000 Binary files a/src/pt/mizumangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangas.kt b/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangas.kt deleted file mode 100644 index 497a823f09..0000000000 --- a/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangas.kt +++ /dev/null @@ -1,151 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mizumangas - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.util.concurrent.TimeUnit - -class MizuMangas : HttpSource() { - - override val name = "Mizu Mangás" - - override val baseUrl = "https://mizumangas.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = true - - // Migrated from Madara to a custom CMS. - override val versionId = 2 - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimitHost(baseUrl.toHttpUrl(), 1, 1, TimeUnit.SECONDS) - .rateLimitHost(API_URL.toHttpUrl(), 1, 1, TimeUnit.SECONDS) - .rateLimitHost(CDN_URL.toHttpUrl(), 1, 2, TimeUnit.SECONDS) - .build() - - private val json: Json by injectLazy() - - private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() } - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - private fun apiHeadersBuilder(): Headers.Builder = headersBuilder() - .add("Accept", ACCEPT_JSON) - - /** - * The site doesn't have a popular section, so we use latest instead. - */ - override fun popularMangaRequest(page: Int) = latestUpdatesRequest(page) - - override fun popularMangaParse(response: Response) = latestUpdatesParse(response) - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$API_URL/manga?page=$page&per_page=60", apiHeaders) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs<MizuMangasPaginatedContent<MizuMangasWorkDto>>() - val workList = result.data.map(MizuMangasWorkDto::toSManga) - - return MangasPage(workList, result.hasNextPage) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // The search with query isn't working in their direct API for some reason, - // so we use their site wrapped API instead for now. - val apiUrl = "$API_URL/search".toHttpUrl().newBuilder() - .addPathSegment(query) - .build() - - return GET(apiUrl, apiHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = response.parseAs<MizuMangasSearchDto>() - val workList = result.mangas.map(MizuMangasWorkDto::toSManga) - - return MangasPage(workList, hasNextPage = false) - } - - override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url - - override fun mangaDetailsRequest(manga: SManga): Request { - val id = manga.url.substringAfter("/manga/") - - return GET("$API_URL/manga/$id", apiHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - return response.parseAs<MizuMangasWorkDto>().toSManga() - } - - override fun chapterListRequest(manga: SManga): Request { - val id = manga.url.substringAfter("/manga/") - - return GET("$API_URL/chapter/manga/all/$id", apiHeaders) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return response.parseAs<List<MizuMangasChapterDto>>() - .map(MizuMangasChapterDto::toSChapter) - } - - override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url - - override fun pageListRequest(chapter: SChapter): Request { - val id = chapter.url.substringAfter("/reader/") - - return GET("$API_URL/chapter/$id") - } - - override fun pageListParse(response: Response): List<Page> { - val result = response.parseAs<MizuMangasChapterDto>() - val chapterUrl = "$baseUrl/manga/reader/${result.id}" - - return result.pages.mapIndexed { i, pageDto -> - Page(i, chapterUrl, "$CDN_URL/${pageDto.page}") - } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private inline fun <reified T> Response.parseAs(): T = use { - json.decodeFromString(it.body.string()) - } - - companion object { - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_JSON = "application/json" - - private const val API_URL = "https://api.mizumangas.com.br" - const val CDN_URL = "https://cdn.mizumangas.com.br" - } -} diff --git a/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangasDto.kt b/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangasDto.kt deleted file mode 100644 index b3a41391b4..0000000000 --- a/src/pt/mizumangas/src/eu/kanade/tachiyomi/extension/pt/mizumangas/MizuMangasDto.kt +++ /dev/null @@ -1,85 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mizumangas - -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -@Serializable -data class MizuMangasPaginatedContent<T>( - @SerialName("current_page") val currentPage: Int, - val data: List<T> = emptyList(), - @SerialName("last_page") val lastPage: Int, -) { - - val hasNextPage: Boolean - get() = currentPage < lastPage -} - -@Serializable -data class MizuMangasSearchDto(val mangas: List<MizuMangasWorkDto>) - -@Serializable -data class MizuMangasWorkDto( - val id: Int, - val photo: String? = null, - val synopsis: String? = null, - val name: String, - val status: MizuMangasStatusDto? = null, - val categories: List<MizuMangasCategoryDto> = emptyList(), - val people: List<MizuMangasStaffDto> = emptyList(), -) { - - fun toSManga(): SManga = SManga.create().apply { - title = name - author = people.joinToString { it.name } - description = synopsis - genre = categories.joinToString { it.name } - status = when (this@MizuMangasWorkDto.status?.name) { - "Ativo" -> SManga.ONGOING - "Completo" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - thumbnail_url = "${MizuMangas.CDN_URL}/$photo" - url = "/manga/$id" - } -} - -@Serializable -data class MizuMangasStatusDto(val name: String) - -@Serializable -data class MizuMangasCategoryDto(val name: String) - -@Serializable -data class MizuMangasStaffDto(val name: String) - -@Serializable -data class MizuMangasChapterDto( - val id: Int, - @SerialName("chapter") val number: String, - @SerialName("created_at") val createdAt: String? = null, - @SerialName("manga_pages") val pages: List<MizuMangasPageDto> = emptyList(), -) { - - fun toSChapter(): SChapter = SChapter.create().apply { - name = "Capítulo $number" - chapter_number = number.toFloatOrNull() ?: -1f - date_upload = runCatching { DATE_FORMATTER.parse(createdAt!!)?.time } - .getOrNull() ?: 0L - url = "/manga/reader/$id" - } - - companion object { - private val DATE_FORMATTER by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.US) - .apply { timeZone = TimeZone.getTimeZone("UTC") } - } - } -} - -@Serializable -data class MizuMangasPageDto(val page: String) diff --git a/src/pt/muitohentai/AndroidManifest.xml b/src/pt/muitohentai/AndroidManifest.xml index 389c51256f..8072ee00db 100644 --- a/src/pt/muitohentai/AndroidManifest.xml +++ b/src/pt/muitohentai/AndroidManifest.xml @@ -1,3 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> - +<manifest /> diff --git a/src/pt/mundohentai/AndroidManifest.xml b/src/pt/mundohentai/AndroidManifest.xml index 389c51256f..8072ee00db 100644 --- a/src/pt/mundohentai/AndroidManifest.xml +++ b/src/pt/mundohentai/AndroidManifest.xml @@ -1,3 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> - +<manifest /> diff --git a/src/pt/mundowebtoon/AndroidManifest.xml b/src/pt/mundowebtoon/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/pt/mundowebtoon/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mundowebtoon/build.gradle b/src/pt/mundowebtoon/build.gradle deleted file mode 100644 index 11a4d79ffe..0000000000 --- a/src/pt/mundowebtoon/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mundo Webtoon' - pkgNameSuffix = 'pt.mundowebtoon' - extClass = '.MundoWebtoon' - extVersionCode = 7 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mundowebtoon/res/mipmap-hdpi/ic_launcher.png b/src/pt/mundowebtoon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cf3086fc28..0000000000 Binary files a/src/pt/mundowebtoon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundowebtoon/res/mipmap-mdpi/ic_launcher.png b/src/pt/mundowebtoon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1be09afbb2..0000000000 Binary files a/src/pt/mundowebtoon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundowebtoon/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mundowebtoon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index fa73a97e3a..0000000000 Binary files a/src/pt/mundowebtoon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundowebtoon/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mundowebtoon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3d1facb3c1..0000000000 Binary files a/src/pt/mundowebtoon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundowebtoon/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mundowebtoon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index eb0012f9a2..0000000000 Binary files a/src/pt/mundowebtoon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundowebtoon/res/web_hi_res_512.png b/src/pt/mundowebtoon/res/web_hi_res_512.png deleted file mode 100644 index 2d0cb3f1d1..0000000000 Binary files a/src/pt/mundowebtoon/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mundowebtoon/src/eu/kanade/tachiyomi/extension/pt/mundowebtoon/MundoWebtoon.kt b/src/pt/mundowebtoon/src/eu/kanade/tachiyomi/extension/pt/mundowebtoon/MundoWebtoon.kt deleted file mode 100644 index 7cb35ef45a..0000000000 --- a/src/pt/mundowebtoon/src/eu/kanade/tachiyomi/extension/pt/mundowebtoon/MundoWebtoon.kt +++ /dev/null @@ -1,217 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mundowebtoon - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MundoWebtoon : ParsedHttpSource() { - - override val name = "Mundo Webtoon" - - override val baseUrl = "https://mundowebtoon.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(::sanitizeHtmlIntercept) - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = - "div.section:contains(mais lídos) + div.section div.andro_product" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h6.andro_product-title small").text().withoutLanguage() - thumbnail_url = element.select("div.andro_product-thumb img").srcAttr() - setUrlWithoutDomain(element.select("div.andro_product-thumb > a").attr("abs:href")) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request { - val path = if (page > 1) "/index.php?pagina=$page" else "" - return GET("$baseUrl$path", headers) - } - - override fun latestUpdatesSelector() = "div.row.atualizacoes div.andro_product" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h5.andro_product-title").text().withoutLanguage() - thumbnail_url = element.select("div.andro_product-thumb img").srcAttr() - setUrlWithoutDomain(element.select("div.andro_product-thumb > a").attr("abs:href")) - } - - override fun latestUpdatesNextPageSelector() = "ul.paginacao li:last-child:not(.active) a" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headers.newBuilder() - .set("Referer", "$baseUrl/mangas") - .build() - - val url = "$baseUrl/mangas".toHttpUrl().newBuilder() - .addQueryParameter("busca", query) - .toString() - - return GET(url, newHeaders) - } - - override fun searchMangaSelector() = "div.container div.andro_product" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("span.andro_product-title").text().withoutLanguage() - thumbnail_url = element.select("div.andro_product-thumb img").srcAttr() - setUrlWithoutDomain(element.select("div.andro_product-thumb > a").attr("abs:href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.selectFirst("div.andro_product-single-content")!! - - title = infoElement.select("div.mangaTitulo h3").text().withoutLanguage() - author = infoElement.select("div.BlDataItem a[href*=autor]") - .joinToString(", ") { it.text() } - artist = infoElement.select("div.BlDataItem a[href*=artista]") - .joinToString(", ") { it.text() } - genre = infoElement.select("div.col-md-12 a.label-warning[href*=genero]").toList() - .filter { it.text().isNotEmpty() } - .joinToString { it.text().trim() } - status = infoElement.selectFirst("div.BlDataItem a[href*=status]") - ?.text()?.toStatus() ?: SManga.UNKNOWN - description = infoElement.select("div.andro_product-excerpt").text() - thumbnail_url = document.select("div.andro_product-single-thumb img").srcAttr() - } - - override fun chapterListSelector() = "div.CapitulosListaTodos div.CapitulosListaItem" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.selectFirst("h5")!!.ownText() - scanlator = element.select("a.color_gray[target='_blank']") - .joinToString(", ") { it.text() } - date_upload = element.select("h5 span[style]").text().toDate() - setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href")) - } - - override fun pageListRequest(chapter: SChapter): Request { - val chapterUrl = (baseUrl + chapter.url).toHttpUrl() - - val payload = FormBody.Builder() - .add("data", chapterUrl.pathSegments[1]) - .add("num", chapterUrl.pathSegments[2]) - .add("modo", "1") - .add("busca", "img") - .build() - - val newHeaders = headersBuilder() - .add("Content-Length", payload.contentLength().toString()) - .add("Content-Type", payload.contentType().toString()) - .set("Referer", baseUrl + chapter.url) - .build() - - return POST("$baseUrl/leitor_image.php", newHeaders, payload) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("img[pag]") - .mapIndexed { i, element -> - Page(i, document.location(), element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun sanitizeHtmlIntercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(chain.request()) - - if (!response.headers["Content-Type"].orEmpty().contains("text/html")) { - return response - } - - val newBody = response.body.string() - .replace("\t", "") - .replace(SCRIPT_REGEX, "") - .replace(HEAD_REGEX, "<head></head>") - .replace(COMMENT_REGEX, "") - .toResponseBody(HTML_MEDIA_TYPE) - - response.close() - - return response.newBuilder() - .body(newBody) - .build() - } - - private fun Elements.srcAttr(): String = - attr(if (hasAttr("data-src")) "data-src" else "src") - - private fun String.toDate(): Long { - return runCatching { DATE_FORMATTER.parse(trim())?. time } - .getOrNull() ?: 0L - } - - private fun String.toStatus() = when (this) { - "Ativo" -> SManga.ONGOING - "Completo" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.withoutLanguage(): String = replace(FLAG_REGEX, "").trim() - - companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/webp,image/apng,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - - private val FLAG_REGEX = "\\((Pt[-/]br|Scan)\\)".toRegex(RegexOption.IGNORE_CASE) - private val SCRIPT_REGEX = "<script>.*</script>" - .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) - private val HEAD_REGEX = "<head>.*</head>" - .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) - private val COMMENT_REGEX = "<!--.*-->".toRegex(RegexOption.MULTILINE) - - private val HTML_MEDIA_TYPE = "text/html".toMediaType() - - private val DATE_FORMATTER by lazy { - SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) - } - } -} diff --git a/src/pt/nixmangas/AndroidManifest.xml b/src/pt/nixmangas/AndroidManifest.xml deleted file mode 100644 index 389c51256f..0000000000 --- a/src/pt/nixmangas/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> - diff --git a/src/pt/nixmangas/build.gradle b/src/pt/nixmangas/build.gradle deleted file mode 100644 index a161d07131..0000000000 --- a/src/pt/nixmangas/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Nix Mangás' - pkgNameSuffix = 'pt.nixmangas' - extClass = '.NixMangas' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/nixmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/nixmangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84ed291912..0000000000 Binary files a/src/pt/nixmangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/nixmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/nixmangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 008d349da3..0000000000 Binary files a/src/pt/nixmangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/nixmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/nixmangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f7d413ee65..0000000000 Binary files a/src/pt/nixmangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/nixmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/nixmangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index cc0a1bd6ff..0000000000 Binary files a/src/pt/nixmangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/nixmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/nixmangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1e4dc71527..0000000000 Binary files a/src/pt/nixmangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/nixmangas/res/web_hi_res_512.png b/src/pt/nixmangas/res/web_hi_res_512.png deleted file mode 100644 index 3f20f665a2..0000000000 Binary files a/src/pt/nixmangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangas.kt b/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangas.kt deleted file mode 100644 index 47711914f0..0000000000 --- a/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangas.kt +++ /dev/null @@ -1,165 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.nixmangas - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.util.concurrent.TimeUnit - -class NixMangas : HttpSource() { - - override val name = "Nix Mangás" - - override val baseUrl = "https://nixmangas.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimitHost(baseUrl.toHttpUrl(), 1, 1, TimeUnit.SECONDS) - .rateLimitHost(API_URL.toHttpUrl(), 1, 1, TimeUnit.SECONDS) - .rateLimitHost(CDN_URL.toHttpUrl(), 1, 2, TimeUnit.SECONDS) - .build() - - private val json: Json by injectLazy() - - private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() } - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - private fun apiHeadersBuilder(): Headers.Builder = headersBuilder() - .add("Accept", ACCEPT_JSON) - - /** - * The site doesn't have a popular section, so we use latest instead. - */ - override fun popularMangaRequest(page: Int) = latestUpdatesRequest(page) - - override fun popularMangaParse(response: Response) = latestUpdatesParse(response) - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$API_URL/mangas?page=$page", apiHeaders) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs<NixMangasPaginatedContent<NixMangasWorkDto>>() - val workList = result.data.map(NixMangasWorkDto::toSManga) - - return MangasPage(workList, result.hasNextPage) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // The search with query isn't working in their direct API for some reason, - // so we use their site wrapped API instead for now. - val apiUrl = "$API_URL/mangas".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("search", query) - .build() - - return GET(apiUrl, apiHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage = latestUpdatesParse(response) - - override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url - - override fun mangaDetailsRequest(manga: SManga): Request { - val slug = manga.url.substringAfter("/obras/") - - return GET("$API_URL/mangas/$slug/details", apiHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.parseAs<NixMangasDetailsDto>() - - return result.manga.toSManga() - } - - override fun chapterListRequest(manga: SManga): Request { - val slug = manga.url.substringAfter("/obras/") - - return chapterListPaginatedRequest(slug) - } - - private fun chapterListPaginatedRequest(slug: String, page: Int = 1): Request { - return GET("$API_URL/mangas/$slug/chapters?page=$page", apiHeaders) - } - - override fun chapterListParse(response: Response): List<SChapter> { - var result = response.parseAs<NixMangasPaginatedContent<NixMangasChapterDto>>() - val currentTimeStamp = System.currentTimeMillis() - val chapters = result.data.toMutableList() - val slug = response.request.url.pathSegments[2] - - while (result.currentPage < result.lastPage) { - val nextRequest = chapterListPaginatedRequest(slug, result.currentPage + 1) - result = client.newCall(nextRequest).execute().parseAs() - - chapters += result.data - } - - return chapters - .filter { it.isPublished } - .map(NixMangasChapterDto::toSChapter) - .filter { it.date_upload <= currentTimeStamp } - .sortedByDescending(SChapter::chapter_number) - } - - override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url - - override fun pageListRequest(chapter: SChapter): Request { - val id = chapter.url.substringAfter("/ler/") - - return GET("$API_URL/chapters/$id") - } - - override fun pageListParse(response: Response): List<Page> { - val result = response.parseAs<NixMangasReaderDto>() - val chapterUrl = "$baseUrl/ler/${result.chapter.slug}" - - return result.chapter.pages.mapIndexed { i, pageDto -> - Page(i, chapterUrl, pageDto.pageUrl) - } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .add("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private inline fun <reified T> Response.parseAs(): T = use { - json.decodeFromString(it.body.string()) - } - - companion object { - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_JSON = "application/json" - - private const val API_URL = "https://api.nixmangas.com/v1" - private const val CDN_URL = "https://cdn.nixmangas.com" - } -} diff --git a/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangasDto.kt b/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangasDto.kt deleted file mode 100644 index a33af7489b..0000000000 --- a/src/pt/nixmangas/src/eu/kanade/tachiyomi/extension/pt/nixmangas/NixMangasDto.kt +++ /dev/null @@ -1,88 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.nixmangas - -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -@Serializable -data class NixMangasPaginatedContent<T>( - @SerialName("current_page") val currentPage: Int, - val data: List<T> = emptyList(), - @SerialName("last_page") val lastPage: Int, -) { - - val hasNextPage: Boolean - get() = currentPage < lastPage -} - -@Serializable -data class NixMangasSearchDto(val mangas: NixMangasPaginatedContent<NixMangasWorkDto>) - -@Serializable -data class NixMangasDetailsDto(val manga: NixMangasWorkDto) - -@Serializable -data class NixMangasReaderDto(val chapter: NixMangasChapterDto) - -@Serializable -data class NixMangasWorkDto( - val id: String, - val chapters: List<NixMangasChapterDto> = emptyList(), - val cover: String? = null, - val genres: List<NixMangasGenreDto> = emptyList(), - @SerialName("is_adult") val isAdult: Boolean = false, - val slug: String, - val status: String? = null, - @SerialName("synopses") val synopsis: String? = null, - val thumbnail: String, - val title: String, -) { - - fun toSManga(): SManga = SManga.create().apply { - title = this@NixMangasWorkDto.title - description = synopsis - genre = genres.joinToString { it.name } - status = when (this@NixMangasWorkDto.status) { - "ACTIVE" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - thumbnail_url = cover - url = "/obras/$slug" - } -} - -@Serializable -data class NixMangasGenreDto(val name: String) - -@Serializable -data class NixMangasChapterDto( - val id: String, - @SerialName("is_published") val isPublished: Boolean, - val number: Float, - val pages: List<NixMangasPageDto> = emptyList(), - val slug: String, - @SerialName("published_at") val publishedAt: String? = null, -) { - - fun toSChapter(): SChapter = SChapter.create().apply { - name = "Capítulo ${number.toString().replace(".0", "")}" - chapter_number = number - date_upload = runCatching { DATE_FORMATTER.parse(publishedAt!!)?.time } - .getOrNull() ?: 0L - url = "/ler/$id" - } - - companion object { - private val DATE_FORMATTER by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.US) - .apply { timeZone = TimeZone.getTimeZone("UTC") } - } - } -} - -@Serializable -data class NixMangasPageDto(@SerialName("page_url") val pageUrl: String) diff --git a/src/pt/opex/AndroidManifest.xml b/src/pt/opex/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/opex/AndroidManifest.xml +++ b/src/pt/opex/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/saikaiscan/AndroidManifest.xml b/src/pt/saikaiscan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/pt/saikaiscan/AndroidManifest.xml +++ b/src/pt/saikaiscan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/pt/saikaiscan/build.gradle b/src/pt/saikaiscan/build.gradle index a3fc8f9849..e6e60d779b 100644 --- a/src/pt/saikaiscan/build.gradle +++ b/src/pt/saikaiscan/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Saikai Scan' pkgNameSuffix = 'pt.saikaiscan' extClass = '.SaikaiScan' - extVersionCode = 10 + extVersionCode = 11 } apply from: "$rootDir/common.gradle" diff --git a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt index 893a9e4c8d..3e0dc7760d 100644 --- a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt +++ b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt @@ -22,7 +22,7 @@ class SaikaiScan : HttpSource() { override val name = SOURCE_NAME - override val baseUrl = "https://saikai.com.br" + override val baseUrl = "https://saikaiscans.net" override val lang = "pt-BR" @@ -51,7 +51,7 @@ class SaikaiScan : HttpSource() { .addQueryParameter("page", page.toString()) .addQueryParameter("per_page", PER_PAGE) .addQueryParameter("relationships", "language,type,format") - .toString() + .build() return GET(apiEndpointUrl, apiHeaders) } @@ -60,9 +60,8 @@ class SaikaiScan : HttpSource() { val result = response.parseAs<SaikaiScanPaginatedStoriesDto>() val mangaList = result.data!!.map(SaikaiScanStoryDto::toSManga) - val hasNextPage = result.meta!!.currentPage < result.meta.lastPage - return MangasPage(mangaList, hasNextPage) + return MangasPage(mangaList, result.hasNextPage) } override fun latestUpdatesRequest(page: Int): Request { @@ -75,7 +74,7 @@ class SaikaiScan : HttpSource() { .addQueryParameter("page", page.toString()) .addQueryParameter("per_page", PER_PAGE) .addQueryParameter("relationships", "language,type,format,latestReleases.separator") - .toString() + .build() return GET(apiEndpointUrl, apiHeaders) } @@ -99,7 +98,7 @@ class SaikaiScan : HttpSource() { filters.filterIsInstance<UrlQueryFilter>() .forEach { it.addQueryParameter(apiEndpointUrl) } - return GET(apiEndpointUrl.toString(), apiHeaders) + return GET(apiEndpointUrl.build(), apiHeaders) } override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) @@ -118,7 +117,7 @@ class SaikaiScan : HttpSource() { .addQueryParameter("slug", storySlug) .addQueryParameter("per_page", "1") .addQueryParameter("relationships", "language,type,format,artists,status") - .toString() + .build() return GET(apiEndpointUrl, apiHeaders) } @@ -141,7 +140,7 @@ class SaikaiScan : HttpSource() { .addQueryParameter("slug", storySlug) .addQueryParameter("per_page", "1") .addQueryParameter("relationships", "releases") - .toString() + .build() return GET(apiEndpointUrl, apiHeaders) } @@ -169,7 +168,7 @@ class SaikaiScan : HttpSource() { val apiEndpointUrl = "$API_URL/api/releases/$releaseId".toHttpUrl().newBuilder() .addQueryParameter("relationships", "releaseImages") - .toString() + .build() return GET(apiEndpointUrl, apiHeaders) } @@ -177,7 +176,7 @@ class SaikaiScan : HttpSource() { override fun pageListParse(response: Response): List<Page> { val result = response.parseAs<SaikaiScanReleaseResultDto>() - return result.data!!.releaseImages.mapIndexed { i, obj -> + return result.data?.releaseImages.orEmpty().mapIndexed { i, obj -> Page(i, "", "$IMAGE_SERVER_URL/${obj.image}") } } @@ -295,7 +294,7 @@ class SaikaiScan : HttpSource() { private const val COMIC_FORMAT_ID = "2" private const val PER_PAGE = "12" - private const val API_URL = "https://api.saikai.com.br" - const val IMAGE_SERVER_URL = "https://s3-alpha.saikai.com.br" + private const val API_URL = "https://api.saikaiscans.net" + const val IMAGE_SERVER_URL = "https://s3-alpha.saikaiscans.net" } } diff --git a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScanDto.kt b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScanDto.kt index bb9f25c40f..ab48b86e4a 100644 --- a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScanDto.kt +++ b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScanDto.kt @@ -12,7 +12,11 @@ import java.util.Locale data class SaikaiScanResultDto<T>( val data: T? = null, val meta: SaikaiScanMetaDto? = null, -) +) { + + val hasNextPage: Boolean + get() = meta !== null && meta.currentPage < meta.lastPage +} typealias SaikaiScanPaginatedStoriesDto = SaikaiScanResultDto<List<SaikaiScanStoryDto>> typealias SaikaiScanReleaseResultDto = SaikaiScanResultDto<SaikaiScanReleaseDto> diff --git a/src/pt/taosect/AndroidManifest.xml b/src/pt/taosect/AndroidManifest.xml index 77c0c33893..b79ee94936 100644 --- a/src/pt/taosect/AndroidManifest.xml +++ b/src/pt/taosect/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/pt/yomumangas/AndroidManifest.xml b/src/pt/yomumangas/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/pt/yomumangas/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/pt/yomumangas/build.gradle b/src/pt/yomumangas/build.gradle new file mode 100644 index 0000000000..8b3230f43b --- /dev/null +++ b/src/pt/yomumangas/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Yomu Mangás' + pkgNameSuffix = 'pt.yomumangas' + extClass = '.YomuMangas' + extVersionCode = 3 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/yomumangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/yomumangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..47013f3484 Binary files /dev/null and b/src/pt/yomumangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/yomumangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/yomumangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..dcd5ab52a7 Binary files /dev/null and b/src/pt/yomumangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/yomumangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/yomumangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..1d3ce2077a Binary files /dev/null and b/src/pt/yomumangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/yomumangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/yomumangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3210653980 Binary files /dev/null and b/src/pt/yomumangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/yomumangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/yomumangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..91ec7f3a6b Binary files /dev/null and b/src/pt/yomumangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/yomumangas/res/web_hi_res_512.png b/src/pt/yomumangas/res/web_hi_res_512.png new file mode 100644 index 0000000000..e59253d0d3 Binary files /dev/null and b/src/pt/yomumangas/res/web_hi_res_512.png differ diff --git a/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangas.kt b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangas.kt new file mode 100644 index 0000000000..d112f02719 --- /dev/null +++ b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangas.kt @@ -0,0 +1,208 @@ +package eu.kanade.tachiyomi.extension.pt.yomumangas + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.injectLazy +import java.util.concurrent.TimeUnit + +class YomuMangas : HttpSource() { + + override val name = "Yomu Mangás" + + override val baseUrl = "https://yomumangas.com" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 1, 1, TimeUnit.SECONDS) + .rateLimitHost(API_URL.toHttpUrl(), 1, 1, TimeUnit.SECONDS) + .rateLimitHost(CDN_URL.toHttpUrl(), 1, 2, TimeUnit.SECONDS) + .build() + + private val json: Json by injectLazy() + + private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() } + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Origin", baseUrl) + .add("Referer", baseUrl) + + private fun apiHeadersBuilder(): Headers.Builder = headersBuilder() + .add("Accept", ACCEPT_JSON) + + override fun popularMangaRequest(page: Int): Request { + return GET("$API_URL/mangas/home", apiHeaders) + } + + override fun popularMangaParse(response: Response): MangasPage { + val result = response.parseAs<YomuMangasHomeDto>() + val seriesList = result.votes.map(YomuMangasSeriesDto::toSManga) + + return MangasPage(seriesList, hasNextPage = false) + } + + override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page) + + override fun latestUpdatesParse(response: Response): MangasPage { + val result = response.parseAs<YomuMangasHomeDto>() + val seriesList = result.updates.map(YomuMangasSeriesDto::toSManga) + + return MangasPage(seriesList, hasNextPage = false) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val apiUrl = "$API_URL/mangas/search".toHttpUrl().newBuilder() + .addQueryParameter("query", query) + .addQueryParameter("page", page.toString()) + + filters.filterIsInstance<UrlQueryFilter>() + .forEach { it.addQueryParameter(apiUrl) } + + return GET(apiUrl.build(), apiHeaders) + } + + override fun searchMangaParse(response: Response): MangasPage { + val result = response.parseAs<YomuMangasSearchDto>() + val seriesList = result.mangas.map(YomuMangasSeriesDto::toSManga) + + return MangasPage(seriesList, result.hasNextPage) + } + + override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url + + override fun mangaDetailsRequest(manga: SManga): Request { + val id = manga.url + .substringAfter("/manga/") + .substringBefore("/") + + return GET("$API_URL/mangas/$id", apiHeaders) + } + + override fun mangaDetailsParse(response: Response): SManga { + return response.parseAs<YomuMangasDetailsDto>().manga.toSManga() + } + + override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) + + private fun chapterListApiRequest(mangaId: Int): Request { + return GET("$API_URL/mangas/$mangaId/chapters", apiHeaders) + } + + override fun chapterListParse(response: Response): List<SChapter> { + val series = response.parseAs<YomuMangasDetailsDto>().manga + + return client.newCall(chapterListApiRequest(series.id)).execute() + .parseAs<YomuMangasChaptersDto>().chapters + .sortedByDescending(YomuMangasChapterDto::chapter) + .map { it.toSChapter(series) } + } + + override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url + + override fun pageListRequest(chapter: SChapter): Request { + val urlParts = chapter.url.split("/", "#") + val seriesId = urlParts[2] + val chapterNumber = urlParts[6] + + return GET("$API_URL/mangas/$seriesId/chapters/$chapterNumber", apiHeaders) + } + + override fun pageListParse(response: Response): List<Page> { + return response.parseAs<YomuMangasChapterDetailsDto>() + .chapter.images.orEmpty() + .mapIndexed { i, image -> Page(i, "", "$CDN_URL/${image.uri}") } + } + + override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) + + override fun imageUrlParse(response: Response): String = "" + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .add("Accept", ACCEPT_IMAGE) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + + override fun getFilterList(): FilterList = FilterList( + StatusFilter(getStatusList()), + TypeFilter(getTypesList()), + NsfwContentFilter(), + AdultContentFilter(), + GenreFilter(getGenresList()), + ) + + private fun getStatusList(): List<Status> = listOf( + Status("Todos", ""), + Status("Lançando", "RELEASING"), + Status("Finalizado", "FINISHED"), + Status("Cancelado", "CANCELLED"), + Status("Hiato", "HIATUS"), + Status("Não lançado", "NOT_YET_RELEASED"), + Status("Traduzindo", "TRANSLATING"), + Status("Desconhecido", "UNKNOWN"), + ) + + private fun getTypesList(): List<Type> = listOf( + Type("Todos", ""), + Type("Mangá", "MANGA"), + Type("Manhwa", "MANHWA"), + Type("Mangá em hiato", "MANGA_HIATUS"), + Type("Webcomic", "WEBCOMIC"), + Type("Webtoon", "WEBTOON"), + Type("Hentai", "HENTAI"), + Type("Doujinshi", "DOUJIN"), + Type("One-shot", "ONESHOT"), + ) + + private fun getGenresList(): List<Genre> = listOf( + Genre("Ação", "1"), + Genre("Aventura", "8"), + Genre("Comédia", "2"), + Genre("Drama", "3"), + Genre("Ecchi", "15"), + Genre("Esportes", "14"), + Genre("Fantasia", "6"), + Genre("Hentai", "19"), + Genre("Horror", "4"), + Genre("Mahou shoujo", "18"), + Genre("Mecha", "17"), + Genre("Mistério", "7"), + Genre("Música", "16"), + Genre("Psicológico", "9"), + Genre("Romance", "13"), + Genre("Sci-fi", "11"), + Genre("Slice of life", "10"), + Genre("Sobrenatural", "5"), + Genre("Suspense", "12"), + ) + + private inline fun <reified T> Response.parseAs(): T = use { + json.decodeFromString(it.body.string()) + } + + companion object { + private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" + private const val ACCEPT_JSON = "application/json" + + private const val API_URL = "https://api.yomumangas.com" + const val CDN_URL = "https://images.yomumangas.com" + } +} diff --git a/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasDto.kt b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasDto.kt new file mode 100644 index 0000000000..e742993d87 --- /dev/null +++ b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasDto.kt @@ -0,0 +1,96 @@ +package eu.kanade.tachiyomi.extension.pt.yomumangas + +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.text.SimpleDateFormat +import java.util.Locale + +@Serializable +data class YomuMangasHomeDto( + val updates: List<YomuMangasSeriesDto> = emptyList(), + val votes: List<YomuMangasSeriesDto> = emptyList(), +) + +@Serializable +data class YomuMangasSearchDto( + val mangas: List<YomuMangasSeriesDto> = emptyList(), + val page: Int, + val pages: Int, +) { + + val hasNextPage: Boolean + get() = page < pages +} + +@Serializable +data class YomuMangasDetailsDto(val manga: YomuMangasSeriesDto) + +@Serializable +data class YomuMangasSeriesDto( + val id: Int, + val slug: String, + val title: String, + val cover: String? = null, + val status: String, + val authors: List<String>? = emptyList(), + val artists: List<String>? = emptyList(), + val genres: List<YomuMangasGenreDto>? = emptyList(), + val description: String? = null, +) { + + fun toSManga(): SManga = SManga.create().apply { + title = this@YomuMangasSeriesDto.title + author = authors.orEmpty().joinToString { it.trim() } + artist = artists.orEmpty().joinToString { it.trim() } + genre = genres.orEmpty() + .sortedBy { it.name } + .joinToString { it.name.trim() } + description = this@YomuMangasSeriesDto.description?.trim() + status = when (this@YomuMangasSeriesDto.status) { + "RELEASING" -> SManga.ONGOING + "FINISHED" -> SManga.COMPLETED + "HIATUS" -> SManga.ON_HIATUS + "CANCELLED" -> SManga.CANCELLED + "TRANSLATING" -> SManga.PUBLISHING_FINISHED + else -> SManga.UNKNOWN + } + thumbnail_url = cover?.let { "${YomuMangas.CDN_URL}/$it" } + url = "/manga/$id/$slug" + } +} + +@Serializable +data class YomuMangasGenreDto(val name: String) + +@Serializable +data class YomuMangasChaptersDto(val chapters: List<YomuMangasChapterDto> = emptyList()) + +@Serializable +data class YomuMangasChapterDto( + val id: Int, + val chapter: Float, + @SerialName("uploaded_at") val uploadedAt: String, + val images: List<YomuMangasImageDto>? = emptyList(), +) { + + fun toSChapter(series: YomuMangasSeriesDto): SChapter = SChapter.create().apply { + name = "Capítulo ${chapter.toString().removeSuffix(".0")}" + date_upload = runCatching { DATE_FORMATTER.parse(uploadedAt)?.time } + .getOrNull() ?: 0L + url = "/manga/${series.id}/${series.slug}/chapter/$id#$chapter" + } + + companion object { + private val DATE_FORMATTER by lazy { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + } + } +} + +@Serializable +data class YomuMangasChapterDetailsDto(val chapter: YomuMangasChapterDto) + +@Serializable +data class YomuMangasImageDto(val uri: String) diff --git a/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasFilters.kt b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasFilters.kt new file mode 100644 index 0000000000..2bd95683b5 --- /dev/null +++ b/src/pt/yomumangas/src/eu/kanade/tachiyomi/extension/pt/yomumangas/YomuMangasFilters.kt @@ -0,0 +1,73 @@ +package eu.kanade.tachiyomi.extension.pt.yomumangas + +import eu.kanade.tachiyomi.source.model.Filter +import okhttp3.HttpUrl + +interface UrlQueryFilter { + fun addQueryParameter(url: HttpUrl.Builder) +} + +class NsfwContentFilter : Filter.CheckBox("Conteúdo NSFW"), UrlQueryFilter { + override fun addQueryParameter(url: HttpUrl.Builder) { + if (state) { + url.addQueryParameter("nsfw", "true") + } + } +} + +class AdultContentFilter : Filter.CheckBox("Conteúdo adulto"), UrlQueryFilter { + override fun addQueryParameter(url: HttpUrl.Builder) { + if (state) { + url.addQueryParameter("hentai", "true") + } + } +} + +open class EnhancedSelect<T>(name: String, values: Array<T>) : Filter.Select<T>(name, values) { + val selected: T + get() = values[state] +} + +data class Status(val name: String, val value: String) { + override fun toString() = name +} + +class StatusFilter(statusList: List<Status>) : + EnhancedSelect<Status>("Status", statusList.toTypedArray()), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + if (state > 0) { + url.addQueryParameter("status", selected.value) + } + } +} + +data class Type(val name: String, val value: String) { + override fun toString() = name +} + +class TypeFilter(typesList: List<Type>) : + EnhancedSelect<Type>("Tipo", typesList.toTypedArray()), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + if (state > 0) { + url.addQueryParameter("type", selected.value) + } + } +} + +class Genre(name: String, val id: String) : Filter.CheckBox(name) { + override fun toString() = name +} + +class GenreFilter(genres: List<Genre>) : + Filter.Group<Genre>("Gêneros", genres), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.filter(Genre::state) + .forEach { url.addQueryParameter("genres[]", it.id) } + } +} diff --git a/src/pt/yugenmangas/AndroidManifest.xml b/src/pt/yugenmangas/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/src/pt/yugenmangas/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest /> diff --git a/src/pt/yugenmangas/build.gradle b/src/pt/yugenmangas/build.gradle new file mode 100644 index 0000000000..178c26ab9b --- /dev/null +++ b/src/pt/yugenmangas/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'Yugen Mangás' + pkgNameSuffix = 'pt.yugenmangas' + extClass = '.YugenMangas' + extVersionCode = 37 +} + +apply from: "$rootDir/common.gradle" diff --git a/multisrc/overrides/madara/yugenmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/yugenmangas/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/mipmap-hdpi/ic_launcher.png rename to src/pt/yugenmangas/res/mipmap-hdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/yugenmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/yugenmangas/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/mipmap-mdpi/ic_launcher.png rename to src/pt/yugenmangas/res/mipmap-mdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/yugenmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/yugenmangas/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/mipmap-xhdpi/ic_launcher.png rename to src/pt/yugenmangas/res/mipmap-xhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/yugenmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/yugenmangas/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/mipmap-xxhdpi/ic_launcher.png rename to src/pt/yugenmangas/res/mipmap-xxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/yugenmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/yugenmangas/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/mipmap-xxxhdpi/ic_launcher.png rename to src/pt/yugenmangas/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/madara/yugenmangas/res/web_hi_res_512.png b/src/pt/yugenmangas/res/web_hi_res_512.png similarity index 100% rename from multisrc/overrides/madara/yugenmangas/res/web_hi_res_512.png rename to src/pt/yugenmangas/res/web_hi_res_512.png diff --git a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt new file mode 100644 index 0000000000..ad44d3e7aa --- /dev/null +++ b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt @@ -0,0 +1,187 @@ +package eu.kanade.tachiyomi.extension.pt.yugenmangas + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +/** + * Changed the name from "YugenMangas" to "Yugen Mangás" when + * the source was updated to handle their CMS changes, so no + * `versionId` change is needed as the ID should be different to + * force users to migrate. + */ +class YugenMangas : ParsedHttpSource() { + + override val name = "Yugen Mangás" + + override val baseUrl = "https://yugenmangas.org" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .rateLimit(1, 2, TimeUnit.SECONDS) + .build() + + private val json: Json by injectLazy() + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) + + override fun popularMangaSelector(): String = "div.popular div.swiper-wrapper a" + + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("h1")!!.text() + thumbnail_url = element.selectFirst("img")!!.absUrl("src") + url = element.attr("href") + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/updates/?page=$page", headers) + } + + override fun latestUpdatesSelector() = "div.container-update-series div.card-series-updates" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("a.title-serie h1")!!.text() + thumbnail_url = element.selectFirst("img")!!.absUrl("src") + url = element.selectFirst("a")!!.attr("href") + } + + override fun latestUpdatesNextPageSelector() = "div.pagination a:contains(Próxima)" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/api/series/list".toHttpUrl().newBuilder() + .addQueryParameter("query", query) + .build() + + return GET(url, headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val result = response.parseAs<List<SearchResultDto>>() + val matches = result.map { + SManga.create().apply { + title = it.name + url = "/series/${it.slug}" + } + } + + return MangasPage(matches, hasNextPage = false) + } + + override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") + + override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") + + override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + val infoElement = document.selectFirst("div.main div.resume > div.sinopse")!! + + title = infoElement.selectFirst("div.title-name h1")!!.text() + author = infoElement.selectFirst("div.author")!!.text() + genre = infoElement.select("div.genero span").joinToString { it.text() } + status = infoElement.selectFirst("div.lancamento p")!!.text().toStatus() + description = infoElement.select("div.sinopse > p").text() + thumbnail_url = document.selectFirst("div.content div.side div.top-side img")!!.absUrl("src") + } + + override fun chapterListSelector() = "#listadecapitulos div.chapter a" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = element.selectFirst("span.chapter-title")!!.text() + scanlator = element.selectFirst("div.end-chapter span")?.text() + date_upload = element.selectFirst("span.chapter-lancado")!!.text().toDate() + url = element.attr("href") + } + + override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url + + override fun pageListRequest(chapter: SChapter): Request { + val paths = chapter.url.removePrefix("/").split("/") + + val newHeaders = headersBuilder() + .set("Referer", getChapterUrl(chapter)) + .build() + + return GET("$baseUrl/api/serie/${paths[1]}/chapter/${paths[2]}/images/imgs", newHeaders) + } + + override fun pageListParse(response: Response): List<Page> { + val result = response.parseAs<ReaderDto>() + val chapterUrl = response.request.headers["Referer"].orEmpty() + + return result.images.orEmpty().mapIndexed { index, image -> + Page(index, chapterUrl, "$CDN_BASE_URL/${image.removePrefix("/")}") + } + } + + override fun pageListParse(document: Document) = throw UnsupportedOperationException("Not used") + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", page.url) + .build() + + return GET(page.imageUrl!!, newHeaders) + } + + @Serializable + private data class SearchResultDto(val name: String, val slug: String) + + @Serializable + private data class ReaderDto( + @SerialName("chapter_images") val images: List<String>? = emptyList(), + ) + + private fun String.toDate(): Long { + return runCatching { DATE_FORMATTER.parse(trim())?.time } + .getOrNull() ?: 0L + } + + private fun String.toStatus() = when (this) { + "ongoing" -> SManga.ONGOING + "completed", "finished" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + private inline fun <reified T> Response.parseAs(): T = use { + json.decodeFromString(it.body.string()) + } + + companion object { + private const val CDN_BASE_URL = "https://media.yugenmangas.org" + + private val DATE_FORMATTER by lazy { + SimpleDateFormat("dd.MM.yyyy", Locale("pt", "BR")) + } + } +} diff --git a/src/ru/acomics/AndroidManifest.xml b/src/ru/acomics/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/acomics/AndroidManifest.xml +++ b/src/ru/acomics/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/comx/AndroidManifest.xml b/src/ru/comx/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/comx/AndroidManifest.xml +++ b/src/ru/comx/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/comx/build.gradle b/src/ru/comx/build.gradle index 50cd8885c2..b038236c44 100644 --- a/src/ru/comx/build.gradle +++ b/src/ru/comx/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Com-X' pkgNameSuffix = 'ru.comx' extClass = '.ComX' - extVersionCode = 26 + extVersionCode = 27 } apply from: "$rootDir/common.gradle" diff --git a/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt b/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt index 571c1306b3..d37f6c61f9 100644 --- a/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt +++ b/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt @@ -130,7 +130,8 @@ class ComX : ParsedHttpSource() { manga.thumbnail_url = baseUrl + element.select("img").first()!!.attr("data-src") element.select(".readed__title a").first()!!.let { manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().split(" / ").first() + // Russian's titles prevails. +Site bad search English titles. + manga.title = it.text().replace(" / ", " | ").split(" | ").last().trim() } return manga } @@ -156,7 +157,8 @@ class ComX : ParsedHttpSource() { manga.thumbnail_url = baseUrl + element.select("img").first()!!.attr("src").replace("mini/mini", "mini/mid") element.select("a.latest__title").first()!!.let { manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().split(" / ").first() + // Russian's titles prevails. +Site bad search English titles. + manga.title = it.text().replace(" / ", " | ").split(" | ").last().trim() } return manga } @@ -257,17 +259,23 @@ class ComX : ParsedHttpSource() { } val rawAgeStop = if (document.toString().contains("ВНИМАНИЕ! 18+")) "18+" else "" val manga = SManga.create() - manga.title = infoElement.select(".page__title-original").text().replace(" / ", " | ").split(" | ").first() + manga.title = infoElement.select(".page__header h1").text().trim() manga.author = infoElement.select(".page__list li:contains(Издатель)").text() manga.genre = category + ", " + rawAgeStop + ", " + infoElement.select(".page__tags a").joinToString { it.text() } manga.status = parseStatus(infoElement.select(".page__list li:contains(Статус)").text()) - manga.description = infoElement.select(".page__header h1").text().replace(" / ", " | ").split(" | ").first() + "\n" + + manga.description = infoElement.select(".page__title-original").text().trim() + "\n" + if (document.select(".page__list li:contains(Тип выпуска)").text().contains("!!! События в комиксах - ХРОНОЛОГИЯ !!!")) { "Cобытие в комиксах - ХРОНОЛОГИЯ\n" } else { "" } + ratingStar + " " + ratingValue + " (голосов: " + ratingVotes + ")\n" + - Jsoup.parse(infoElement.select(".page__text ").first()!!.html().replace("<br>", "REPLACbR")).text().replace("REPLACbR", "\n") + infoElement.select(".page__text ").first()?.html()?.let { Jsoup.parse(it) } + ?.select("body:not(:has(p)),p,br") + ?.prepend("\\n")?.text()?.replace("\\n", "\n")?.replace("\n ", "\n") + .orEmpty() + + val src = infoElement.select(".img-wide img").let { + it.attr("data-src").ifEmpty { it.attr("src") } + } - val src = infoElement.select(".img-wide img").attr("data-src") if (src.contains("://")) { manga.thumbnail_url = src } else { diff --git a/src/ru/desu/AndroidManifest.xml b/src/ru/desu/AndroidManifest.xml index 880af3062a..0190f2e209 100644 --- a/src/ru/desu/AndroidManifest.xml +++ b/src/ru/desu/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -19,6 +18,10 @@ android:host="desu.me" android:pathPattern="/manga/..*" android:scheme="https" /> + <data + android:host="desu.win" + android:pathPattern="/manga/..*" + android:scheme="https" /> </intent-filter> </activity> </application> diff --git a/src/ru/desu/build.gradle b/src/ru/desu/build.gradle index 61fa41b661..cee17dc838 100644 --- a/src/ru/desu/build.gradle +++ b/src/ru/desu/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Desu' pkgNameSuffix = 'ru.desu' extClass = '.Desu' - extVersionCode = 22 + extVersionCode = 23 } apply from: "$rootDir/common.gradle" diff --git a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt b/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt index b19532754c..f8eb219c51 100644 --- a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt +++ b/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt @@ -40,7 +40,7 @@ class Desu : ConfigurableSource, HttpSource() { override val id: Long = 6684416167758830305 - override val baseUrl = "https://desu.me" + override val baseUrl = "https://desu.win" override val lang = "ru" diff --git a/src/ru/mangabook/AndroidManifest.xml b/src/ru/mangabook/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/mangabook/AndroidManifest.xml +++ b/src/ru/mangabook/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/mangaclub/AndroidManifest.xml b/src/ru/mangaclub/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/mangaclub/AndroidManifest.xml +++ b/src/ru/mangaclub/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/mangaclub/build.gradle b/src/ru/mangaclub/build.gradle index 879dc750c4..09462c0e46 100644 --- a/src/ru/mangaclub/build.gradle +++ b/src/ru/mangaclub/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MangaClub' pkgNameSuffix = 'ru.mangaclub' extClass = '.MangaClub' - extVersionCode = 11 + extVersionCode = 12 } apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/MangaClub.kt b/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/MangaClub.kt index 98d8950452..82cd6a49db 100644 --- a/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/MangaClub.kt +++ b/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/MangaClub.kt @@ -93,18 +93,18 @@ class MangaClub : ParsedHttpSource() { /** Details **/ override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val licensedStatus = document.select("div.fullstory").text().contains("Данное произведение лицензировано на территории РФ. Главы удалены.") thumbnail_url = document.select("div.image img").attr("abs:src") title = document.select("div.info strong").text().replace("\\'", "'").substringBefore("/").trim() author = document.select("div.info a[href*=author]").joinToString(", ") { it.text().trim() } artist = author - status = when (document.select("div.info a[href*=status_translation]").text().trim()) { - "Продолжается" -> if (licensedStatus) SManga.LICENSED else SManga.ONGOING - "Завершен" -> if (licensedStatus) SManga.LICENSED else SManga.COMPLETED - "Заморожено/Заброшено" -> if (licensedStatus) SManga.LICENSED else SManga.ON_HIATUS + status = if (document.select("div.fullstory").text().contains("Данное произведение лицензировано на территории РФ. Главы удалены.")) SManga.LICENSED else when (document.select("div.info a[href*=status_translation]").text().trim()) { + "Продолжается" -> SManga.ONGOING + "Завершен" -> SManga.COMPLETED + "Заморожено/Заброшено" -> SManga.ON_HIATUS else -> SManga.UNKNOWN } - description = "Читайте описание через WebView" + + description = document.select(".description").first()!!.text() genre = document.select("div.info a[href*=tags]").joinToString(", ") { it.text().replaceFirstChar { it.uppercase() }.trim() } @@ -123,8 +123,8 @@ class MangaClub : ParsedHttpSource() { /** Pages **/ override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("div.manga-lines-page a").forEach { - add(Page(it.attr("data-p").toInt(), "", "${baseUrl.replace("//", "//img.")}/${it.attr("data-i")}")) + document.select(".manga-lines-page a").forEach { + add(Page(it.attr("data-p").toInt(), "", it.attr("data-i"))) } } override fun imageUrlParse(document: Document): String = "" diff --git a/src/ru/mangahub/AndroidManifest.xml b/src/ru/mangahub/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/mangahub/AndroidManifest.xml +++ b/src/ru/mangahub/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/mangapoisk/AndroidManifest.xml b/src/ru/mangapoisk/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/mangapoisk/AndroidManifest.xml +++ b/src/ru/mangapoisk/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/mangapoisk/build.gradle b/src/ru/mangapoisk/build.gradle index 10f1a4974b..d679c5ec78 100644 --- a/src/ru/mangapoisk/build.gradle +++ b/src/ru/mangapoisk/build.gradle @@ -5,7 +5,8 @@ ext { extName = 'MangaPoisk' pkgNameSuffix = 'ru.mangapoisk' extClass = '.MangaPoisk' - extVersionCode = 5 + extVersionCode = 10 + isNsfw = true } apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt b/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt index 0ff87c873e..d9195bddbe 100644 --- a/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt +++ b/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt @@ -23,16 +23,14 @@ import java.util.Locale class MangaPoisk : ParsedHttpSource() { override val name = "MangaPoisk" - override val baseUrl = "https://mangapoisk.com" + override val baseUrl = "https://mangapoisk.me" override val lang = "ru" override val supportsLatest = true - private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.3987.163 Safari/537.36" - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", userAgent) + .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50") .add("Referer", baseUrl) override fun popularMangaRequest(page: Int): Request = @@ -71,58 +69,78 @@ class MangaPoisk : ParsedHttpSource() { return GET(url, headers) } - override fun popularMangaSelector() = "article.card" + override fun searchMangaSelector(): String = "article.card" - override fun latestUpdatesSelector() = popularMangaSelector() + override fun searchMangaFromElement(element: Element): SManga { + return SManga.create().apply { + thumbnail_url = getImage(element.select("a > img").first()!!) - override fun searchMangaParse(response: Response): MangasPage { - return popularMangaParse(response) + setUrlWithoutDomain(element.select("a.card-about").first()!!.attr("href")) + + element.select("a > h2.entry-title").first()!!.let { + title = it.text().split("/").first() + } + } } - private fun getImage(first: Element): String? { - val image = first.attr("data-src") - if (image.isNotEmpty()) { - return image + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = if (document.location().contains("search?q")) { + document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + } else { + document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } } - return first.attr("src") + + return MangasPage(mangas, mangas.isNotEmpty()) } + override fun popularMangaNextPageSelector(): Nothing? = null + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun popularMangaSelector() = ".manga-card" + + override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun popularMangaFromElement(element: Element): SManga { return SManga.create().apply { thumbnail_url = getImage(element.select("a > img").first()!!) - setUrlWithoutDomain(element.select("a.card-about").first()!!.attr("href")) + setUrlWithoutDomain(element.select("a").first()!!.attr("href")) - element.select("a > h2.entry-title").first()!!.let { - title = it.text().split("/").first() + element.select("a").first()!!.let { + title = it.attr("title").split("/").first() } } } + override fun latestUpdatesSelector() = popularMangaSelector() + + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.page-link[rel=next]" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + private fun getImage(first: Element): String? { + val image = first.attr("data-src") + if (image.isNotEmpty()) { + return image + } + return first.attr("src") + } override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("article div.card-body").first()!! + val infoElement = document.select("div.card:has(header)").first()!! val manga = SManga.create() - val entitle = infoElement.select(".post-name-en") - manga.title = if (entitle.isNullOrEmpty()) { infoElement.select(".post-name").text() } else entitle.text().replaceRange(0, 2, "") - manga.genre = infoElement.select(".post-info > span:eq(10) > a").joinToString { it.text() } - manga.description = infoElement.select(".post-info > div .manga-description.entry").text() - manga.status = if (document.select(".order-2 h3").text() == "Главы удалены по требованию правообладателя.") { - SManga.LICENSED - } else { - parseStatus(infoElement.select(".post-info > span:eq(7)").text()) - } - manga.thumbnail_url = infoElement.select("img.img-fluid").first()!!.attr("src") + manga.title = infoElement.select(".text-base span").first()!!.text() + manga.genre = infoElement.select("span:contains(Жанр:) a").joinToString { it.text() } + manga.description = infoElement.select(".manga-description").text() + manga.status = parseStatus(infoElement.select("span:contains(Статус:)").text()) + manga.thumbnail_url = infoElement.select("img.w-full").first()!!.attr("src") return manga } @@ -132,6 +150,10 @@ class MangaPoisk : ParsedHttpSource() { else -> SManga.UNKNOWN } override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { + val document = client.newCall(GET("$baseUrl${manga.url}?tab=chapters", headers)).execute().asJsoup() + if (document.select(".text-md:contains(Главы удалены по требованию правообладателя)").isNotEmpty()) { + return Observable.error(Exception("Лицензировано - Нет глав")) + } val pageItems = client.newCall(chapterListRequest(manga)).execute().asJsoup().select("li.page-item") val pages = mutableListOf(1) if (pageItems.lastIndex > 1) { @@ -186,7 +208,10 @@ class MangaPoisk : ParsedHttpSource() { return chapter } override fun pageListParse(document: Document): List<Page> { - return document.select(".img-fluid.page-image").mapIndexed { index, element -> + if (document.toString().contains("text-error-500-400-token")) { + throw Exception("Лицензировано - Глава удалена по требованию правообладателя.") + } + return document.select(".page-image").mapIndexed { index, element -> Page(index, "", getImage(element)) } } @@ -260,7 +285,5 @@ class MangaPoisk : ParsedHttpSource() { override fun imageUrlParse(document: Document) = throw Exception("Not Used") - override fun searchMangaSelector(): String = throw Exception("Not Used") - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not Used") } diff --git a/src/ru/newbie/AndroidManifest.xml b/src/ru/newbie/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/newbie/AndroidManifest.xml +++ b/src/ru/newbie/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/nudemoon/AndroidManifest.xml b/src/ru/nudemoon/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/nudemoon/AndroidManifest.xml +++ b/src/ru/nudemoon/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/nudemoon/build.gradle b/src/ru/nudemoon/build.gradle index a2134e4fa8..8e85617db6 100644 --- a/src/ru/nudemoon/build.gradle +++ b/src/ru/nudemoon/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Nude-Moon' pkgNameSuffix = 'ru.nudemoon' extClass = '.Nudemoon' - extVersionCode = 15 + extVersionCode = 16 isNsfw = true } diff --git a/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt b/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt index b69fdc6de1..3a1ad9fcd6 100644 --- a/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt +++ b/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt @@ -21,7 +21,7 @@ class Nudemoon : ParsedHttpSource() { override val name = "Nude-Moon" - override val baseUrl = "https://nude-moon.org" + override val baseUrl = "https://x.nude-moon.fun" override val lang = "ru" diff --git a/src/ru/remanga/AndroidManifest.xml b/src/ru/remanga/AndroidManifest.xml index c268a5b208..1cb45bc3c5 100644 --- a/src/ru/remanga/AndroidManifest.xml +++ b/src/ru/remanga/AndroidManifest.xml @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" -package="eu.kanade.tachiyomi.extension"> - +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".ru.remanga.RemangaActivity" diff --git a/src/ru/remanga/build.gradle b/src/ru/remanga/build.gradle index c8af60b3a5..c4b29f7ce5 100644 --- a/src/ru/remanga/build.gradle +++ b/src/ru/remanga/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Remanga' pkgNameSuffix = 'ru.remanga' extClass = '.Remanga' - extVersionCode = 75 + extVersionCode = 82 } dependencies { diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt index 3a4c72da7a..8d4eefc365 100644 --- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt +++ b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt @@ -10,13 +10,14 @@ import eu.kanade.tachiyomi.extension.ru.remanga.dto.BookDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.BranchesDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.ChunksPageDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExBookDto +import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExLibraryDto +import eu.kanade.tachiyomi.extension.ru.remanga.dto.ExWrapperDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.LibraryDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.MangaDetDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.MyLibraryDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.PageDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.PageWrapperDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.PagesDto -import eu.kanade.tachiyomi.extension.ru.remanga.dto.SeriesExWrapperDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.SeriesWrapperDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.TagsDto import eu.kanade.tachiyomi.extension.ru.remanga.dto.UserDto @@ -212,7 +213,14 @@ class Remanga : ConfigurableSource, HttpSource() { override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) override fun searchMangaParse(response: Response): MangasPage { - if (response.request.url.toString().contains("/bookmarks/")) { + if (response.request.url.toString().contains(exManga)) { + val page = json.decodeFromString<ExWrapperDto<List<ExLibraryDto>>>(response.body.string()) + val mangas = page.data.map { + it.toSManga() + } + + return MangasPage(mangas, true) + } else if (response.request.url.toString().contains("/bookmarks/")) { val page = json.decodeFromString<PageWrapperDto<MyLibraryDto>>(response.body.string()) val mangas = page.content.map { it.title.toSManga() @@ -229,19 +237,20 @@ class Remanga : ConfigurableSource, HttpSource() { return MangasPage(mangas, page.props.page < page.props.total_pages!!) } } + private fun ExLibraryDto.toSManga(): SManga = + SManga.create().apply { + // Do not change the title name to ensure work with a multilingual catalog! + title = name + url = "/api/titles/$dir/" + thumbnail_url = baseUrl + img + } private fun LibraryDto.toSManga(): SManga = SManga.create().apply { // Do not change the title name to ensure work with a multilingual catalog! title = if (isEng.equals("rus")) rus_name else en_name url = "/api/titles/$dir/" - thumbnail_url = if (img.high?.isNotEmpty() == true) { - baseUrl + img.high - } else if (img.mid?.isNotEmpty() == true) { - baseUrl + img.mid - } else { - baseUrl + img.low - } + thumbnail_url = baseUrl + img.mid } private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US) } @@ -307,6 +316,11 @@ class Remanga : ConfigurableSource, HttpSource() { url.setQueryParameter("count_chapters_gte", "0") } } + is RequireEX -> { + if (filter.state == 1) { + return GET("$exManga/manga?take=20&skip=${10 * (page - 1)}&name=$query", exHeaders()) + } + } else -> {} } } @@ -367,10 +381,14 @@ class Remanga : ConfigurableSource, HttpSource() { thumbnail_url = baseUrl + img.high var altName = "" if (another_name.isNotEmpty()) { - altName = "Альтернативные названия:\n" + another_name + "\n\n" + altName = "Альтернативные названия:\n" + another_name + "\n" } val mediaNameLanguage = if (isEng.equals("rus")) en_name else rus_name - this.description = mediaNameLanguage + "\n" + ratingStar + " " + ratingValue + " (голосов: " + count_rating + ")\n" + altName + Jsoup.parse(o.description.replace("<p>", "").replace("</p>", "REPLACbR")).text().replace("REPLACbR", "\n") + this.description = "$mediaNameLanguage\n$ratingStar $ratingValue (голосов: $count_rating)\n$altName" + + o.description?.let { Jsoup.parse(it) } + ?.select("body:not(:has(p)),p,br") + ?.prepend("\\n")?.text()?.replace("\\n", "\n")?.replace("\n ", "\n") + .orEmpty() genre = (parseType(type) + ", " + parseAge(age_limit) + ", " + (genres + categories).joinToString { it.name }).split(", ").filter { it.isNotEmpty() }.joinToString { it.trim() } status = parseStatus(o.status.id) } @@ -386,7 +404,7 @@ class Remanga : ConfigurableSource, HttpSource() { .asObservable().doOnNext { response -> if (!response.isSuccessful) { response.close() - if (response.code == 404 && USER_ID == "") warnLogin = true else throw Exception("HTTP error ${response.code}") + if (USER_ID == "") warnLogin = true else throw Exception("HTTP error ${response.code}") } } .map { response -> @@ -407,7 +425,14 @@ class Remanga : ConfigurableSource, HttpSource() { } private fun mangaBranches(manga: SManga): List<BranchesDto> { - val responseString = client.newCall(GET(baseUrl + manga.url, headers)).execute().body.string() + val requestString = client.newCall(GET(baseUrl + manga.url, headers)).execute() + if (!requestString.isSuccessful) { + if (USER_ID == "") { + throw Exception("HTTP error ${requestString.code}. Для просмотра контента необходима авторизация через WebView\uD83C\uDF0E") + } + throw Exception("HTTP error ${requestString.code}") + } + val responseString = requestString.body.string() // manga requiring login return "content" as a JsonArray instead of the JsonObject we expect // callback request for update outside the library val content = json.decodeFromString<JsonObject>(responseString)["content"] @@ -415,6 +440,9 @@ class Remanga : ConfigurableSource, HttpSource() { val series = json.decodeFromJsonElement<MangaDetDto>(content) branches[series.dir] = series.branches mangaIDs[series.dir] = series.id + if (parseStatus(series.status.id) == SManga.LICENSED && series.branches.maxByOrNull { selector(it) }!!.count_chapters == 0) { + throw Exception("Лицензировано - Нет глав") + } series.branches } else { emptyList() @@ -422,19 +450,16 @@ class Remanga : ConfigurableSource, HttpSource() { } private fun filterPaid(tempChaptersList: MutableList<SChapter>): MutableList<SChapter> { - val lastEx = tempChaptersList.find { it.scanlator.equals("exmanga") } return if (!preferences.getBoolean(PAID_PREF, false)) { - tempChaptersList.filter { - !it.name.contains("\uD83D\uDCB2") || if (lastEx != null) { - ( - ( - it.name.substringBefore( - ". Глава", - ).toIntOrNull()!! <= - (lastEx.name.substringBefore(". Глава").toIntOrNull()!!) - ) && - (it.chapter_number < lastEx.chapter_number) - ) + val lastEx = tempChaptersList.find { !it.name.contains("\uD83D\uDCB2") } + tempChaptersList.filterNot { + it.name.contains("\uD83D\uDCB2") && if (lastEx != null) { + val volCor = it.name.substringBefore( + ". Глава", + ).toIntOrNull()!! + val volLast = lastEx.name.substringBefore(". Глава").toIntOrNull()!! + (volCor > volLast) || + ((volCor == volLast) && (it.chapter_number > lastEx.chapter_number)) } else { false } @@ -461,13 +486,13 @@ class Remanga : ConfigurableSource, HttpSource() { else -> { val mangaID = mangaIDs[manga.url.substringAfter("/api/titles/").substringBefore("/").substringBefore("?")] val exChapters = if (preferences.getBoolean(exPAID_PREF, true)) { - json.decodeFromString<SeriesExWrapperDto<List<ExBookDto>>>(client.newCall(GET("$exManga/chapter/history/$mangaID", exHeaders())).execute().body.string()).data + json.decodeFromString<ExWrapperDto<List<ExBookDto>>>(client.newCall(GET("$exManga/chapter/history/$mangaID", exHeaders())).execute().body.string()).data } else { emptyList() } val selectedBranch = branch.maxByOrNull { selector(it) }!! val tempChaptersList = mutableListOf<SChapter>() - (1..(selectedBranch.count_chapters / 100 + 1)).map { + (1..(selectedBranch.count_chapters / 300 + 1)).map { val response = chapterListRequest(selectedBranch.id, it) chapterListParse(response, manga, exChapters) }.let { tempChaptersList.addAll(it.flatten()) } @@ -481,7 +506,7 @@ class Remanga : ConfigurableSource, HttpSource() { ).content.firstOrNull()?.chapter?.toFloatOrNull() ?: -2F ) ) { - (1..(selectedBranch2.count_chapters / 100 + 1)).map { + (1..(selectedBranch2.count_chapters / 300 + 1)).map { val response = chapterListRequest(selectedBranch2.id, it) chapterListParse(response, manga, exChapters) }.let { tempChaptersList.addAll(0, it.flatten()) } @@ -498,7 +523,7 @@ class Remanga : ConfigurableSource, HttpSource() { private fun chapterListRequest(branch: Long, page: Number): Response = client.newCall( GET( - "$baseUrl/api/titles/chapters/?branch_id=$branch&page=$page&count=100", + "$baseUrl/api/titles/chapters/?branch_id=$branch&page=$page&count=300", headers, ), ).execute().run { @@ -535,9 +560,7 @@ class Remanga : ConfigurableSource, HttpSource() { } if (chapter.is_paid and (chapter.is_bought == true)) { - if (exChID == null) { - url = "$url#is_bought" - } + url = "$url#is_bought" } } else { exChID = null @@ -570,7 +593,7 @@ class Remanga : ConfigurableSource, HttpSource() { val heightEmptyChunks = 10 if (chapter.scanlator.equals("exmanga")) { try { - val exPage = json.decodeFromString<SeriesExWrapperDto<List<List<PagesDto>>>>(body) + val exPage = json.decodeFromString<ExWrapperDto<List<List<PagesDto>>>>(body) val result = mutableListOf<Page>() exPage.data.forEach { it.filter { page -> page.height > heightEmptyChunks }.forEach { page -> @@ -688,6 +711,7 @@ class Remanga : ConfigurableSource, HttpSource() { private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages) override fun getFilterList() = FilterList( + RequireEX(), OrderBy(), GenreList(getGenreList()), CategoryList(getCategoryList()), @@ -894,6 +918,11 @@ class Remanga : ConfigurableSource, HttpSource() { "Только проекты с главами", arrayOf("Да", "Все"), ) + + private class RequireEX : Filter.Select<String>( + "Использовать поиск", + arrayOf("Remanga", "ExManga(без фильтров)"), + ) private var isEng: String? = preferences.getString(LANGUAGE_PREF, "eng") override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { val userAgentSystem = androidx.preference.CheckBoxPreference(screen.context).apply { diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt index 751ad8d314..7764a33a1b 100644 --- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt +++ b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt @@ -49,7 +49,7 @@ data class MangaDetDto( val rus_name: String, val another_name: String, val dir: String, - val description: String, + val description: String?, val issue_year: Int?, val img: ImgDto, val type: TagsDto, @@ -97,7 +97,7 @@ data class BookDto( ) @Serializable -data class SeriesExWrapperDto<T>( +data class ExWrapperDto<T>( val data: T, ) @@ -108,6 +108,14 @@ data class ExBookDto( val chapter: String, ) +@Serializable +data class ExLibraryDto( + val id: Long, + val dir: String, + val name: String = "Без названия", + val img: String?, +) + @Serializable data class PagesDto( val id: Int, diff --git a/src/ru/unicomics/AndroidManifest.xml b/src/ru/unicomics/AndroidManifest.xml index 9745510248..ec2ffbaf03 100644 --- a/src/ru/unicomics/AndroidManifest.xml +++ b/src/ru/unicomics/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/ru/waymanga/AndroidManifest.xml b/src/ru/waymanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/waymanga/AndroidManifest.xml +++ b/src/ru/waymanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/ru/webofcomics/AndroidManifest.xml b/src/ru/webofcomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/ru/webofcomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/webofcomics/build.gradle b/src/ru/webofcomics/build.gradle deleted file mode 100644 index 4225ddd8ca..0000000000 --- a/src/ru/webofcomics/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Web of Comics' - pkgNameSuffix = 'ru.webofcomics' - extClass = '.WebOfComics' - extVersionCode = 5 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/webofcomics/res/mipmap-hdpi/ic_launcher.png b/src/ru/webofcomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f748c94c17..0000000000 Binary files a/src/ru/webofcomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/webofcomics/res/mipmap-mdpi/ic_launcher.png b/src/ru/webofcomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8ab7e531da..0000000000 Binary files a/src/ru/webofcomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/webofcomics/res/mipmap-xhdpi/ic_launcher.png b/src/ru/webofcomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 87e8ad2754..0000000000 Binary files a/src/ru/webofcomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/webofcomics/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/webofcomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a94dc433c1..0000000000 Binary files a/src/ru/webofcomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/webofcomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/webofcomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index fb32c27310..0000000000 Binary files a/src/ru/webofcomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/webofcomics/res/web_hi_res_512.png b/src/ru/webofcomics/res/web_hi_res_512.png deleted file mode 100644 index 61cbd06a4c..0000000000 Binary files a/src/ru/webofcomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/webofcomics/src/eu/kanade/tachiyomi/extension/ru/webofcomics/WebOfComics.kt b/src/ru/webofcomics/src/eu/kanade/tachiyomi/extension/ru/webofcomics/WebOfComics.kt deleted file mode 100644 index 2729b8f8cd..0000000000 --- a/src/ru/webofcomics/src/eu/kanade/tachiyomi/extension/ru/webofcomics/WebOfComics.kt +++ /dev/null @@ -1,808 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.webofcomics - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit -import kotlin.math.absoluteValue -import kotlin.random.Random - -class WebOfComics : ParsedHttpSource() { - - override val name = "Web of Comics" - - override val baseUrl = "https://webofcomics.ru" - - override val lang = "ru" - - override val supportsLatest = true - - private val userAgentRandomizer = "${Random.nextInt().absoluteValue}" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.$userAgentRandomizer Safari/537.36") - .add("Referer", baseUrl) - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .rateLimit(3) - .build() - - override fun popularMangaRequest(page: Int): Request { - return POST( - "$baseUrl/page/$page", - body = FormBody.Builder() - .add("dlenewssortby", "rating") - .add("dledirection", "desc") - .add("set_new_sort", "dle_sort_main") - .add("set_direction_sort", "dle_direction_main") - .build(), - headers = headers, - ) - } - - override fun latestUpdatesRequest(page: Int): Request { - return POST( - "$baseUrl/page/$page", - body = FormBody.Builder() - .add("dlenewssortby", "date") - .add("dledirection", "desc") - .add("set_new_sort", "dle_sort_main") - .add("set_direction_sort", "dle_direction_main") - .build(), - headers = headers, - ) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - return POST( - "$baseUrl/index.php?do=search", - body = FormBody.Builder() - .add("do", "search") - .add("subaction", "search") - .add("story", query) - .add("search_start", page.toString()) - .build(), - headers = headers, - ) - } - val mutableGenre = mutableListOf<String>() - val mutableType = mutableListOf<String>() - val mutableAge = mutableListOf<String>() - val mutableStatus = mutableListOf<String>() - var publisherCat = "" - var orderBy = "desc" - var ascEnd = "rating" - var sectionOr = "All" - (if (filters.isEmpty()) getFilterList() else filters).forEach { select -> - when (select) { - is OrderBy -> { - orderBy = arrayOf("date", "rating", "news_read", "comm_num", "title", "d.year", "d.multirating")[select.state!!.index] - ascEnd = if (select.state!!.ascending) "asc" else "desc" - } - is AgeList -> select.state.forEach { age -> - if (age.state) { - mutableAge += age.name - } - } - is StatusList -> select.state.forEach { status -> - if (status.state) { - mutableStatus += status.name - } - } - is Section -> { - if (select.state == 1) { - sectionOr = "Comics" - filters.forEach { filter -> - when (filter) { - is GenreComics -> filter.state.forEach { genre -> - if (genre.state) { - mutableGenre += genre.name - } - } - is TypeComics -> filter.state.forEach { type -> - if (type.state) { - mutableType += type.name - } - } - is PublishersComics -> { - if (filter.state > 0) { - publisherCat = getPublishersComics()[filter.state].id - } - } - else -> {} - } - } - } - if (select.state == 2) { - sectionOr = "Manga" - filters.forEach { filter -> - when (filter) { - is GenreManga -> filter.state.forEach { genre -> - if (genre.state) { - mutableGenre += genre.name - } - } - is TypeManga -> filter.state.forEach { type -> - if (type.state) { - mutableType += type.name - } - } - is PublishersManga -> { - if (filter.state > 0) { - publisherCat = getPublishersManga()[filter.state].id - } - } - else -> {} - } - } - } - } - else -> {} - } - } - - if (sectionOr == "Comics") { - return GET("$baseUrl/f/age=${mutableAge.joinToString(",")}/comicsormanga=Comics/o.cat=$publisherCat/translatestatus=${mutableStatus.joinToString(",")}/genre=${mutableGenre.joinToString(",")}/type=${mutableType.joinToString(",")}/sort=$orderBy/order=$ascEnd/page/$page", headers) - } - - if (sectionOr == "Manga") { - return GET("$baseUrl/f/age=${mutableAge.joinToString(",")}/comicsormanga=Manga/o.cat=$publisherCat/translatestatus=${mutableStatus.joinToString(",")}/genremanga=${mutableGenre.joinToString(",")}/typemanga=${mutableType.joinToString(",")}/sort=$orderBy/order=$ascEnd/page/$page", headers) - } - - return POST( - "$baseUrl/page/$page", - body = FormBody.Builder() - .add("dlenewssortby", orderBy) - .add("dledirection", ascEnd) - .add("set_new_sort", "dle_sort_main") - .add("set_direction_sort", "dle_direction_main") - .build(), - headers = headers, - ) - } - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - return MangasPage(mangas, mangas.isNotEmpty()) - } - - override fun popularMangaSelector() = ".movie-item" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + element.select(".lazyload").first()!!.attr("data-src").replace("/thumbs", "") - element.select(".movie-title").first()!!.let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.html().substringBefore("<div>") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = ".pnext a" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector(): String? = null - - private fun parseStatus(status: String): Int { - return when (status) { - "Завершён" -> SManga.COMPLETED - "Продолжается" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - - override fun mangaDetailsParse(document: Document): SManga { - val ratingValue = document.select(".multirating-itog-rateval").text().toFloat() - val ratingVotes = document.select(".multirating-itog-votes").text() - val ratingStar = when { - ratingValue > 9.5 -> "★★★★★" - ratingValue > 8.5 -> "★★★★✬" - ratingValue > 7.5 -> "★★★★☆" - ratingValue > 6.5 -> "★★★✬☆" - ratingValue > 5.5 -> "★★★☆☆" - ratingValue > 4.5 -> "★★✬☆☆" - ratingValue > 3.5 -> "★★☆☆☆" - ratingValue > 2.5 -> "★✬☆☆☆" - ratingValue > 1.5 -> "★☆☆☆☆" - ratingValue > 0.5 -> "✬☆☆☆☆" - else -> "☆☆☆☆☆" - } - - val manga = SManga.create() - val infoElement = document.select(".page-cols").first()!! - val infoElement2 = document.select(".m-info2 .sliceinfo1") - manga.title = infoElement.select("h1").first()!!.text() - manga.thumbnail_url = baseUrl + infoElement.select(".lazyload").first()!!.attr("data-src") - manga.description = infoElement.select("H2").first()!!.text() + "\n" + ratingStar + " " + ratingValue + " (голосов: " + ratingVotes + ")\n" + Jsoup.parse(document.select(".slice-this").first()!!.html().replace("<br>", "REPLACbR")).text().replace("REPLACbR", "\n").substringAfter("Описание:").trim() - manga.author = infoElement2.select(":contains(Автор) a").joinToString { it.text() } - if (manga.author.isNullOrEmpty()) { - manga.author = infoElement.select(".mi-item:contains(Издательство)").first()!!.text() - } - manga.artist = infoElement2.select(":contains(Художник) a").joinToString { it.text() } - manga.genre = (infoElement.select(".mi-item:contains(Тип) a") + infoElement.select(".mi-item:contains(Возраст) a") + infoElement.select(".mi-item:contains(Формат) a") + infoElement.select(".mi-item:contains(Жанр) a")).joinToString { it.text() } - manga.status = if (document.toString().contains("Удалено по просьбе правообладателя")) { - SManga.LICENSED - } else { - parseStatus(infoElement.select(".mi-item:contains(Перевод) a").first()!!.text()) - } - - return manga - } - - override fun chapterListRequest(manga: SManga): Request { - val typeSeries = if (manga.url.contains("/manga/")) { - "xsort='tommanga,glavamanga' template='custom-linkstocomics-xfmanga-guest'" - } else { - "xsort='number' template='custom-linkstocomics-xfcomics-guest'" - } - - return POST( - "$baseUrl/engine/ajax/customajax.php", - body = FormBody.Builder() - .add("castom", "custom senxf='fastnavigation|${manga.url.substringAfterLast("/").substringBefore("-")}' $typeSeries limit='3000' sort='asc' cache='yes'") - .build(), - headers = headers, - ) - } - - override fun chapterListSelector() = ".ltcitems:has(a:not(.alttranslatelink))" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("a").first()!!.let { - val numberSection = it.text().substringBefore(" - ") - chapter.name = it.text().substringAfterLast(":") - chapter.chapter_number = if (numberSection.contains("#")) { - numberSection.substringAfter("#").replace("-", ".").toFloatOrNull() ?: -1f - } else { - numberSection.substringAfter("Глава").substringAfter("-").toFloatOrNull() ?: -1f - } - chapter.setUrlWithoutDomain(it.attr("href")) - } - chapter.date_upload = simpleDateFormat.parse(element.select("div").first()!!.text().trim())?.time ?: 0L - return chapter - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun pageListParse(document: Document): List<Page> { - var baseImgUrl = document.select("link[rel='image_src']").last()!!.attr("href") - - if (baseImgUrl.isEmpty()) { - return document.select(".readtab .lazyload").mapIndexed { index, element -> - Page( - index, - "", - "https://read.webofcomics.ru/webofcomics.ru/www/webofcomics.ru/public_html/uploads/" + element.attr("data-src").substringAfter("/uploads/"), - ) - } - } - - val uploadUrl = - with(baseImgUrl) { - when { - contains("/uploads/") -> "/uploads/" - contains("/manga/") -> "/manga/" - contains("/mangaparser/") -> "/mangaparser/" - else -> "errorUploads" - } - } - - baseImgUrl = baseImgUrl.substringBefore(uploadUrl) - - if (document.select(".readtab .lazyload").isNotEmpty()) { - return document.select(".readtab .lazyload").mapIndexed { index, element -> - Page( - index, - "", - baseImgUrl + uploadUrl + element.attr("data-src").substringAfter(uploadUrl), - ) - } - } else { - val counterPageStr = document.select("#comics script").toString() - - val startPageStr = counterPageStr - .substringAfter("for(var i =") - .substringBefore("; i <") - .trim() - var endPageStr = counterPageStr - .substringAfter("; i <") - .substringBefore("; i++)") - .trim() - - if (endPageStr.contains("=")) { - endPageStr = (endPageStr.replace("=", "").trim().toInt() + 1).toString() - } - - if (baseImgUrl.contains("/share.")) { - baseImgUrl = counterPageStr - .substringAfter("data-src=\"") - .substringBefore("' + i") - .trim().replace("https://feik.domain.ru/", "https://read.webofcomics.ru/webofcomics.ru/www/webofcomics.ru/public_html/") + - counterPageStr - .substringAfter("i + '") - .substringBefore("\">") - .trim() - } - - val countSubPage = counterPageStr.split("document.write").size - return (startPageStr.toInt() until endPageStr.toInt()).mapIndexed { index, page -> - val subPage = when (countSubPage) { - 3 -> { - when { - page < 10 -> "0" - else -> "" - } - } - 4 -> { - when { - page < 10 -> "00" - page < 100 -> "0" - else -> "" - } - } - else -> "" - } - Page( - index, - "", - baseImgUrl.substringBeforeLast("/") + "/$subPage$page." + baseImgUrl.substringAfterLast("."), - ) - } - } - } - - override fun imageUrlParse(document: Document) = "" - - // Filters - private class CheckFilter(name: String) : Filter.CheckBox(name) - private data class Publisher(val name: String, val id: String) - - override fun getFilterList() = FilterList( - Section(), - OrderBy(), - Filter.Separator(), - Filter.Header("ОБЩИЕ ФИЛЬТРЫ♼"), - Filter.Separator(), - StatusList(getStatusList()), - AgeList(getAgeList()), - Filter.Separator(), - Filter.Header("КОМИКСЫ♼"), - Filter.Separator(), - TypeComics(getTypesComics()), - GenreComics(getGenresComics()), - PublishersComics(publishersComics), - Filter.Separator(), - Filter.Header("МАНГА♼"), - Filter.Separator(), - TypeManga(getTypesManga()), - GenreManga(getGenresManga()), - PublishersManga(publishersManga), - ) - - private class OrderBy : Filter.Sort( - "Сортировать по", - arrayOf("Дате обновления", "Популярности", "Просмотрам", "Комментариям", "Алфавиту", "Дате выпуска♼", "Рейтингу♼"), - Selection(1, false), - ) - - private class Section : Filter.Select<String>( - "ИЛИ", - arrayOf("Сортировка(без фильтрации♼)", "КОМИКСЫ", "МАНГА"), - ) - - private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус перевода", statuses) - private fun getStatusList() = listOf( - CheckFilter("Продолжается"), - CheckFilter("Завершён"), - CheckFilter("Заморожен"), - ) - - private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages) - private fun getAgeList() = listOf( - CheckFilter("16+"), - CheckFilter("18+"), - ) - - // Filters Comics - private class GenreComics(genres: List<CheckFilter>) : Filter.Group<CheckFilter>("Жанры", genres) - private class TypeComics(types: List<CheckFilter>) : Filter.Group<CheckFilter>("Тип", types) - private class PublishersComics(publishers: Array<String>) : Filter.Select<String>("Издательства", publishers) - - private fun getGenresComics() = listOf( - CheckFilter("Антиутопия"), - CheckFilter("Биография"), - CheckFilter("Боевик"), - CheckFilter("Боевые Искусства"), - CheckFilter("Вампиры"), - CheckFilter("Вестерн"), - CheckFilter("Военный"), - CheckFilter("Детектив"), - CheckFilter("Детский"), - CheckFilter("Драма"), - CheckFilter("Зомби"), - CheckFilter("Исторический"), - CheckFilter("Киберпанк"), - CheckFilter("Комедия"), - CheckFilter("Космоопера"), - CheckFilter("Криминал"), - CheckFilter("Мелодрама"), - CheckFilter("Мистика"), - CheckFilter("Нуар"), - CheckFilter("Пародия"), - CheckFilter("Повседневность"), - CheckFilter("Постапокалиптика"), - CheckFilter("Приключения"), - CheckFilter("Путешествия во времени"), - CheckFilter("Романтика"), - CheckFilter("Сверхъестественное"), - CheckFilter("Слэшер"), - CheckFilter("Стимпанк"), - CheckFilter("Супергероика"), - CheckFilter("Триллер"), - CheckFilter("Ужасы"), - CheckFilter("Фантасмагория"), - CheckFilter("Фантастика"), - CheckFilter("Фэнтези"), - CheckFilter("Эротика"), - ) - - private fun getTypesComics() = listOf( - CheckFilter("Ван шот"), - CheckFilter("Лимитка"), - CheckFilter("Макси-серия (Онгоинг)"), - CheckFilter("Графический роман"), - CheckFilter("Спешл"), - CheckFilter("Превью"), - CheckFilter("Ежегодник"), - CheckFilter("Сборник"), - ) - - private fun getPublishersComics() = listOf( - Publisher("Все", "Not"), - Publisher("12 Bis", "221"), - Publisher("12-Gauge Comics", "59"), - Publisher("215 INK", "135"), - Publisher("4Twenty Limited", "236"), - Publisher("Aardvark", "122"), - Publisher("Ablaze", "137"), - Publisher("Abrams ComicArts", "220"), - Publisher("Abstract Studio", "30"), - Publisher("Action Lab", "116"), - Publisher("Aftershock Comics", "95"), - Publisher("Albin Michel", "43"), - Publisher("Alias Enterprises", "79"), - Publisher("Alterna Comics", "114"), - Publisher("Alterna Comics", "70"), - Publisher("American Mythology Productions", "101"), - Publisher("America's Best Comics", "51"), - Publisher("Ankama", "180"), - Publisher("Antarctic Press", "46"), - Publisher("Archaia Studios Press", "45"), - Publisher("Archie Comics", "39"), - Publisher("Arh Comix", "139"), - Publisher("Arrow", "156"), - Publisher("Atlas Comics", "108"), - Publisher("Atomeka Press", "84"), - Publisher("Avatar Press", "22"), - Publisher("AWA Studios", "162"), - Publisher("Benitez Productions", "158"), - Publisher("Best Jackett Press", "237"), - Publisher("Black Mask Studios", "93"), - Publisher("Blizzard Entertainment", "100"), - Publisher("Bluewater Comics", "96"), - Publisher("Bongo", "78"), - Publisher("Boom! Studios", "25"), - Publisher("Boundless Comics", "24"), - Publisher("Bubble", "94"), - Publisher("Carlsen Comics", "165"), - Publisher("Casterman", "54"), - Publisher("Catalan Communications", "58"), - Publisher("CD Projekt Red", "163"), - Publisher("Chaos! Comics", "48"), - Publisher("Cinebooks", "44"), - Publisher("Coffin Comics", "103"), - Publisher("Comico", "73"), - Publisher("Comics Experience", "160"), - Publisher("Comics USA", "35"), - Publisher("Creator Owned Comics", "195"), - Publisher("Curtis Magazines", "217"), - Publisher("Dargaud", "41"), - Publisher("Dark Horse Comics", "18"), - Publisher("Darkstorm Comics", "26"), - Publisher("Dayjob Studio", "166"), - Publisher("DC Comics", "11"), - Publisher("Dead Dog", "155"), - Publisher("Delcourt", "71"), - Publisher("Devil's Due Publishing", "33"), - Publisher("Devolver Digital", "106"), - Publisher("Digital Webbing", "143"), - Publisher("Disney Comics", "109"), - Publisher("Dreamwave Productions", "66"), - Publisher("Dupuis", "111"), - Publisher("Dynamite Entertainment", "19"), - Publisher("Eclipse Comics", "102"), - Publisher("Editorial Novaro", "228"), - Publisher("Egmont Magazines", "38"), - Publisher("Ehapa Verlag", "151"), - Publisher("Entity", "129"), - Publisher("Eternity", "157"), - Publisher("Europe Comics", "134"), - Publisher("Evil Twin Comics", "27"), - Publisher("Exploding Albatross Funnybooks", "64"), - Publisher("Fantagraphics", "117"), - Publisher("Fawcett Publications", "34"), - Publisher("First Comics", "198"), - Publisher("First Second Books", "90"), - Publisher("Fleetway", "80"), - Publisher("Fox Atomic Comics", "119"), - Publisher("Games Workshop", "133"), - Publisher("Genesis West", "172"), - Publisher("GG Studio", "77"), - Publisher("Ginger Rabbit Studio", "67"), - Publisher("Glénat", "112"), - Publisher("H. H. Windsor", "174"), - Publisher("Harris Comics", "55"), - Publisher("Heavy Metal", "89"), - Publisher("Heiro-Graphic", "123"), - Publisher("Humanoids", "21"), - Publisher("Icon Comics", "29"), - Publisher("Id Software", "69"), - Publisher("IDW Publishing", "16"), - Publisher("Illusion Studios", "181"), - Publisher("Image Comics", "15"), - Publisher("Joe Books", "83"), - Publisher("Koyama Press", "104"), - Publisher("Le Lombard", "63"), - Publisher("Legendary Comics", "144"), - Publisher("Lion Forge Comics", "85"), - Publisher("Locust Moon Press", "121"), - Publisher("Machaon", "107"), - Publisher("Magic Strip", "190"), - Publisher("Magnetic Press", "61"), - Publisher("Malibu", "56"), - Publisher("Marvel Comics", "13"), - Publisher("Max", "14"), - Publisher("Mirage", "40"), - Publisher("MonkeyBrain Comics", "42"), - Publisher("Monsterverse", "115"), - Publisher("Moonstone", "209"), - Publisher("Mount Olympus Comics", "171"), - Publisher("Nintendo", "127"), - Publisher("Northstar", "113"), - Publisher("Nowa Fantastyka", "141"), - Publisher("Omnibus Press", "188"), - Publisher("Oni Press", "28"), - Publisher("Panelsyndicate", "88"), - Publisher("Piranha Press", "206"), - Publisher("Radical Publishing", "86"), - Publisher("Rebellion", "68"), - Publisher("Red 5 Comics", "164"), - Publisher("Revolution Software Ltd", "37"), - Publisher("Revolver Comics", "72"), - Publisher("SAF Comics", "170"), - Publisher("Salleck Publications", "65"), - Publisher("Scholastic Book Services", "49"), - Publisher("Sega", "189"), - Publisher("Self Published", "47"), - Publisher("Sergio Bonelli Editore", "98"), - Publisher("Shadowline", "36"), - Publisher("Sirius Entertainment", "140"), - Publisher("Skybound", "62"), - Publisher("Slave Labor Graphics (SLG)", "75"), - Publisher("Soleil", "17"), - Publisher("Space Goat Productions", "225"), - Publisher("Splitter", "87"), - Publisher("Star Comics", "159"), - Publisher("Storm King Comics", "175"), - Publisher("Tell Tale Publications", "167"), - Publisher("Titan Comics", "60"), - Publisher("Top Cow", "23"), - Publisher("Top Shelf", "118"), - Publisher("Topps", "53"), - Publisher("Toutain Editor", "203"), - Publisher("UDON", "169"), - Publisher("Valiant", "50"), - Publisher("Vanquish Interactive", "224"), - Publisher("Vault Comics", "161"), - Publisher("Vents d'Ouest", "110"), - Publisher("Vertigo", "20"), - Publisher("Viper Comics", "154"), - Publisher("Virgin Comics", "76"), - Publisher("Vortex", "74"), - Publisher("Warp Graphics", "31"), - Publisher("WildStorm", "52"), - Publisher("Zenescope Entertainment", "32"), - Publisher("Неизвестное издательство", "57"), - ) - - private val publishersComics = getPublishersComics().map { - it.name - }.toTypedArray() - - // Filters Manga - private class GenreManga(genres: List<CheckFilter>) : Filter.Group<CheckFilter>("Жанры", genres) - private class TypeManga(types: List<CheckFilter>) : Filter.Group<CheckFilter>("Тип", types) - private class PublishersManga(publishers: Array<String>) : Filter.Select<String>("Издательства", publishers) - - private fun getGenresManga() = listOf( - CheckFilter("Арт"), - CheckFilter("Бара"), - CheckFilter("Боевик"), - CheckFilter("Боевые искусства"), - CheckFilter("Вампиры"), - CheckFilter("Война"), - CheckFilter("Гарем"), - CheckFilter("Гендерная интрига"), - CheckFilter("Героическое фэнтези"), - CheckFilter("Гуро"), - CheckFilter("Детектив"), - CheckFilter("Дзёсэй"), - CheckFilter("Додзинси"), - CheckFilter("Драма"), - CheckFilter("Ёнкома"), - CheckFilter("Зомби"), - CheckFilter("Игра"), - CheckFilter("Исекай"), - CheckFilter("История"), - CheckFilter("Киберпанк"), - CheckFilter("Кодомо"), - CheckFilter("Комедия"), - CheckFilter("Кулинария"), - CheckFilter("Магия"), - CheckFilter("Махо-сёдзё"), - CheckFilter("Меха"), - CheckFilter("Мистика"), - CheckFilter("Мужская беременность"), - CheckFilter("Научная фантастика"), - CheckFilter("Оборотни"), - CheckFilter("Образовательная"), - CheckFilter("Омегаверс"), - CheckFilter("Повседневность"), - CheckFilter("Полиция"), - CheckFilter("Постапокалиптика"), - CheckFilter("Приключения"), - CheckFilter("Психология"), - CheckFilter("Романтика"), - CheckFilter("Самурайский боевик"), - CheckFilter("Сборник"), - CheckFilter("Сверхъестественное"), - CheckFilter("Сёдзё"), - CheckFilter("Сёдзё-ай"), - CheckFilter("Сёнэн"), - CheckFilter("Сёнэн-ай"), - CheckFilter("Сказка"), - CheckFilter("Спорт"), - CheckFilter("Сэйнэн"), - CheckFilter("Трагедия"), - CheckFilter("Триллер"), - CheckFilter("Ужасы"), - CheckFilter("Фантастика"), - CheckFilter("Фэнтези"), - CheckFilter("Школа"), - CheckFilter("Экшен"), - CheckFilter("Элементы юмора"), - CheckFilter("Эротика"), - CheckFilter("Этти"), - CheckFilter("Юри"), - CheckFilter("Яой"), - - ) - - private fun getTypesManga() = listOf( - CheckFilter("Манга"), - CheckFilter("Манхва"), - CheckFilter("Маньхуа"), - CheckFilter("OEL-Манга"), - CheckFilter("Руманга"), - CheckFilter("Индонезийский"), - CheckFilter("Комикс западный"), - ) - - private fun getPublishersManga() = listOf( - Publisher("Все", "Not"), - Publisher("AC.QQ", "128"), - Publisher("Akita Shoten", "136"), - Publisher("Alphapolis", "223"), - Publisher("Asahi Shimbunsha", "204"), - Publisher("ASCII Media Works", "92"), - Publisher("Beyond", "201"), - Publisher("Bilibili", "193"), - Publisher("Bomtoon", "177"), - Publisher("BookCube", "186"), - Publisher("Cherish Media", "227"), - Publisher("Comico Japan", "210"), - Publisher("Core Magazine", "194"), - Publisher("D&C Media", "211"), - Publisher("Daum", "142"), - Publisher("DCC", "230"), - Publisher("eComiX", "202"), - Publisher("Enterbrain", "150"), - Publisher("Frontier Works", "214"), - Publisher("Fujimi Shobo", "146"), - Publisher("Gentosha", "212"), - Publisher("Hakusensha", "176"), - Publisher("Hifumi Shobo", "218"), - Publisher("iQIYI", "226"), - Publisher("Kadokawa Shoten", "97"), - Publisher("Kaiousha", "185"), - Publisher("Kakao", "138"), - Publisher("Kidari Studio", "196"), - Publisher("Kill Time Communication", "148"), - Publisher("Kodansha", "126"), - Publisher("KuaiKan Manhua", "147"), - Publisher("Lezhin", "125"), - Publisher("Libre", "215"), - Publisher("Mag Garden", "208"), - Publisher("Manhuatai", "182"), - Publisher("Mkzhan", "197"), - Publisher("MrBlue", "187"), - Publisher("Naver", "145"), - Publisher("Nihon Bungeisha", "233"), - Publisher("PeanuToon", "205"), - Publisher("Pixiv", "216"), - Publisher("Printemps Shuppan", "213"), - Publisher("Ridibooks", "207"), - Publisher("Screamo", "192"), - Publisher("Seirin Kogeisha", "168"), - Publisher("Seoul Media Comics", "191"), - Publisher("Shinshokan", "235"), - Publisher("Shogakukan", "124"), - Publisher("Shonen Gahosha", "173"), - Publisher("Shueisha", "105"), - Publisher("Square Enix", "131"), - Publisher("Suiseisha", "219"), - Publisher("Takeshobo", "199"), - Publisher("Tencent", "200"), - Publisher("Tokuma Shoten", "222"), - Publisher("Tokyopop", "99"), - Publisher("Toptoon", "184"), - Publisher("U17", "130"), - Publisher("Webtoon", "183"), - Publisher("Неизвестное издательство", "149"), - ) - - private val publishersManga = getPublishersManga().map { - it.name - }.toTypedArray() - - companion object { - private val simpleDateFormat by lazy { SimpleDateFormat("dd.MM.yyyy", Locale.US) } - } -} diff --git a/src/ru/yagamiproject/AndroidManifest.xml b/src/ru/yagamiproject/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/ru/yagamiproject/AndroidManifest.xml +++ b/src/ru/yagamiproject/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/th/mikudoujin/AndroidManifest.xml b/src/th/mikudoujin/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/th/mikudoujin/AndroidManifest.xml +++ b/src/th/mikudoujin/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/th/nekopost/AndroidManifest.xml b/src/th/nekopost/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/th/nekopost/AndroidManifest.xml +++ b/src/th/nekopost/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/th/nekopost/build.gradle b/src/th/nekopost/build.gradle index 537d823022..27fe92ec32 100644 --- a/src/th/nekopost/build.gradle +++ b/src/th/nekopost/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Nekopost' pkgNameSuffix = 'th.nekopost' extClass = '.Nekopost' - extVersionCode = 9 + extVersionCode = 10 isNsfw = true } diff --git a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt index 0fa008408c..69cbedaabe 100644 --- a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt +++ b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt @@ -2,38 +2,35 @@ package eu.kanade.tachiyomi.extension.th.nekopost import eu.kanade.tachiyomi.extension.th.nekopost.model.RawChapterInfo import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectInfo -import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectNameListItem +import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSearchSummary import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSummaryList import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale -class Nekopost : ParsedHttpSource() { +class Nekopost : HttpSource() { private val json: Json by injectLazy() override val baseUrl: String = "https://www.nekopost.net/manga/" - private val latestMangaEndpoint: String = - "https://api.osemocphoto.com/frontAPI/getLatestChapter/m" - private val projectDataEndpoint: String = - "https://api.osemocphoto.com/frontAPI/getProjectInfo" + private val latestMangaEndpoint: String = "https://api.osemocphoto.com/frontAPI/getLatestChapter/m" + private val projectDataEndpoint: String = "https://api.osemocphoto.com/frontAPI/getProjectInfo" private val fileHost: String = "https://www.osemocphoto.com" + private val nekopostUrl = "https://www.nekopost.net" override val client: OkHttpClient = network.cloudflareClient @@ -57,118 +54,96 @@ class Nekopost : ParsedHttpSource() { else -> SManga.UNKNOWN } - override fun latestUpdatesRequest(page: Int): Request = throw NotImplementedError("Unused") + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.") - override fun latestUpdatesParse(response: Response): MangasPage = - throw NotImplementedError("Unused") + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.") - override fun chapterListSelector(): String = throw NotImplementedError("Unused") + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.") - override fun chapterFromElement(element: Element): SChapter = - throw NotImplementedError("Unused") + override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException("Not used.") - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl) - - override fun imageUrlParse(document: Document): String = throw NotImplementedError("Unused") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Unused") + override fun mangaDetailsRequest(manga: SManga): Request { + return GET("$projectDataEndpoint/${manga.url}", headers) + } - override fun latestUpdatesNextPageSelector(): String = throw Exception("Unused") + override fun mangaDetailsParse(response: Response): SManga { + val responseBody = response.body + val projectInfo: RawProjectInfo = json.decodeFromString(responseBody.string()) + val manga = SManga.create() + manga.apply { + projectInfo.projectInfo.let { + url = it.projectId + title = it.projectName + artist = it.artistName + author = it.authorName + description = it.info + status = getStatus(it.status) + thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg" + initialized = true + } - override fun latestUpdatesSelector(): String = throw Exception("Unused") + genre = if (projectInfo.projectCategoryUsed != null) { + projectInfo.projectCategoryUsed.joinToString(", ") { it.categoryName } + } else { + "" + } + } + return manga + } - override fun mangaDetailsParse(document: Document): SManga = throw NotImplementedError("Unused") + override fun chapterListRequest(manga: SManga): Request { + val headers = Headers.headersOf("accept", "*/*", "content-type", "text/plain;charset=UTF-8", "origin", nekopostUrl) + return GET("$projectDataEndpoint/${manga.url}", headers) + } - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(GET("$projectDataEndpoint/${manga.url}", headers)) - .asObservableSuccess() - .map { response -> - val responseBody = response.body - val projectInfo: RawProjectInfo = json.decodeFromString(responseBody.string()) + override fun chapterListParse(response: Response): List<SChapter> { + val responseBody = response.body.string() + val projectInfo: RawProjectInfo = json.decodeFromString(responseBody) + val manga = SManga.create() + manga.status = getStatus(projectInfo.projectInfo.status) - manga.apply { - projectInfo.projectInfo.let { - url = it.projectId - title = it.projectName - artist = it.artistName - author = it.authorName - description = it.info - status = getStatus(it.status) - thumbnail_url = - "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg" - initialized = true - } + if (manga.status == SManga.LICENSED) { + throw Exception("Licensed - No chapter to show") + } - genre = if (projectInfo.projectCategoryUsed != null) { - projectInfo.projectCategoryUsed.joinToString(", ") { it.categoryName } - } else { - "" - } - } + return projectInfo.projectChapterList!!.map { chapter -> + SChapter.create().apply { + url = "${projectInfo.projectInfo.projectId.toInt()}/${chapter.chapterId}/${projectInfo.projectInfo.projectId.toInt()}_${chapter.chapterId}.json" + name = chapter.chapterName + date_upload = SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss", + Locale("th"), + ).parse(chapter.createDate)?.time ?: 0L + chapter_number = chapter.chapterNo.toFloat() + scanlator = chapter.providerName } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(GET("$projectDataEndpoint/${manga.url}", headers)) - .asObservableSuccess() - .map { response -> - val responseBody = response.body - val projectInfo: RawProjectInfo = json.decodeFromString(responseBody.string()) - - manga.status = getStatus(projectInfo.projectInfo.status) - - if (manga.status == SManga.LICENSED) { - throw Exception("Licensed - No chapter to show") - } - - projectInfo.projectChapterList!!.map { chapter -> - SChapter.create().apply { - url = - "${manga.url}/${chapter.chapterId}/${manga.url}_${chapter.chapterId}.json" - name = chapter.chapterName - date_upload = SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss", - Locale("th"), - ).parse(chapter.createDate)?.time - ?: 0L - chapter_number = chapter.chapterNo.toFloat() - scanlator = chapter.providerName - } - } - } - } else { - Observable.error(Exception("Licensed - No chapter to show")) } } - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return client.newCall(GET("$fileHost/collectManga/${chapter.url}", headers)) - .asObservableSuccess() - .map { response -> - val responseBody = response.body - val chapterInfo: RawChapterInfo = json.decodeFromString(responseBody.string()) - - chapterInfo.pageItem.map { page -> - val imgUrl: String = if (page.pageName != null) { - "$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.pageName}" - } else { - "$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.fileName}" - } - Page( - index = page.pageNo, - imageUrl = imgUrl, - ) - } - } + override fun pageListRequest(chapter: SChapter): Request { + return GET("$fileHost/collectManga/${chapter.url}", headers) } - override fun pageListParse(document: Document): List<Page> = throw NotImplementedError("Unused") + override fun pageListParse(response: Response): List<Page> { + val responseBody = response.body + val chapterInfo: RawChapterInfo = json.decodeFromString(responseBody.string()) + return chapterInfo.pageItem.map { page -> + val imgUrl: String = if (page.pageName != null) { + "$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.pageName}" + } else { + "$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.fileName}" + } + Page( + index = page.pageNo, + imageUrl = imgUrl, + ) + } + } override fun popularMangaRequest(page: Int): Request { if (page <= 1) existingProject.clear() // API has a bug that sometime it returns null on first page - return GET("$latestMangaEndpoint/${if (firstPageNulled) page else page - 1 }", headers) + return GET("$latestMangaEndpoint/${if (firstPageNulled) page else page - 1}", headers) } override fun popularMangaParse(response: Response): MangasPage { @@ -176,18 +151,15 @@ class Nekopost : ParsedHttpSource() { val projectList: RawProjectSummaryList = json.decodeFromString(responseBody.string()) val mangaList: List<SManga> = if (projectList.listChapter != null) { - projectList.listChapter - .filter { !existingProject.contains(it.projectId) } - .map { - SManga.create().apply { - url = it.projectId - title = it.projectName - thumbnail_url = - "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg" - initialized = false - status = 0 - } + projectList.listChapter.filter { !existingProject.contains(it.projectId) }.map { + SManga.create().apply { + url = it.projectId + title = it.projectName + thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg" + initialized = false + status = 0 } + } } else { firstPageNulled = true // API has a bug that sometime it returns null on first page return MangasPage(emptyList(), hasNextPage = false) @@ -198,51 +170,27 @@ class Nekopost : ParsedHttpSource() { return MangasPage(mangaList, hasNextPage = true) } - override fun popularMangaFromElement(element: Element): SManga = - throw NotImplementedError("Unused") - - override fun popularMangaNextPageSelector(): String = throw Exception("Unused") - - override fun popularMangaSelector(): String = throw Exception("Unused") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Unused") - - override fun searchMangaNextPageSelector(): String = throw Exception("Unused") - - override fun fetchSearchManga( - page: Int, - query: String, - filters: FilterList, - ): Observable<MangasPage> { - return client.newCall(GET("$fileHost/dataJson/dataProjectName.json")) - .asObservableSuccess() - .map { response -> - val responseBody = response.body - val projectList: List<RawProjectNameListItem> = - json.decodeFromString(responseBody.string()) - - val mangaList: List<SManga> = projectList.filter { project -> - Regex( - query, - setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE), - ).find(project.npName) != null - }.map { project -> - SManga.create().apply { - url = project.npProjectId - title = project.npName - status = getStatus(project.npStatus) - initialized = false - } - } - - MangasPage(mangaList, false) - } + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val headers = Headers.headersOf("accept", "*/*", "content-type", "text/plain;charset=UTF-8", "origin", nekopostUrl) + val requestBody = "{\"keyword\":\"$query\"}".toRequestBody() + return POST("$nekopostUrl/api/explore/search", headers, requestBody) } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - throw Exception("Unused") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Unused") + override fun searchMangaParse(response: Response): MangasPage { + val responseBody = response.body.string() + + val projectList: List<RawProjectSearchSummary> = json.decodeFromString(responseBody) + val mangaList: List<SManga> = projectList.filter { project -> + project.projectType == "m" + }.map { project -> + SManga.create().apply { + url = project.projectId.toString() + title = project.projectName + status = project.status + initialized = false + } + } - override fun searchMangaSelector(): String = throw Exception("Unused") + return MangasPage(mangaList, false) + } } diff --git a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/model/RawProjectSearchSummary.kt b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/model/RawProjectSearchSummary.kt new file mode 100644 index 0000000000..dd9be071b9 --- /dev/null +++ b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/model/RawProjectSearchSummary.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.th.nekopost.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RawProjectSearchSummary( + val projectId: Int, + val projectName: String, + val projectType: String, + @SerialName("STATUS") + val status: Int, + val noChapter: Int, + val coverVersion: Int, + val info: String, + val views: Int, + @SerialName("lastUpdate") + val lastUpdateDate: String, +) diff --git a/src/th/niceoppai/AndroidManifest.xml b/src/th/niceoppai/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/th/niceoppai/AndroidManifest.xml +++ b/src/th/niceoppai/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/th/niceoppai/build.gradle b/src/th/niceoppai/build.gradle index 5b58e4467d..1c36205175 100644 --- a/src/th/niceoppai/build.gradle +++ b/src/th/niceoppai/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Niceoppai' pkgNameSuffix = 'th.niceoppai' extClass = '.Niceoppai' - extVersionCode = 27 + extVersionCode = 28 isNsfw = true } diff --git a/src/th/niceoppai/src/eu/kanade/tachiyomi/extension/th/niceoppai/Niceoppai.kt b/src/th/niceoppai/src/eu/kanade/tachiyomi/extension/th/niceoppai/Niceoppai.kt index 3e905ef945..f98692c924 100644 --- a/src/th/niceoppai/src/eu/kanade/tachiyomi/extension/th/niceoppai/Niceoppai.kt +++ b/src/th/niceoppai/src/eu/kanade/tachiyomi/extension/th/niceoppai/Niceoppai.kt @@ -43,16 +43,12 @@ class Niceoppai : ParsedHttpSource() { return GET("$baseUrl/manga_list/all/any/most-popular-monthly/$page", headers) } override fun popularMangaSelector() = "div.nde" - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.title = element.select("div.det a").text() + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + title = element.selectFirst("div.det a")!!.text() element.select("div.cvr").let { - manga.setUrlWithoutDomain(it.select("div.img_wrp a").attr("href")) - manga.thumbnail_url = it.select("img").attr("abs:src") - manga.initialized = false + setUrlWithoutDomain(it.select("a").attr("href")) + thumbnail_url = it.select("img").attr("abs:src") } - - return manga } override fun popularMangaNextPageSelector() = "ul.pgg li a" diff --git a/src/tr/MangaDenizi/AndroidManifest.xml b/src/tr/MangaDenizi/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/tr/MangaDenizi/AndroidManifest.xml +++ b/src/tr/MangaDenizi/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/tr/mangaship/AndroidManifest.xml b/src/tr/mangaship/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/tr/mangaship/AndroidManifest.xml +++ b/src/tr/mangaship/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/tr/serimanga/AndroidManifest.xml b/src/tr/serimanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/tr/serimanga/AndroidManifest.xml +++ b/src/tr/serimanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/uk/honeymanga/AndroidManifest.xml b/src/uk/honeymanga/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/uk/honeymanga/AndroidManifest.xml +++ b/src/uk/honeymanga/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/uk/mangainua/AndroidManifest.xml b/src/uk/mangainua/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/uk/mangainua/AndroidManifest.xml +++ b/src/uk/mangainua/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/uk/mangainua/build.gradle b/src/uk/mangainua/build.gradle index ff5ee90499..5760a59064 100644 --- a/src/uk/mangainua/build.gradle +++ b/src/uk/mangainua/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'MangaInUa' pkgNameSuffix = 'uk.mangainua' extClass = '.Mangainua' - extVersionCode = 3 + extVersionCode = 4 isNsfw = true } diff --git a/src/uk/mangainua/res/mipmap-hdpi/ic_launcher.png b/src/uk/mangainua/res/mipmap-hdpi/ic_launcher.png index d546cd8979..c479bc1db1 100644 Binary files a/src/uk/mangainua/res/mipmap-hdpi/ic_launcher.png and b/src/uk/mangainua/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/uk/mangainua/res/mipmap-mdpi/ic_launcher.png b/src/uk/mangainua/res/mipmap-mdpi/ic_launcher.png index 3b63958da4..ced96f77da 100644 Binary files a/src/uk/mangainua/res/mipmap-mdpi/ic_launcher.png and b/src/uk/mangainua/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/uk/mangainua/res/mipmap-xhdpi/ic_launcher.png b/src/uk/mangainua/res/mipmap-xhdpi/ic_launcher.png index 812eba1141..f37eb78021 100644 Binary files a/src/uk/mangainua/res/mipmap-xhdpi/ic_launcher.png and b/src/uk/mangainua/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/uk/mangainua/res/mipmap-xxhdpi/ic_launcher.png b/src/uk/mangainua/res/mipmap-xxhdpi/ic_launcher.png index 5d90bc81db..3961213bc4 100644 Binary files a/src/uk/mangainua/res/mipmap-xxhdpi/ic_launcher.png and b/src/uk/mangainua/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/uk/mangainua/res/mipmap-xxxhdpi/ic_launcher.png b/src/uk/mangainua/res/mipmap-xxxhdpi/ic_launcher.png index 43900ba24d..99ef7402b5 100644 Binary files a/src/uk/mangainua/res/mipmap-xxxhdpi/ic_launcher.png and b/src/uk/mangainua/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/uk/mangainua/src/eu/kanade/tachiyomi/extension/uk/mangainua/Mangainua.kt b/src/uk/mangainua/src/eu/kanade/tachiyomi/extension/uk/mangainua/Mangainua.kt index 66f2d6d599..685059d11a 100644 --- a/src/uk/mangainua/src/eu/kanade/tachiyomi/extension/uk/mangainua/Mangainua.kt +++ b/src/uk/mangainua/src/eu/kanade/tachiyomi/extension/uk/mangainua/Mangainua.kt @@ -7,13 +7,17 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup import okhttp3.FormBody import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import org.jsoup.select.Evaluator import java.text.SimpleDateFormat import java.util.Locale @@ -27,7 +31,7 @@ class Mangainua : ParsedHttpSource() { override val client: OkHttpClient = network.cloudflareClient - override fun headersBuilder(): Headers.Builder = Headers.Builder() + override fun headersBuilder(): Headers.Builder = super.headersBuilder() .add("Referer", baseUrl) // Popular @@ -37,11 +41,11 @@ class Mangainua : ParsedHttpSource() { override fun popularMangaSelector() = "div.owl-carousel div.card--big" override fun popularMangaFromElement(element: Element): SManga { return SManga.create().apply { - element.select("h3.card__title a").first()!!.let { + element.selectFirst("h3.card__title a")!!.let { setUrlWithoutDomain(it.attr("href")) title = it.text() } - thumbnail_url = element.select("img").attr("abs:src") + thumbnail_url = element.selectFirst("img")?.absUrl("src") } } override fun popularMangaNextPageSelector() = "not used" @@ -53,11 +57,11 @@ class Mangainua : ParsedHttpSource() { override fun latestUpdatesSelector() = "main.main article.item" override fun latestUpdatesFromElement(element: Element): SManga { return SManga.create().apply { - element.select("h3.card__title a").first()!!.let { + element.selectFirst("h3.card__title a")!!.let { setUrlWithoutDomain(it.attr("href")) title = it.text() } - thumbnail_url = element.select("div.card--big img").attr("abs:data-src") + thumbnail_url = element.selectFirst("div.card--big img")?.absUrl("data-src") } } override fun latestUpdatesNextPageSelector() = "a:contains(Наступна)" @@ -76,18 +80,18 @@ class Mangainua : ParsedHttpSource() { headers = headers, ) } else { - throw UnsupportedOperationException("Запит має містити щонайменше 3 символи / The query must contain at least 3 characters") + throw Exception("Запит має містити щонайменше 3 символи / The query must contain at least 3 characters") } } override fun searchMangaSelector() = latestUpdatesSelector() override fun searchMangaFromElement(element: Element): SManga { return SManga.create().apply { - element.select("h3.card__title a").first()!!.let { + element.selectFirst("h3.card__title a")!!.let { setUrlWithoutDomain(it.attr("href")) title = it.text() } - thumbnail_url = element.select("div.card--big img").attr("abs:src") + thumbnail_url = element.selectFirst("div.card--big img")?.absUrl("src") } } override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() @@ -95,35 +99,40 @@ class Mangainua : ParsedHttpSource() { // Manga Details override fun mangaDetailsParse(document: Document): SManga { return SManga.create().apply { - title = document.select("span.UAname").text() - description = document.select("div.item__full-description").text() - thumbnail_url = document.select("div.item__full-sidebar--poster img").first()!!.attr("abs:src") - status = when (document.select("div.item__full-sideba--header:has(div:containsOwn(Статус перекладу:))").first()?.select("span.item__full-sidebar--description")?.first()?.text()) { + title = document.selectFirst("span.UAname")!!.text() + description = document.selectFirst("div.item__full-description")!!.text() + thumbnail_url = document.selectFirst("div.item__full-sidebar--poster img")!!.absUrl("src") + status = when (document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Статус перекладу:))")?.selectFirst("span.item__full-sidebar--description")?.text()) { "Триває" -> SManga.ONGOING "Покинуто" -> SManga.CANCELLED "Закінчений" -> SManga.COMPLETED else -> SManga.UNKNOWN } - val type = when (document.select("div.item__full-sideba--header:has(div:containsOwn(Тип:))").first()?.select("span.item__full-sidebar--description")?.first()!!.text()) { + val type = when (document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Тип:))")?.selectFirst("span.item__full-sidebar--description")!!.text()) { "ВЕБМАНХВА" -> "Manhwa" "МАНХВА" -> "Manhwa" "МАНЬХВА" -> "Manhua" "ВЕБМАНЬХВА" -> "Manhua" else -> "Manga" } - genre = document.select("div.item__full-sideba--header:has(div:containsOwn(Жанри:))").first()?.select("span.item__full-sidebar--description")?.first()!!.select("a").joinToString { it.text() } + ", " + type + genre = document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Жанри:))")?.selectFirst("span.item__full-sidebar--description")!!.select("a").joinToString { it.text() } + ", " + type } } // Chapters - override fun chapterListSelector() = "div.ltcitems" - - private var previousChapterName: String? = null - private var previousChapterNumber: Float = 0.0f + override fun chapterListSelector() = throw UnsupportedOperationException() override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { urlElement -> + throw UnsupportedOperationException() + } + + private fun parseChapterElements(elements: Elements): List<SChapter> { + var previousChapterName: String? = null + var previousChapterNumber: Float = 0.0f + val dateFormat = DATE_FORMATTER + return elements.map { element -> + SChapter.create().apply { + val urlElement = element.selectFirst("a")!! setUrlWithoutDomain(urlElement.attr("href")) val chapterName = urlElement.text().substringAfter("НОВЕ").trim() val chapterNumber = urlElement.text().substringAfter("Розділ").substringBefore("-").trim() @@ -137,31 +146,58 @@ class Mangainua : ParsedHttpSource() { chapter_number = chapterNumber.toFloat() previousChapterNumber = chapterNumber.toFloat() } + date_upload = dateFormat.parse(element.child(0).ownText())?.time!! } - date_upload = parseDate(element.select("div.ltcright:containsOwn(.)").text()) } } override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() + val document = response.asJsoup() + val userHash = document.parseUserHash() + val metaElement = document.selectFirst(Evaluator.Id("linkstocomics"))!! + val body = FormBody.Builder() + .addEncoded("action", "show") + .addEncoded("news_id", metaElement.attr("data-news_id")) + .addEncoded("news_category", metaElement.attr("data-news_category")) + .addEncoded("this_link", metaElement.attr("data-this_link")) + .addEncoded("user_hash", userHash) + .build() + val request = POST("$baseUrl/engine/ajax/controller.php?mod=load_chapters", headers, body) + val chaptersHtml = client.newCall(request).execute().body.string() + val chaptersDocument = Jsoup.parseBodyFragment(chaptersHtml) + return parseChapterElements(chaptersDocument.body().children()).asReversed() } // Pages override fun pageListParse(document: Document): List<Page> { - return document.select("ul.loadcomicsimages img").mapIndexed { i, element -> - Page(i, "", element.attr("abs:data-src")) + val userHash = document.parseUserHash() + val newsId = document.selectFirst(Evaluator.Id("comics"))!!.attr("data-news_id") + val url = "$baseUrl/engine/ajax/controller.php?mod=load_chapters_image&news_id=$newsId&action=show&user_hash=$userHash" + val pagesHtml = client.newCall(GET(url, headers)).execute().body.string() + val pagesDocument = Jsoup.parseBodyFragment(pagesHtml) + return pagesDocument.getElementsByTag("img").mapIndexed { index, img -> + Page(index, imageUrl = img.attr("data-src")) } } - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - private fun parseDate(dateStr: String): Long { - return runCatching { DATE_FORMATTER.parse(dateStr)?.time } - .getOrNull() ?: 0L - } + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() companion object { private val DATE_FORMATTER by lazy { SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH) } + + private fun Document.parseUserHash(): String { + val start = "site_login_hash = '" + for (element in body().children()) { + if (element.tagName() != "script") continue + val data = element.data() + val leftIndex = data.indexOf(start) + if (leftIndex == -1) continue + val startIndex = leftIndex + start.length + val endIndex = data.indexOf('\'', startIndex) + return data.substring(startIndex, endIndex) + } + throw Exception("Couldn't find user hash") + } } } diff --git a/src/vi/blogtruyen/AndroidManifest.xml b/src/vi/blogtruyen/AndroidManifest.xml index f1474fbb26..d17382d140 100644 --- a/src/vi/blogtruyen/AndroidManifest.xml +++ b/src/vi/blogtruyen/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".vi.blogtruyen.BlogTruyenUrlActivity" android:excludeFromRecents="true" diff --git a/src/vi/hentaivn/AndroidManifest.xml b/src/vi/hentaivn/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/vi/hentaivn/AndroidManifest.xml +++ b/src/vi/hentaivn/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/vi/hentaivn/build.gradle b/src/vi/hentaivn/build.gradle index 79437661fd..61c702506c 100644 --- a/src/vi/hentaivn/build.gradle +++ b/src/vi/hentaivn/build.gradle @@ -5,8 +5,12 @@ ext { extName = 'HentaiVN' pkgNameSuffix = 'vi.hentaivn' extClass = '.HentaiVN' - extVersionCode = 25 + extVersionCode = 30 isNsfw = true } +dependencies { + implementation(project(":lib-randomua")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/CookieInterceptor.kt b/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/CookieInterceptor.kt new file mode 100644 index 0000000000..ce312b3211 --- /dev/null +++ b/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/CookieInterceptor.kt @@ -0,0 +1,46 @@ +package eu.kanade.tachiyomi.extension.vi.hentaivn + +import android.util.Log +import android.webkit.CookieManager +import okhttp3.Interceptor +import okhttp3.Response + +class CookieInterceptor( + private val domain: String, + private val key: String, + private val value: String, +) : Interceptor { + + init { + val url = "https://$domain/" + val cookie = "$key=$value; Domain=$domain; Path=/" + setCookie(url, cookie) + } + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + if (!request.url.host.endsWith(domain)) return chain.proceed(request) + + val cookie = "$key=$value" + val cookieList = request.header("Cookie")?.split("; ") ?: emptyList() + if (cookie in cookieList) return chain.proceed(request) + + setCookie("https://$domain/", "$cookie; Domain=$domain; Path=/") + val prefix = "$key=" + val newCookie = buildList(cookieList.size + 1) { + cookieList.filterNotTo(this) { it.startsWith(prefix) } + add(cookie) + }.joinToString("; ") + val newRequest = request.newBuilder().header("Cookie", newCookie).build() + return chain.proceed(newRequest) + } + + private fun setCookie(url: String, value: String) { + try { + CookieManager.getInstance().setCookie(url, value) + } catch (e: Exception) { + // Probably running on Tachidesk + Log.e("HentaiVN", "failed to set cookie", e) + } + } +} diff --git a/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt b/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt index d6835debb0..3c57e89004 100644 --- a/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt +++ b/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt @@ -1,8 +1,20 @@ package eu.kanade.tachiyomi.extension.vi.hentaivn +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.extension.BuildConfig +import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA +import eu.kanade.tachiyomi.lib.randomua.getPrefUAType +import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -11,174 +23,91 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CookieJar import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import java.text.ParseException +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.text.SimpleDateFormat import java.util.Locale -class HentaiVN : ParsedHttpSource() { +class HentaiVN : ParsedHttpSource(), ConfigurableSource { + + private val preferences: SharedPreferences by lazy { + Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) + } - override val baseUrl = "https://hentaivn.site" - override val lang = "vi" override val name = "HentaiVN" - override val supportsLatest = true + private val defaultBaseUrl = "https://hentaivn.tv" + override val baseUrl = preferences.getString(PREF_KEY_BASE_URL, defaultBaseUrl)!! + + private val domain = baseUrl.toHttpUrl().host private val searchUrl = "$baseUrl/forum/search-plus.php" private val searchByAuthorUrl = "$baseUrl/tim-kiem-tac-gia.html" private val searchAllURL = "$baseUrl/tim-kiem-truyen.html" - private val searchClient = network.cloudflareClient - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .addInterceptor { chain -> - val originalRequest = chain.request() - when { - originalRequest.url.toString().startsWith(searchUrl) -> { - searchClient.newCall(originalRequest).execute() - } - else -> chain.proceed(originalRequest) - } + + override val lang = "vi" + + override val supportsLatest = true + + override val client: OkHttpClient by lazy { + val baseClient = if (preferences.getBoolean(PREF_KEY_ENABLE_CLOUDFLARE_BYPASS, true)) { + network.cloudflareClient + } else { + network.client } - .rateLimit(3) - .build() - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) - .add("Cookie", "view1=1; view4=1") // bypass "captcha" and get popular manga + baseClient.newBuilder() + .addNetworkInterceptor(CookieInterceptor(domain, "view1", "1")) + .addNetworkInterceptor(CookieInterceptor(domain, "view4", "1")) + .setRandomUserAgent( + preferences.getPrefUAType(), + preferences.getPrefCustomUA(), + ) + .rateLimit(1) + .build() + } - private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Referer", "$baseUrl/") // latestUpdates override fun latestUpdatesRequest(page: Int): Request { return GET("$baseUrl/chap-moi.html?page=$page", headers) } - override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item" - override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)" + override fun latestUpdatesSelector() = ".block-item ul li.item" + override fun latestUpdatesFromElement(element: Element): SManga { val manga = SManga.create() - element.select(".box-description a").first()!!.let { + element.select(".box-description a, .box-description-2 a").first()!!.let { manga.setUrlWithoutDomain(it.attr("href")) manga.title = it.text().trim() } - manga.thumbnail_url = element.select(".box-cover a img").attr("data-src") + manga.thumbnail_url = imageFromElement(element.selectFirst(".box-cover a img, .box-cover-2 a img")) return manga } + override fun latestUpdatesNextPageSelector() = ".pagination *:contains(Next)" + // Popular override fun popularMangaRequest(page: Int): Request { return GET("$baseUrl/danh-sach.html?page=$page", headers) } - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() override fun popularMangaSelector() = latestUpdatesSelector() - // Chapter - override fun chapterListSelector() = "table.listing > tbody > tr" - override fun chapterFromElement(element: Element): SChapter { - if (element.select("a").isEmpty()) throw Exception(element.select("h2").html()) - val chapter = SChapter.create() - element.select("a").first()!!.let { - chapter.name = it.select("h2").text() - chapter.setUrlWithoutDomain(it.attr("href")) - } - chapter.date_upload = parseDate(element.select("td:nth-child(2)").text().trim()) - return chapter - } - - override fun chapterListRequest(manga: SManga): Request { - val mangaId = manga.url.substringAfterLast("/").substringBefore('-') - return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers) - } - - private fun parseDate(dateString: String): Long { - return try { - dateFormat.parse(dateString)?.time ?: 0L - } catch (e: ParseException) { - return 0L - } - } - - override fun imageUrlParse(document: Document) = "" - - // Detail - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".main > .page-left > .left-info > .page-info") - val manga = SManga.create() - manga.title = document.selectFirst(".breadcrumb2 li:last-child span")!!.text() - manga.author = infoElement.select("p:contains(Tác giả:) a").text() - manga.description = infoElement.select(":root > p:contains(Nội dung:) + p").text() - manga.genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() } - manga.thumbnail_url = - document.select(".main > .page-right > .right-info > .page-ava > img").attr("src") - manga.status = - parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()) - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Đang tiến hành") -> SManga.ONGOING - status.contains("Đã hoàn thành") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } + override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - // Pages - override fun pageListParse(document: Document): List<Page> { - return document.select("#image > img").mapIndexed { i, e -> - Page(i, imageUrl = e.attr("abs:src")) - } - } + override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() // Search - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - if (document.select("p").toString() - .contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên") - ) { - throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm") - } - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select(".search-des > a, .box-description a").first()!!.let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().trim() - } - manga.thumbnail_url = element.select("div.search-img img, .box-cover a img").attr("abs:src") - return manga - } - - override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)" - - private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers) - private fun searchMangaByIdParse(response: Response, ids: String): MangasPage { - val details = mangaDetailsParse(response) - details.url = "/$ids-doc-truyen-id.html" - return MangasPage(listOf(details), false) - } - override fun fetchSearchManga( page: Int, query: String, @@ -229,34 +158,242 @@ class HentaiVN : ParsedHttpSource() { } } - companion object { - const val PREFIX_ID_SEARCH = "id:" - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=".toHttpUrlOrNull()!! - .newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is TextField -> url.addQueryParameter(filter.key, filter.state) - is GenreList -> - filter.state - .filter { it.state } - .map { it.id } - .forEach { url.addQueryParameter("tag[]", it) } - is GroupList -> { - val group = getGroupList()[filter.state] - url.addQueryParameter("group", group.id) + val url = searchUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("name", query) + addQueryParameter("dou", "") + addQueryParameter("char", "") + addQueryParameter("search", "") + + if (page > 1) { + addQueryParameter("page", page.toString()) + } + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is TextField -> setQueryParameter(filter.key, filter.state) + is GenreList -> + filter.state + .filter { it.state } + .map { it.id } + .forEach { addQueryParameter("tag[]", it) } + is GroupList -> { + val group = getGroupList()[filter.state] + addQueryParameter("group", group.id) + } + else -> {} } - else -> {} } + }.build() + + return GET(url, headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + if (document.select("p").toString() + .contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên") + ) { + throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm") } - return GET(url.toString(), headers) + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + val hasNextPage = searchMangaNextPageSelector().let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) } override fun searchMangaSelector() = - ".search-ul .search-li, .main > .block-left > .block-item > ul > li.item" + ".search-ul .search-li, ${latestUpdatesSelector()}" + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select(".search-des a, .box-description a, .box-description-2 a").first()!!.let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text().trim() + } + manga.thumbnail_url = imageFromElement(element.selectFirst("div.search-img img, .box-cover a img, .box-cover-2 a img")) + return manga + } + + override fun searchMangaNextPageSelector() = ".pagination *:contains(Cuối), .pagination *:contains(Next)" + + private fun searchMangaByIdRequest(id: String) = GET("$searchAllURL?key=$id", headers) + private fun searchMangaByIdParse(response: Response, ids: String): MangasPage { + val details = mangaDetailsParse(response) + details.url = "/$ids-doc-truyen-id.html" + return MangasPage(listOf(details), false) + } + + // Detail + private val genreUrlRegex = Regex("""\"(list-info-theloai-mobile\.php?.+)\"""") + + override fun mangaDetailsParse(document: Document): SManga { + if (document.toString().contains("document.cookie = \"mobile=1")) { // Desktop version + val infoElement = document.select(".main > .page-left > .left-info > .page-info") + return SManga.create().apply { + title = document.selectFirst(".breadcrumb2 li:last-child span")!!.text() + author = infoElement.select("p:contains(Tác giả:) a").text() + description = infoElement.select(":root > p:contains(Nội dung:) + p").text() + genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() } + thumbnail_url = + imageFromElement(document.selectFirst(".main > .page-right > .right-info > .page-ava > img")) + status = + parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()) + } + } else { // Mobile version + val id = document.location().substringAfterLast("/").substringBefore("-") + val documentText = document.toString() + + return SManga.create().apply { + val thumbnailElem = document.selectFirst(".content-images-1 img.cover-1") + thumbnail_url = imageFromElement(thumbnailElem) + + title = thumbnailElem?.attr("alt")?.substringBeforeLast(" Cover")?.trim() ?: client + .newCall(GET("$baseUrl/list-info-ten-mobile.php?id_anime=$id")) + .execute() + .asJsoup() + .select("h3") + .text() + + val genreUrl = genreUrlRegex.find(documentText)?.groupValues?.get(1) + genre = client + .newCall(GET("$baseUrl/$genreUrl")) + .execute() + .asJsoup() + .select("a.tag") + .joinToString { it.text() } + + val infoElement = client + .newCall(GET("$baseUrl/list-info-all-mobile.php?id_anime=$id")) + .execute() + .asJsoup() + author = infoElement.select("p:contains(Tác giả:) a").text() + status = parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()) + description = infoElement.select("p:contains(Nội dung:) + p").text() + } + } + } + + // Chapter + override fun chapterListRequest(manga: SManga): Request { + val mangaId = manga.url.substringAfterLast("/").substringBefore('-') + return GET("$baseUrl/list-showchapter.php?idchapshow=$mangaId", headers) + } + + override fun chapterListSelector() = "table.listing > tbody > tr" + + override fun chapterFromElement(element: Element): SChapter { + if (element.select("a").isEmpty()) throw Exception(element.select("h2").html()) + val chapter = SChapter.create() + element.select("a").first()!!.let { + chapter.name = it.select("h2").text() + chapter.setUrlWithoutDomain(it.attr("href")) + } + chapter.date_upload = parseDate(element.select("td:nth-child(2)").text().trim()) + return chapter + } + + // Pages + override fun pageListParse(document: Document): List<Page> { + return document.select("#image > img").mapIndexed { i, e -> + Page(i, imageUrl = imageFromElement(e)) + } + } + + override fun imageUrlParse(document: Document) = "" + + private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) + + private fun parseDate(dateString: String): Long { + return kotlin.runCatching { + dateFormat.parse(dateString)?.time + }.getOrNull() ?: 0L + } + + private fun parseStatus(status: String?) = when { + status == null -> SManga.UNKNOWN + status.contains("Đang tiến hành") -> SManga.ONGOING + status.contains("Đã hoàn thành") -> SManga.COMPLETED + status.contains("Tạm ngưng") -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + + private fun imageFromElement(element: Element?): String? { + if (element == null) return null + + return when { + element.hasAttr("data-src") -> element.attr("abs:data-src") + element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src") + element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc") + element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ") + else -> element.attr("abs:src") + } + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + SwitchPreferenceCompat(screen.context).apply { + key = PREF_KEY_ENABLE_CLOUDFLARE_BYPASS + title = TITLE_ENABLE_CLOUDFLARE_BYPASS + summary = SUMMARY_ENABLE_CLOUDFLARE_BYPASS + + setDefaultValue(true) + + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + true + } + }.also(screen::addPreference) + + EditTextPreference(screen.context).apply { + key = PREF_KEY_BASE_URL + title = TITLE_BASE_URL + summary = SUMMARY_BASE_URL + + setDefaultValue(defaultBaseUrl) + dialogTitle = TITLE_BASE_URL + + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + true + } + }.also(screen::addPreference) + + ListPreference(screen.context).apply { + key = PREF_KEY_RANDOM_UA + title = TITLE_RANDOM_UA + entries = ENTRIES_RANDOM_UA + entryValues = VALUES_RANDOM_UA + summary = "%s" + setDefaultValue("off") + + setOnPreferenceChangeListener { _, _ -> + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + true + } + }.also(screen::addPreference) + + EditTextPreference(screen.context).apply { + key = PREF_KEY_CUSTOM_UA + title = TITLE_CUSTOM_UA + summary = SUMMARY_CUSTOM_UA + setOnPreferenceChangeListener { _, newValue -> + try { + Headers.Builder().add("User-Agent", newValue as String).build() + Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() + true + } catch (e: IllegalArgumentException) { + Toast.makeText(screen.context, "Chuỗi đại diện người dùng không hợp lệ: ${e.message}", Toast.LENGTH_LONG).show() + false + } + } + }.also(screen::addPreference) + } private class Alls : Filter.Text("Tìm tất cả") private class Author : Filter.Text("Tác giả") @@ -279,12 +416,12 @@ class HentaiVN : ParsedHttpSource() { Author(), TextField("Doujinshi", "dou"), TextField("Nhân vật", "char"), - GenreList(getGenreList()), GroupList(getGroupList()), + GenreList(getGenreList()), ) - // jQuery.makeArray($('#container > div > div > div.box-box.textbox > form > ul:nth-child(7) > li').map((i, e) => `Genre("${e.textContent}", "${e.children[0].value}")`)).join(',\n') - // https://hentaivn.net/forum/search-plus.php + // console.log(jQuery.makeArray($('ul.ul-search > li').map((i, e) => `Genre("${e.textContent}", "${e.children[0].value}")`)).join(',\n')) + // https://hentaivn.autos/forum/search-plus.php private fun getGenreList() = listOf( Genre("3D Hentai", "3"), Genre("Action", "5"), @@ -459,7 +596,7 @@ class HentaiVN : ParsedHttpSource() { ) // jQuery.makeArray($('#container > div > div > div.box-box.textbox > form > ul:nth-child(8) > li').map((i, e) => `TransGroup("${e.textContent}", "${e.children[0].value}")`)).join(',\n') - // https://hentaivn.net/forum/search-plus.php + // https://hentaivn.autos/forum/search-plus.php private fun getGroupList() = arrayOf( TransGroup("Tất cả", "0"), TransGroup("Đang cập nhật", "1"), @@ -513,4 +650,27 @@ class HentaiVN : ParsedHttpSource() { TransGroup("Depressed Lolicons Squad - DLS", "52"), TransGroup("Heaven Of The Fuck", "53"), ) + + companion object { + const val PREFIX_ID_SEARCH = "id:" + + const val RESTART_TACHIYOMI = "Khởi động lại Tachiyomi để áp dụng thay đổi." + + const val PREF_KEY_ENABLE_CLOUDFLARE_BYPASS = "enable_cloudflare" + const val TITLE_ENABLE_CLOUDFLARE_BYPASS = "Kích hoạt bỏ qua Cloudflare" + const val SUMMARY_ENABLE_CLOUDFLARE_BYPASS = "Nếu bật khi không cần thiết, có thể gây lỗi \"Bỏ qua Cloudflare thất bại\" giả." + + const val PREF_KEY_BASE_URL = "override_base_url_${BuildConfig.VERSION_CODE}" + const val TITLE_BASE_URL = "Thay đổi tên miền" + const val SUMMARY_BASE_URL = "Thay đổi này là tạm thời và sẽ bị xoá khi cập nhật tiện ích mở rộng." + + const val PREF_KEY_RANDOM_UA = "pref_key_random_ua_" + const val TITLE_RANDOM_UA = "Chuỗi đại diện người dùng ngẫu nhiên" + val ENTRIES_RANDOM_UA = arrayOf("Tắt", "Máy tính", "Di động") + val VALUES_RANDOM_UA = arrayOf("off", "desktop", "mobile") + + const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua_" + const val TITLE_CUSTOM_UA = "Chuỗi đại diện người dùng tuỳ chỉnh" + const val SUMMARY_CUSTOM_UA = "Để trống để dùng chuỗi đại diện người dùng mặc định của ứng dụng. Cài đặt này bị vô hiệu nếu chuỗi đại diện người dùng ngẫu nhiên được bật." + } } diff --git a/src/vi/lxhentai/AndroidManifest.xml b/src/vi/lxhentai/AndroidManifest.xml index 14f6600880..c64269cfc1 100644 --- a/src/vi/lxhentai/AndroidManifest.xml +++ b/src/vi/lxhentai/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -21,4 +20,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/vi/mangaxy/AndroidManifest.xml b/src/vi/mangaxy/AndroidManifest.xml index b4571bfa87..8072ee00db 100644 --- a/src/vi/mangaxy/AndroidManifest.xml +++ b/src/vi/mangaxy/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/vi/medoctruyentranh/AndroidManifest.xml b/src/vi/medoctruyentranh/AndroidManifest.xml deleted file mode 100644 index 30deb7f797..0000000000 --- a/src/vi/medoctruyentranh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/medoctruyentranh/build.gradle b/src/vi/medoctruyentranh/build.gradle deleted file mode 100644 index 239edcf18c..0000000000 --- a/src/vi/medoctruyentranh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'MeDocTruyenTranh' - pkgNameSuffix = 'vi.medoctruyentranh' - extClass = '.MeDocTruyenTranh' - extVersionCode = 6 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3717736513..0000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ba41ace7b7..0000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4715cb96ba..0000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 544a1d3624..0000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4ea96e542f..0000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/web_hi_res_512.png b/src/vi/medoctruyentranh/res/web_hi_res_512.png deleted file mode 100644 index deceb34250..0000000000 Binary files a/src/vi/medoctruyentranh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt b/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt deleted file mode 100644 index d8d7dc94f4..0000000000 --- a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt +++ /dev/null @@ -1,189 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.medoctruyentranh - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Locale - -class MeDocTruyenTranh : ParsedHttpSource() { - - override val name = "MeDocTruyenTranh" - - override val baseUrl = "https://www.medoctruyentranh.net" - - override val lang = "vi" - - override val supportsLatest = false - - override val client = network.cloudflareClient - - private val json: Json by injectLazy() - - override fun popularMangaSelector() = "div.classifyList a" - - override fun searchMangaSelector() = ".listCon a" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/tim-truyen/toan-bo" + if (page > 1) "/$page" else "", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - // trying to build URLs from this JSONObject could cause issues but we need it to get thumbnails - val nextData = document.select("script#__NEXT_DATA__").first()!! - .let { json.parseToJsonElement(it.data()).jsonObject } - - val titleCoverMap = nextData["props"]!! - .jsonObject["pageProps"]!! - .jsonObject["initialState"]!! - .jsonObject["classify"]!! - .jsonObject["comics"]!! - .jsonArray.associate { - Pair( - it.jsonObject["title"]!!.jsonPrimitive.content, - it.jsonObject["coverimg"]!!.jsonPrimitive.content, - ) - } - - val mangas = document.select(popularMangaSelector()).map { - popularMangaFromElement(it).apply { - thumbnail_url = titleCoverMap[this.title] - } - } - - return MangasPage(mangas, document.select(popularMangaNextPageSelector()) != null) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/$query", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("div.storytitle").text() - setUrlWithoutDomain(element.attr("href")) - } - } - - override fun popularMangaNextPageSelector() = "div.page_floor a.focus + a + a" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val jsonData = element.ownerDocument()!!.select("#__NEXT_DATA__").first()!!.data() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.storytitle").text() - - val indexOfManga = jsonData.indexOf(manga.title) - val startIndex = jsonData.indexOf("coverimg", indexOfManga) + 11 - val endIndex = jsonData.indexOf("}", startIndex) - 1 - manga.thumbnail_url = jsonData.substring(startIndex, endIndex) - return manga - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val nextData = document.select("script#__NEXT_DATA__").first()!! - .let { json.parseToJsonElement(it.data()).jsonObject } - - val mangaDetail = nextData["props"]!! - .jsonObject["pageProps"]!! - .jsonObject["initialState"]!! - .jsonObject["detail"]!! - .jsonObject["story_item"]!! - .jsonObject - - title = mangaDetail["title"]!!.jsonPrimitive.content - author = mangaDetail["author_list"]!!.jsonArray.joinToString { it.jsonPrimitive.content } - genre = mangaDetail["category_list"]!!.jsonArray.joinToString { it.jsonPrimitive.content } - description = mangaDetail["summary"]!!.jsonPrimitive.content - status = parseStatus(mangaDetail["is_updating"]!!.jsonPrimitive.content) - thumbnail_url = mangaDetail["coverimg"]!!.jsonPrimitive.content - } - - private fun parseStatus(status: String) = when { - status.contains("1") -> SManga.ONGOING - status.contains("0") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.chapters a" - - override fun chapterListParse(response: Response): List<SChapter> { - val nextData = response.asJsoup().select("script#__NEXT_DATA__").first()!! - .let { json.parseToJsonElement(it.data()).jsonObject } - - return nextData["props"]!! - .jsonObject["pageProps"]!! - .jsonObject["initialState"]!! - .jsonObject["detail"]!! - .jsonObject["story_chapters"]!! - .jsonArray - .flatMap { it.jsonArray } - .map { - val chapterObj = it.jsonObject - - SChapter.create().apply { - name = chapterObj["title"]!!.jsonPrimitive.content - date_upload = parseChapterDate(chapterObj["time"]!!.jsonPrimitive.content) - setUrlWithoutDomain("${response.request.url}/${chapterObj["chapter_index"]!!.jsonPrimitive.content}") - } - } - .reversed() - } - - private fun parseChapterDate(date: String): Long { - // 2019-05-09T07:09:58 - val dateFormat = SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss", - Locale.US, - ) - return dateFormat.parse(date)?.time ?: 0L - } - - override fun pageListParse(document: Document): List<Page> { - val nextData = document.select("script#__NEXT_DATA__").firstOrNull() - ?.let { json.parseToJsonElement(it.data()).jsonObject } - ?: return emptyList() - - return nextData["props"]!! - .jsonObject["pageProps"]!! - .jsonObject["initialState"]!! - .jsonObject["read"]!! - .jsonObject["detail_item"]!! - .jsonObject["elements"]!! - .jsonArray - .mapIndexed { i, jsonEl -> - Page(i, "", jsonEl.jsonObject["content"]!!.jsonPrimitive.content) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("This method should not be called!") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("This method should not be called!") -} diff --git a/src/vi/qmanga/AndroidManifest.xml b/src/vi/qmanga/AndroidManifest.xml deleted file mode 100644 index f059b93aff..0000000000 --- a/src/vi/qmanga/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - <application> - <activity - android:name="eu.kanade.tachiyomi.extension.vi.qmanga.QMangaUrlActivity" - android:excludeFromRecents="true" - android:exported="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data android:host="qmanga5.net" /> - <data - android:pathPattern="/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/vi/qmanga/build.gradle b/src/vi/qmanga/build.gradle deleted file mode 100644 index 88f3c29edb..0000000000 --- a/src/vi/qmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'QManga' - pkgNameSuffix = 'vi.qmanga' - extClass = '.QManga' - extVersionCode = 1 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/vi/qmanga/res/mipmap-hdpi/ic_launcher.png b/src/vi/qmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6f62aa8eb6..0000000000 Binary files a/src/vi/qmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/qmanga/res/mipmap-mdpi/ic_launcher.png b/src/vi/qmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 9f6f86fc69..0000000000 Binary files a/src/vi/qmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/qmanga/res/mipmap-xhdpi/ic_launcher.png b/src/vi/qmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9f2fed409b..0000000000 Binary files a/src/vi/qmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/qmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/qmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d18fa11511..0000000000 Binary files a/src/vi/qmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/qmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/qmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 05bcfed06f..0000000000 Binary files a/src/vi/qmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/qmanga/res/web_hi_res_512.png b/src/vi/qmanga/res/web_hi_res_512.png deleted file mode 100644 index 329c8279b0..0000000000 Binary files a/src/vi/qmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QManga.kt b/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QManga.kt deleted file mode 100644 index ddc2219e1d..0000000000 --- a/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QManga.kt +++ /dev/null @@ -1,1064 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.qmanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -class QManga : ParsedHttpSource() { - - override val name: String = "QManga" - - override val baseUrl: String = "https://qmanga5.net" - - override val lang: String = "vi" - - override val supportsLatest: Boolean = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = - super.headersBuilder().add("Referer", "$baseUrl/") - - private val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.US).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - } - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/de-nghi/pho-bien/xem-nhieu?page=$page", headers) - - override fun popularMangaSelector(): String = "div.content-tab li" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - val imageCommicTab = element.select(".image-commic-bxh, .image-commic-tab") - - setUrlWithoutDomain(imageCommicTab.select("a").attr("href")) - thumbnail_url = imageCommicTab.select("img").attr("abs:data-src") - - title = element.select(".title-commic-tab").text() - } - - override fun popularMangaNextPageSelector(): String = "li.pag-next:not(.disabled)" - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/de-nghi/pho-bien/moi-nhat?page=$page", headers) - - override fun latestUpdatesSelector(): String = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() - - override fun fetchSearchManga( - page: Int, - query: String, - filters: FilterList, - ): Observable<MangasPage> { - return when { - query.startsWith(PREFIX_ID_SEARCH) -> { - val id = query.removePrefix(PREFIX_ID_SEARCH).trim() - val url = "/$id.html" - fetchMangaDetails( - SManga.create().apply { - this.url = url - }, - ) - .map { - it.url = url - MangasPage(listOf(it), false) - } - } - else -> super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - if (query.isNotBlank()) { - addPathSegment("tim-kiem") - addQueryParameter("q", query) - addQueryParameter("page", page.toString()) - } else { - // the `comedy` part doesn't really matter, it just wants a thing here to load - // the filtering page - addPathSegments("danh-muc/comedy") - (if (filters.isEmpty()) getFilterList() else filters) - .filterIsInstance<UriPartFilter>() - .forEach { - addQueryParameter(it.query, it.toUriPart()) - } - } - }.build().toString() - return GET(url, headers) - } - - override fun searchMangaSelector(): String = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select(".title-commic-detail").text() - author = document.select(".writer li").joinToString { it.text() } - description = document.select(".desc-commic-detail").text() - genre = document.select(".categories-list-detail-commic li a").joinToString { it.text() } - thumbnail_url = document.select(".img-commic-detail img").attr("abs:data-src") - - val statusString = document.select(".status_commic p").text() - status = when (statusString) { - "Đang tiến hành" -> SManga.ONGOING - "Đã hoàn thành" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - override fun chapterListSelector(): String = ".ul-list-chaper-detail-commic li" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a h3").text() - - val dateUploadString = element.select("span").first()?.text() - if (!dateUploadString.isNullOrEmpty()) { - date_upload = runCatching { - dateFormat.parse(dateUploadString)?.time - }.getOrNull() ?: 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - // as an anti-scraper measure, you need to type a given keyword and complete - // a reCAPTCHA to unlock some chapters. i'm not doing that. - val isLocked = document.select(".lock-screen").first() != null - if (isLocked) { - throw Exception("Vui lòng mở khoá chương truyện này trong WebView") - } - - return document.select("img.image-commic").mapIndexed { idx, it -> - Page(idx, imageUrl = it.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = - throw UnsupportedOperationException("Not used") - - override fun getFilterList(): FilterList = FilterList( - Filter.Header("Không dùng được với tìm bằng tiêu đề"), - GenreFilter(), - ScanlatorFilter(), - AuthorFilter(), - StatusFilter(), - SortFilter(), - ) - - open class UriPartFilter( - name: String, - val query: String, - private val vals: Array<Pair<String, String>>, - state: Int = 0, - ) : Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) { - fun toUriPart() = vals[state].second - } - - private class StatusFilter : UriPartFilter( - "Trạng thái", - "status", - arrayOf( - Pair("Tất cả", ""), - Pair("Đã hoàn thành", "1"), - Pair("Chưa hoàn thành", "2"), - ), - ) - - private class SortFilter : UriPartFilter( - "Sắp xếp", - "sort", - arrayOf( - Pair("Mới nhất", "moi-nhat"), - Pair("Đang hot", "dang-hot"), - Pair("Cũ nhất", "cu-nhat"), - ), - 1, - ) - - // console.log([...document.querySelectorAll("select[name=cate] option")].map(e => `Pair("${e.innerText}", "${e.value}")`).join(",\n")) - private class GenreFilter : UriPartFilter( - "Thể loại", - "cate", - arrayOf( - Pair("Tất cả", ""), - Pair("Comedy", "comedy"), - Pair("Historical", "historical"), - Pair("Manhua", "manhua"), - Pair("Mystery", "mystery"), - Pair("Romance", "romance"), - Pair("Shoujo", "shoujo"), - Pair("Truyện Màu", "truyen-mau"), - Pair("Fantasy", "fantasy"), - Pair("Supernatural", "supernatural"), - Pair("School Life", "school-life"), - Pair("Slice of life", "slice-of-life"), - Pair("Drama", "drama"), - Pair("Action", "action"), - Pair("Adventure", "adventure"), - Pair("Martial Arts", "martial-arts"), - Pair("One Shot", "one-shot"), - Pair("Sci-fi", "sci-fi"), - Pair("Shounen", "shounen"), - Pair("Ecchi", "ecchi"), - Pair("Horror", "horror"), - Pair("Adult", "adult"), - Pair("Shounen Ai", "shounen-ai"), - Pair("Webtoon", "webtoon"), - Pair("Harem", "harem"), - Pair("Yaoi", "yaoi"), - Pair("Truyện Nhật (Manga)", "truyen-nhat-manga"), - Pair("Doujinshi", "doujinshi"), - Pair("Yuri", "yuri"), - Pair("Smut", "smut"), - Pair("Chuyển sinh", "chuyen-sinh"), - Pair("Seinen", "seinen"), - Pair("Sports", "sports"), - Pair("manga", "manga"), - Pair("Manhwa", "manhwa"), - Pair("Truyền Việt", "truyen-viet"), - Pair("Mecha", "mecha"), - Pair("Comic", "comic"), - Pair("Soft Yuri", "soft-yuri"), - Pair("Trinh Thám", "trinh-tham"), - Pair("Xuyên Không", "xuyen-khong"), - Pair("Soft Yaoi", "soft-yaoi"), - Pair("Psychological", "psychological"), - Pair("NTR", "ntr"), - Pair("Mature", "mature"), - Pair("Tạp chí truyện tranh", "tap-chi-truyen-tranh"), - Pair("Đam Mỹ", "dam-my"), - Pair("Detective", "detective"), - Pair("Magic", "magic"), - Pair("Boy Love", "boy-love"), - Pair("Live action", "live-action"), - Pair("Truyện scan", "truyen-scan"), - Pair("18+", "18"), - Pair("Việt Nam", "viet-nam"), - Pair("Trọng Sinh", "trong-sinh"), - Pair("Huyền Huyễn", "huyen-huyen"), - Pair("Music", "music"), - Pair("Tu chân - tu tiên", "tu-chan-tu-tien"), - Pair("Game", "game"), - Pair("S&M", "sm"), - Pair("Hài hước", "hai-huoc"), - Pair("Không Che", "khong-che"), - Pair("Crime", "crime"), - Pair("Girl Love - Bách Hợp", "girl-love-bach-hop"), - Pair("Romance - Lãng Mạn", "romance-lang-man"), - Pair("Monsters", "monsters"), - Pair("Trùng Sinh", "trung-sinh"), - Pair("Phiêu Lưu", "phieu-luu"), - Pair("Bl manhua", "bl-manhua"), - Pair("Hành Động", "hanh-dong"), - Pair("hắc bang", "hac-bang"), - Pair("vampire", "vampire"), - Pair("phúc hắc công", "phuc-hac-cong"), - Pair("Hệ thống", "he-thong"), - Pair("tổng tài công", "tong-tai-cong"), - Pair("Hiện đại", "hien-dai"), - Pair("vườn trường", "vuon-truong"), - Pair("ABO", "abo"), - Pair("Showbiz", "showbiz"), - Pair("BL Manhwa", "bl-manhwa"), - Pair("Văn phòng", "van-phong"), - ), - ) - - // console.log([...document.querySelectorAll("select[name=writer] option")].map(e => `Pair("${e.innerText}", "${e.value}")`).join(",\n")) - private class AuthorFilter : UriPartFilter( - "Tác giả", - "writer", - arrayOf( - Pair("Tất cả", ""), - Pair("Đang Cập Nhật", "dang-cap-nhat"), - Pair("NetTruyen", "nettruyen"), - Pair("Đường Gia Tam Thiếu", "duong-gia-tam-thieu"), - Pair("Mộng tiên giới", "mong-tien-gioi"), - Pair("Sweet Comic", "sweet-comic"), - Pair("Trịnh Kiến Hòa", "trinh-kien-hoa"), - Pair("MH Channel", "mh-channel"), - Pair("Thiên Tằm Thổ Đậu", "thien-tam-tho-dau"), - Pair("Kamachi Kazuma", "kamachi-kazuma"), - Pair("Melody Happy Group", "melody-happy-group"), - Pair("Khôi Quỷ Các", "khoi-quy-cac"), - Pair("Đặng Chí Huy", "dang-chi-huy"), - Pair("Aikawa Saki", "aikawa-saki"), - Pair("Đang tiến hành", "dang-tien-hanh"), - Pair("Linh Miêu Nhóm", "linh-mieu-nhom"), - Pair("Hoàng Ngọc Lang", "hoang-ngoc-lang"), - Pair("Rumiko Takahashi", "rumiko-takahashi"), - Pair("Manhwa TV", "manhwa-tv"), - Pair("MIYAJIMA Reiji", "miyajima-reiji"), - Pair("Góc nhỏ của Mamoru", "goc-nho-cua-mamoru"), - Pair("CHƯA CẬP NHẬT", "chua-cap-nhat"), - Pair("Shiraishi Yuki", "shiraishi-yuki"), - Pair("Ôn Nhật Lương", "on-nhat-luong"), - Pair("Murata Shinya", "murata-shinya"), - Pair("Jeon Geuk Jin", "jeon-geuk-jin"), - Pair("Kotoyama", "kotoyama"), - Pair("Suzuki Nakaba", "suzuki-nakaba"), - Pair("One", "one"), - Pair("Fujiko F. Fujio", "fujiko-f-fujio"), - Pair("Hiroyuki", "hiroyuki"), - Pair("YAGAMI Rina", "yagami-rina"), - Pair("Chưỡng Duyệt", "chuong-duyet"), - Pair("INOUE Kenji", "inoue-kenji"), - Pair("Mã Vinh Thành", "ma-vinh-thanh"), - Pair("Bác Dịch Động Mạn", "bac-dich-dong-man"), - Pair("Nakamura Asumiko", "nakamura-asumiko"), - Pair("Hữu Lộc Văn Hoa", "huu-loc-van-hoa"), - Pair("Pageratta", "pageratta"), - Pair("Fushimi Tsukasa", "fushimi-tsukasa"), - Pair("Park Jin Hwan", "park-jin-hwan"), - Pair("Khưu Phúc Long", "khuu-phuc-long"), - Pair("Phỉ Nguyệt Chi Truyện", "phi-nguyet-chi-truyen"), - Pair("Azusa Kina", "azusa-kina"), - Pair("KITADA Ryouma", "kitada-ryouma"), - Pair("Mikoshiba Nana", "mikoshiba-nana"), - Pair("Moscareto", "moscareto"), - Pair("Tooyama Ema", "tooyama-ema"), - Pair("Shinkai Makoto", "shinkai-makoto"), - Pair("Mikawa Ghost", "mikawa-ghost"), - Pair("Mitsunaga Yasunori", "mitsunaga-yasunori"), - ), - ) - - // console.log([...document.querySelectorAll("select[name=translate] option")].map(e => `Pair("${e.innerText}", "${e.value}")`).join(",\n")) - private class ScanlatorFilter : UriPartFilter( - "Nhóm dịch", - "translate", - arrayOf( - Pair("Tất cả", ""), - Pair("Miu Miu", "miu-miu"), - Pair("Qmanga Team", "qmanga-team"), - Pair("Aoi Ame Group", "aoi-ame-group"), - Pair("Nhàn Rỗi Team", "nhàn-rõi-team"), - Pair("Beaz Team", "beaz-team"), - Pair("Delay Team", "delay-team"), - Pair("Trà Có Ga", "tra-co-ga"), - Pair("Âu Thiên Team", "au-thien-team"), - Pair("Rainbow Factory", "rainbow-factory"), - Pair("Kẹo Nướng Trong Mưa", "keo-nuong-trong-mua"), - Pair("Lết từ từ team", "let-tu-tu-team"), - Pair("Nhất Tiếu Chi Vương", "nhat-tieu-chi-vuong"), - Pair("404 Error Team", "404-error-team"), - Pair("Chill Blue Team", "chill-blue-team"), - Pair("Roads Untraveled", "roads-untraveled"), - Pair("Đen Xì", "den-xi"), - Pair("Hồi còn sống tụi tui dịch truyện này", "hoi-con-song-tui-tui-dich-truyen-nay"), - Pair("Phong Linh Cung - 风泠龚", "phong-linh-cung-风泠龚"), - Pair("Secret Bears", "secret-bears"), - Pair("KloudLand", "kloudland"), - Pair("chiếc lều màu hồng", "chiec-leu-mau-hong"), - Pair("Anastashia", "anastashia"), - Pair("Ankenna", "ankenna"), - Pair("Dammei BL", "dammei-bl"), - Pair("Vườn Cam Comic", "vuon-cam-comic"), - Pair("Xiao Cin", "xiao-cin"), - Pair("Feisty Forwarders", "feisty-forwarders"), - Pair("Góc nhỏ của Mưa", "goc-nho-cua-mua"), - Pair("Băng Di Team", "bang-di-team"), - Pair("Junii House", "junii-house"), - Pair("Augenstern Team", "augenstern-team"), - Pair("Player Team", "player-team"), - Pair("Fushi Translation Team", "fushi-translation-team"), - Pair("Bông Cúc Thần Chưởng", "bong-cuc-than-chuong"), - Pair("Cục Bột Nhỏ", "cuc-bot-nho"), - Pair("Michi Team", "michi-team"), - Pair("Cửa hàng hoa Phong Lữ", "cua-hang-hoa-phong-lu"), - Pair("Boss Translation BL", "boss-translation-bl"), - Pair("Bạch Nguyệt Comics", "bach-nguyet-comics"), - Pair("Mary Hạ Lục", "mary-ha-luc"), - Pair("Thiên Chu Chi Dạ", "thien-chu-chi-da"), - Pair("GOLD GRAPE", "gold-grape"), - Pair("Solenda Sabie", "solenda-sabie"), - Pair("Beautiful moment", "beautiful-moment"), - Pair("ㅤㅤㅤ", "ㅤㅤㅤ"), - Pair("Púp Pơ Team", "pup-po-team"), - Pair("DamLord", "damlord"), - Pair("Động Mlem Của Hà", "dong-mlem-cua-ha"), - Pair("MiniLights", "minilights"), - Pair("Tayanen Team", "tayanen-team"), - Pair("Fish&Chip Translation Team", "fishchip-translation-team"), - Pair("Trạm Dịch - BeoNa", "tram-dich-beona"), - Pair("Vì Tinh Tú Hình Củ Chuối", "vi-tinh-tu-hinh-cu-chuoi"), - Pair("Ổ Truyện Nhà Táo", "o-truyen-nha-tao-otnt"), - Pair("Daw World", "daw-world"), - Pair("Mi Tesoro", "mi-tesoro"), - Pair("Im Group", "im-group"), - Pair("Ttriteam", "ttriteam"), - Pair("Rina Team", "rina-team"), - Pair("1915 Station", "1915-station"), - Pair("Twinkling Stars Team", "twinkling-stars-team"), - Pair("Phù Thủy Du Hành Team", "phu-thuy-du-hanh-team"), - Pair("Mợt mỏi team", "mot-moi-team"), - Pair("Chúng tôi là XLNĐ hê hê", "chung-toi-la-xlnd-he-he"), - Pair("Siu Nhơn Vàng", "siu-nhon-vang"), - Pair("MiuTee team", "miutee-team"), - Pair("Cừu Pông Translation", "cuu-pong-translation"), - Pair("Truyện Nhà Cừu", "truyen-nha-cuu"), - Pair("Hố Tiền Sử", "ho-tien-su"), - Pair("Cục Bông únu", "cuc-bong-unu"), - Pair("Ăn cơm chó cùng Swoopy", "an-com-cho-cung-swoopy"), - Pair("Gấu Mèo Team", "gau-meo-team"), - Pair("Nhà Sủi Cảo", "nha-sui-cao"), - Pair("Thất Nguyệt Team", "that-nguyet-team"), - Pair("Gió Réo Rì Rào", "gio-reo-ri-rao"), - Pair("Team Dịch Truyện Chào Cậu", "team-dich-truyen-chao-cau"), - Pair("Lưu Huỳnh Màu Vàng", "luu-huynh-mau-vang"), - Pair("Yukino Team", "yukino-team"), - Pair("Chuột Đồng Team", "chuot-dong-team"), - Pair("TIÊU DAO Comics", "tieu-dao-comics"), - Pair("Ổ nhỏ của Amber và Tio", "ổ-nhỏ-của-amber-và-tio"), - Pair("Chuồng Gà Mini", "chuong-ga-mini"), - Pair("Tảo Comics", "tao-comics"), - Pair("Gemini", "gemini"), - Pair("Ổ truyện của cá heo nhỏ", "o-truyen-cua-ca-heo-nho"), - Pair("Bơ Đậu Phộng", "bo-dau-phong"), - Pair("Khủng Long Ngàn Thước", "khung-long-ngan-thuoc"), - Pair("Ổ Lười Nhà Cá", "o-luoi-nha-ca"), - Pair("Tắc Kè Team", "tac-ke-team"), - Pair("Green Flow Comic", "green-flow-comic"), - Pair("AmyR", "amyr"), - Pair("•《Ngôi Nhà Số 19》•", "ngoi-nha-so-19"), - Pair("Ổ Mèo Hoang", "o-meo-hoang"), - Pair("Lâu đài băng - Ice Castle", "lau-dai-bang-ice-castle"), - Pair("Sakura Translation Team", "sakura-translation-team"), - Pair("Hoàng Tử Ếch", "hoang-tu-ech"), - Pair("Kitsune Team", "kitsune-team"), - Pair("Truyện nhà Scrunchies", "truyen-nha-scrunchies"), - Pair("Lang Nhị", "lang-nhi"), - Pair("Truyện Của Lynnaa", "truyen-cua-lynnaa"), - Pair("BimBim Team", "bimbim-team"), - Pair("Ổ Truyện Của Meo Meo", "o-truyen-cua-meo-meo"), - Pair("𝑪𝒐̂́ 𝑵𝒉𝒂̂𝒏 𝑿𝒂 𝑳𝒂̣", "co-nhan-xa-la"), - Pair("I.M", "im"), - Pair("Thiên Sơ Các", "thien-so-các"), - Pair("Wait A Moment", "wait-a-moment"), - Pair("Marceiline Weeaboo", "marceiline-weeaboo"), - Pair("Ổ Truyện Dịch Của SunYi", "o-truyen-dich-cua-sunyi"), - Pair("Tẩm Cung Nhà Miêu", "tam-cung-nha-mieu"), - Pair("Only Chuối.", "only-chuoi"), - Pair("Goky Team", "goky-team"), - Pair("Wicked House Translation Team", "wicked-house-translation-team"), - Pair("Thanh Thời - 清时", "thanh-thoi-清时"), - Pair("Cuồng Manga", "cuong-manga"), - Pair("Team Mèo Lười", "team-meo-luoi"), - Pair("Chưa Có Tên Nên Mình Để Vậy Á", "chua-co-ten-nen-minh-de-vay-a"), - Pair("Đào Hoa Đàm Thủy - 桃花潭水", "dao-hoa-dam-thuy-桃花潭水"), - Pair("Angels And Demons", "angels-and-demons"), - Pair("Lazie chăm chỉ", "lazie-cham-chi"), - Pair("Fufujoshi Is We", "fufujoshi-is-we"), - Pair("SGG.v3", "sggv3"), - Pair("Kén Chọn team", "ken-chon-team"), - Pair("BoraBora", "borabora"), - Pair("Day Dreaming Cat Comic", "day-dreaming-cat-comic"), - Pair("Mộng Mer Team", "mong-mer-team"), - Pair("Stray Dogs", "stray-dogs"), - Pair("The Duckling Team", "the-duckling-team"), - Pair("Flight Sweet Team", "flight-sweet-team"), - Pair("Krazy Solar Team", "krazy-solar-team"), - Pair("RAPTOR Project Team", "raptor-project-team"), - Pair("Thỏ Mùa Đông", "tho-mua-dong"), - Pair("Toasted Bread Translation Group", "toasted-bread-translation-group"), - Pair("Hội_pay_lăk", "hoipaylak"), - Pair("Châu", "chau"), - Pair("Hội_Đêm_Pay_Lăk", "hoidempaylak"), - Pair("Trần thanh", "tran-thanh"), - Pair("nhuệ tinh tinh", "nhue-tinh-tinh"), - Pair("test1234567123", "test1234567123"), - Pair("Thích Ăn Bún Bò", "thich-an-bun-bo"), - Pair("Nhện Nhọ Team", "nhen-nho-team"), - Pair("Cú Đêm", "Cy"), - Pair("LGBTQ", "lgbtq"), - Pair("Moe team", "moe-team"), - Pair("Yellow Ribbon Translation", "yellow-ribbon-translation"), - Pair("t_3r5", "t3r5"), - Pair("Chan Zu team bông du", "chan-zu-team-bong-du"), - Pair("Cá Muối Hội", "ca-muoi-hoi"), - Pair("Dưa hấu", "dua-hau"), - Pair("Bát Nhã A", "bat-nha-a"), - Pair("Fufujjoshi Is Us-Fiu", "fufujjoshi-is-us-fiu"), - Pair("Only Me Translate", "only-me"), - Pair("企鹅-Doanh Trại Cánh Cụt", "doanh-trai-canh-cut"), - Pair("Test", "test"), - Pair("Cục Bông Nhỏ", "cuc-bong-nho"), - Pair("Thỏ Nhỏ là Hủ", "tho-nho-la-hu"), - Pair("Ghost Team", "ghost-team"), - Pair("Kem Tươi Comic", "kem-tuoi-comic"), - Pair("Vergissmeinnicht", "thang13vamuathu5"), - Pair("Vivender", "vivender"), - Pair("Sweet Girl Crime Gang", "sweet-girl-crime-gang"), - Pair("Lạc Thanh Hà", "lac-thanh-ha"), - Pair("Zhu Comics", "zhu-comics"), - Pair("Team lạnh lùng", "team-lanh-lung"), - Pair("bí mật", "bi-mat"), - Pair("Lon nước Ngọt Đéng Yêu", "lon-nuoc-ngot-deng-yeu"), - Pair("Yumi", "yumi"), - Pair("San Đíc Bự", "san-dic-bu"), - Pair("Fluffy Stuff Team", "fluffy-stuff-team"), - Pair("Merry team", "merry-team"), - Pair("Sunflower", "sunflower"), - Pair("Chocola Team", "chocola-team"), - Pair("Pudding Team", "pudding-team"), - Pair("Wabisabi Team", "wabisabi-team"), - Pair("Only Me 7", "only-me-7"), - Pair("hội FA", "hoi-fa"), - Pair("Nekotruotvan", "neko-truot-van"), - Pair("FuyuNguyen0117", "fuyunguyen0117"), - Pair("KGHkhangteam", "kghkhangteam"), - Pair("Meat Team", "meat-team"), - Pair("Luxubu Team", "luxubu-team"), - Pair("naxuanquynh.pro 123", "naxuanquynhpro-123"), - Pair("naxuanquynh.pro 231", "naxuanquynhpro-231"), - Pair("TRACOGA", "tracoga"), - Pair("Mary Elina", "mary-elina"), - Pair("Quả bơ nhỏ", "qua-bo-nho"), - Pair("cây ô nhỏ", "cay-o-nho"), - Pair("THUYLINH", "thuylinh"), - Pair("Ichigo Ichie", "ichigo-ichie"), - Pair("KR Team", "kr-team"), - Pair("Lạc Mỹ Giới", "Lac-my-gioi"), - Pair("Thiên Trường Địa Cửu", "thien-truong-dia-cuu"), - Pair("Trúc Tửu Quán", "truc-tuu-quan"), - Pair("Quầy Truyện Của Ariel", "quay-truyen-cua-ariel"), - Pair("Klee Translation", "klee-translation"), - Pair("Ổ truyện của Tico", "o-truyen-cua-tico"), - Pair("Phong Miên Team", "phong-mien-team"), - Pair("Kẹo Sữa Vị Dâu", "keo-sua-vi-dau"), - Pair("Mê doujinshi", "me-doujinshi"), - Pair("Octo Juse", "octo-juse"), - Pair("Vịt bê đê team", "vit-be-de-team"), - Pair("Ổ truyện của Lyy", "o-truyen-cua-lyy"), - Pair("Ngã Đích Thiên Không", "nga-dich-thien-khong"), - Pair("ruavang050810", "ruavang050810"), - Pair("Tuấn Cục Súc", "tuan-cuc-suc"), - Pair("Biệt Đội Cháo Khuya", "biet-doi-chao-khuya"), - Pair("Tiêu Nhạn Team", "tieu-nhan-team"), - Pair("BGN", "bgndidichyaoi"), - Pair("Ổ bánh Wibủ", "o-banh-wibu"), - Pair("sunmoon", "sunmoon"), - Pair("Mèo Quạo Quọ", "meo-quao-quo"), - Pair("block", "block"), - Pair("Ổ dịch của Jayh", "o-dich-cua-jayh"), - Pair("Mai Thanh Trúc", "mai-thanh-truc"), - Pair("TEQUILA", "tequila"), - Pair("B'rock Team", "brock-team"), - Pair("Christine", "christine"), - Pair("Kokomatcha", "kokomatcha"), - Pair("Embes Team", "embes-team"), - Pair("Melon Translation", "melon-translation"), - Pair("hydrangea Team", "hydrangea-team"), - Pair("New truyen", "new-truyen"), - Pair("Căn Nhà Nhỏ Của Hủ", "can-nha-nho-cua-hu"), - Pair("Solenda Sabie", "3"), - Pair("Đít Nứt Translation", "crusty-cryst"), - Pair("Xưởng Tàu Hủ Của ltbé", "xuong-tau-hu-cua-ltbe"), - Pair("Cá Mực Một Nắng", "ca-muc-mot-nang"), - Pair("HK.Notebook", "hknotebook"), - Pair("Mèo quạo", "meo-quao"), - Pair("Ổ nhỏ đu Buê Đuê", "Onhodubuedue"), - Pair("miniT", "minit"), - Pair("Enigma BL", "enigma-bl"), - Pair("blackcandy", "blackcandy"), - Pair("Đoàn Mai", "doan-mai"), - Pair("aiki mê đam", "aiki-me-dam"), - Pair("Matchass", "matchass"), - Pair("Căn Cứ Của Gấu Và Cánh Cụt", "can-cu-cua-gau-va-canh-cut"), - Pair("Dango999", "dango999"), - Pair("Khu vườn Blueberry", "khu-vuon-blueberry"), - Pair("Ổ Nhỏ Của Hana", "o-nho-cua-hana"), - Pair("Trần Đức Bo", "tran-duc-bo"), - Pair("MelonTranslation", "melontranslation"), - Pair("HỦ béo", "hu-beo"), - Pair("HOA ĐƠN SẮC", "hoa-don-sac"), - Pair("Ngôi Nhà Ấm Áp Của Sóc Nhỏ", "ngoi-nha-am-ap-cua-soc-nho"), - Pair("PaViTu", "pavitu"), - Pair("Bầy Sói Màu Hường", "bay-soi-mau-huong"), - Pair("Hitori Team", "hitori-team"), - Pair("Gà Trắng", "ga-trang"), - Pair("Hoài An", "hoai-an"), - Pair("Vỹ Nguyệt Team", "vy-nguyet-team"), - Pair("thuythuy", "thuythuy"), - Pair("Luxubu Translotor", "luxubu-translotor"), - Pair("Dịch Giả Cute", "dich-gia-cute"), - Pair("Hủ Auto Truất", "hu-auto-truat"), - Pair("Raccon Team", "raccon-team"), - Pair("shirumi-eifwef", "shirumi-eifwef"), - Pair("Dark Circles", "dark-circles"), - Pair("Con Cá thích đọc BL", "con-ca-thich-doc-bl"), - Pair("MiganTeam", "miganteam"), - Pair("Mạn Châu Sa Hoàng", "man-chau-sa-hoang-aloha"), - Pair("Tiểu Hạ", "tieu-ha"), - Pair("Mặt Trời Nhỏ", "mat-troi-nho"), - Pair("Kitty team", "kitty-team"), - Pair("Na Mi", "na-mi"), - Pair("n name", "n-name-hpo"), - Pair("BeBe Team", "bebe-team"), - Pair("Tổ Ấm Của Những Chú Thỏ Đen", "to-am-cua-nhung-chu-tho-den"), - Pair("Ổ truyện ăn tạp của Lười", "o-truyen-an-tap-cua-luoi"), - Pair("Thỏ đu biđia", "tho-du-bidia"), - Pair("z50170937", "z50170937"), - Pair("Broochie", "broochie"), - Pair("Trái bí xanh", "trai-bi-xanh"), - Pair("Bắy một cành hoa", "bay-mot-canh-hoa"), - Pair("Cocain", "cocain"), - Pair("Có Giọt Mưa Xuân", "co-giot-mua-xuan"), - Pair("Cherry Blossom translation team", "cherry-blossom-translation-team"), - Pair("Nemoishere", "nemoishere"), - Pair("Glor Homié", "glor-homie"), - Pair("TKT", "tkt"), - Pair("Youth Station Comics", "youth-station-comics"), - Pair("HDEC-Hạt Dẻ Cười", "hdec-hat-de-cuoi"), - Pair("Kiekie303", "kiekie303"), - Pair("Try House", "try-house"), - Pair("Moonlight romance", "moonlight-romance"), - Pair("Miniontrum", "miniontrum"), - Pair("Nghèo", "ngheo"), - Pair("Thuyền Sơ Quán", "thuyen-so-quan"), - Pair("Đảo đại dương", "dao-dai-duong"), - Pair("Cpnhatuirealroi", "cpnhatuirealroi"), - Pair("Fishy Kari Team", "fishy-kari-team"), - Pair("Đá Bào", "da-bao"), - Pair("Bar Kon Sou", "bar-kon-sou"), - Pair("Their Stories", "their-stories"), - Pair("tran thi my", "tran-thi-my"), - Pair("Sunny UwU", "sunny-uwu"), - Pair("LEO - Thích Ngủ Đông!", "leo-thích-ngủ-dong"), - Pair("Chiếc Dù Tím Nhỏ", "chiec-du-tim-nho"), - Pair("Hắc Nguyệt Team", "hac-nguyet-team"), - Pair("Hoa Đơn Sắc 23", "hoa-don-sac-23"), - Pair("Bàn này hai người ngồi", "ban-nay-hai-nguoi-ngoi"), - Pair("Neil", "neutralneil1412"), - Pair("Ô Nhà Cỏ", "o-nha-co"), - Pair("Chuyến tàu đêm số 2026", "chuyen-tau-dem-so-2026"), - Pair("House Try", "house-try"), - Pair("Minnnng", "minnnng"), - Pair("Black Roses", "black-roses"), - Pair("Black Roses team", "black-roses-team"), - Pair("Double", "double8"), - Pair("Zero Team", "zero-team"), - Pair("Hiraeth team", "hiraeth-team"), - Pair("Truyện nhà làm", "truyen-nha-lam"), - Pair("Trà Đào Và Hạt Chia", "tra-dao-va-hat-chia"), - Pair("Yuki", "yuki"), - Pair("Sky Hàn", "sky-han"), - Pair("heo con", "heo-con"), - Pair("testdes", "testdes"), - Pair("lê p", "le-p"), - Pair("Caly ana", "caly-ana"), - Pair("gue", "gue"), - Pair("Giga Team", "giga-team"), - Pair("Thuy.Thuyiuiu", "thuythuyiuiu"), - Pair("Kiến Mộc", "kien-moc"), - Pair("zoe.21", "zoe21"), - Pair("super bơ", "super-bo"), - Pair("2convitquay", "2convitquay"), - Pair("Hoa bỉ ngạn", "hoa-bi-ngan"), - Pair("6666", "6666"), - Pair("TL&TEAM", "tlteam"), - Pair("MojitoTeam", "mojitoteam"), - Pair("the new life chapter", "the-new-life-chapter"), - Pair("Black Cat", "black-cat"), - Pair("Kkkkkkk", "kkkkkkk"), - Pair("Brown Kangaroo", "brown-kangaroo"), - Pair("Đần đần", "dan-dan"), - Pair("Hương Đình Các", "huong-dinh-cac"), - Pair("LIGHT STAR", "Light-star"), - Pair("Anhize 602", "anhize-602"), - Pair("Nico", "nico"), - Pair("Rii", "Rii02"), - Pair("Ổ Truyện Của Ly", "o-truyen-cua-ly"), - Pair("Vườn nhà Chanh", "vuon-nha-chanh"), - Pair("Hiệu Sách Số 3011 - Doris Team", "hieu-sach-so-3011-doris-team"), - Pair("Thỏ BL Team", "tho-bl-team"), - Pair("Meow cuồng đam mẽo", "meow-cuong-dam-meo"), - Pair( - "công ty trách nhiệm hữu hạn một thành viên", - "cong-ty-trach-nhiem-huu-han-mot-thanh-vien", - ), - Pair("Leviathan Team", "leviathan-team"), - Pair("Hải âu xanh", "hai-au-xanh"), - Pair("Crazy Rabbits - Chuyên Đu BL", "crazy-rabbits-chuyen-du-bl"), - Pair("CCCC-MR", "cccc-mr"), - Pair("Bão - BL Translation", "bao-bl-translation"), - Pair("Eden Translation Group", "eden-translation-group"), - Pair("Tập đoàn Vô Danh", "tap-doan-vo-danh"), - Pair("Động Hồ Ly-Comics", "dong-ho-ly-comics"), - Pair("Snoopy Translation", "snoopy-translation"), - Pair("Sweetie~~", "sweetie"), - Pair("ice", "ice"), - Pair("MNT-Mỹ Nữ Team", "mnt-my-nu-team"), - Pair("Doris tiên sinh", "doris-tien-sinh"), - Pair("Mỹ Nữ Translation Comics", "my-nu-translation-comics"), - Pair( - "Takei Translator : Con sâu Takei mê ngủ", - "takei-translator-con-sau-takei-me-ngu", - ), - Pair("Hồ ly team", "ho-ly-team"), - Pair("Sở Thú Biên Hòa", "so-thu-bien-hoa"), - Pair("Dịch zui zui", "dich-zui-zui"), - Pair("Trường", "truong"), - Pair("LGBt", "lgbt"), - Pair("BanhGauNho", "banhgaunho"), - Pair("Bóng 7 màu", "bong-7-mau"), - Pair("Rainbow Team", "pagerainbow-team"), - Pair("Black Rabbit's House", "black-rabbits-house"), - Pair("Kylie_Nuna", "kylienuna"), - Pair("QTComiczz", "qtcomiczz"), - Pair("Khu mỏ của Ang", "khu-mo-cua-ang"), - Pair("test213", "test213"), - Pair("Polar bear", "polar-bear"), - Pair("Tiểu khoái Lạt", "tieu-khoai-lat"), - Pair("phanh", "phanh"), - Pair("Odesis Team", "odesis-team"), - Pair("Xám Nâu", "xam-nau"), - Pair("Sora", "sora"), - Pair("Cừu Con Ăn Cỏ", "cuuconanco"), - Pair("Ăn Lờ Team - 1", "an-lo-team-1"), - Pair("Hạ Tử Thiên", "ha-tu-thien"), - Pair("Sứa Biển Team", "sua-bien-team"), - Pair("NHÀ KÚP BẾ", "nha-kup-be"), - Pair("Tiệm Bánh Chuối", "tiem-banh-chuoi"), - Pair("Hoamgu", "hoamgu"), - Pair("Chiếc Lười Nghiện BL - CLNBL", "chiec-luoi-nghien-bl-clnbl"), - Pair("Cú Đêm", "cú-dem"), - Pair("Fighting Cats", "fighting-cats"), - Pair("Eunie", "eunie"), - Pair("Bột Năng", "bot-nang"), - Pair("Ổ Nhà Gấu", "o-nha-gau"), - Pair("li12345678", "li12345678"), - Pair("hoàng hôn", "hoang-hon"), - Pair("Mon ham chơi", "mon-ham-choi"), - Pair("Ngọc Haruchiyo", "ngoc-haruchiyo"), - Pair("Vấn Nguyệt Quán", "van-nguyet-quan"), - Pair("Mạn Thiên Tinh", "man-thien-tinh"), - Pair("Tiểu Ngọc team", "tieu-ngoc-team"), - Pair("Simp cấp S+", "simp-cap-s"), - Pair("Vỏ Quýt Nhỏ - 小陈皮", "vo-quyt-nho-小陈皮"), - Pair("MasterShell82", "mastershell82"), - Pair("Neko Team", "neko-team"), - Pair("Cừu Đen", "cuu-den"), - Pair("Tiệm Hoa Cúc Nhỏ", "iconic-group"), - Pair("Wonderland", "wonderland"), - Pair("Jerry", "jerryhll"), - Pair("Hội Người Chăm Chỉ", "hoi-nguoi-cham-chi"), - Pair("Nhà", "QuangTrinh"), - Pair("Menila Cô Đơn", "menila-co-don"), - Pair("ReD Frog Team", "red-frog-team"), - Pair("San Đíc Bự 2", "san-dic-bu-2"), - Pair("All", "Irene"), - Pair("mary", "mary"), - Pair("Lucifer", "lucifer"), - Pair("Ếch Trên Trời Translation Team", "ech-tren-troi-translation-team"), - Pair("Nguyệt", "nguyet"), - Pair("Tu QueQuo", "tu-quequo"), - Pair("ALL", "all"), - Pair("Bestwishes", "bestwishes"), - Pair("Daanie", "daanie"), - Pair("Nhóc Re ngốc", "nhoc-re-ngoc"), - Pair("Summer Translation Team", "summer-translation-team"), - Pair("Bé mực nhỏ", "be-muc-nho"), - Pair("Mih", "mih"), - Pair("Galaxie Team", "galaxie-team"), - Pair("Kudo", "Shinichi"), - Pair("Swalent", "swalent"), - Pair("Thuusu", "thuusu"), - Pair("Hanagaki Alzi", "hanagaki-alzi"), - Pair("Tràn Bảo Thiên", "tran-bao-thien"), - Pair("Duc Do", "duc-do"), - Pair("Ow Daddy_", "ow-daddy"), - Pair("lậu", "lau"), - Pair("AMITY - Những Chiếc Cừu Giang Hồ", "amity-nhung-chiec-cuu-giang-ho"), - Pair("Thiên Trúc Team", "thien-truc-team"), - Pair("Donchin Lười Biếng", "donchin-luoi-bieng"), - Pair("Hana Yori", "hana-yori"), - Pair("Team gấu ở nhờ", "team-gau-o-nho"), - Pair("Blisthebest", "blisthebest"), - Pair("Vịt Đội Mũ Đỏ", "vit-doi-mu-do"), - Pair("Dream Bamboo team", "dream-bamboo-team"), - Pair("Ngôi Nhà Bong Bóng số 2002", "ngoi-nha-bong-bong-so-2002"), - Pair("Trà Ô Long", "tra-o-long"), - Pair("BLUEMOON", "bluemoon"), - Pair("Kimme", "kimme"), - Pair("Yaoi", "yaoi"), - Pair("Hang nhỏ của gấu", "hang-nho-cua-gau"), - Pair("Chuối Team", "chuoi-team"), - Pair("Lâu Đài Tình Ái Team", "lau-dai-tinh-ai-team"), - Pair("Ánh Trăng Translation Group", "anh-trang-translation-group"), - Pair("Pou", "pou"), - Pair("Ánh Trăng Translation", "anh-trang-translation"), - Pair("Lovely.Mb", "lovelymb"), - Pair("Dreams", "dreams"), - Pair("louis ㅎㅎ", "louis-ㅎㅎ"), - Pair("vườn cà chua", "vuon-ca-chua"), - Pair("Vananhthichtien", "vananhthichtien"), - Pair("Vananhthichtienlam", "vananhthichtienlam"), - Pair("Bí mật nhỏ vườn hướng dương", "bi-mat-nho-vuon-huong-duong"), - Pair("Nova", "nova"), - Pair("Dí Deadline Team", "di-deadline-team"), - Pair("Cá Ngừ Thập Cẩm", "ca-ngu-thap-cam"), - Pair("Làm vui nên không có tên đâu", "lam-vui-nen-khong-co-ten-dau"), - Pair("Sao cũng được", "sao-cung-duoc"), - Pair("Vườn Dịt Dàng", "vuon-vit-vang"), - Pair("Tháng 12 của tôi", "thang-12-cua-toi"), - Pair("Laelia", "laelia"), - Pair("ổ thỏ nhỏ team/thỏ nhỏ là hủ", "o-tho-nho-teamtho-nho-la-hu"), - Pair("Một Thành Viên", "mot-thanh-vien"), - Pair("Vergiss1409", "vergiss1409"), - Pair("Strawberry mousse team", "strawberry-mousse-team"), - Pair("Hạ Ma Lục", "ha-ma-luc"), - Pair("Elen ăn chay", "elen-an-chay"), - Pair("Hoamgu21", "hoamgu21"), - Pair("Dreickass team", "Dreickassteam"), - Pair("Xám nâu", "xam-nauu"), - Pair("Petrichor Team", "petrichor-team"), - Pair("Khoai lang nướng/ KLN", "khoai-lang-nuong-kln"), - Pair("Bourbon với thành phần chính là ngô", "bourbon"), - Pair("Không có", "khong-co"), - Pair("Bầu trời xanh trong", "bau-troi-xanh-trong"), - Pair("Không Ngược Đời Không Nể", "khong-nguoc-doi-khong-ne"), - Pair("Hoa thảo đường san hô", "hoa-thao-duong-san-ho"), - Pair("Yellow Duckk", "yellow-duckk"), - Pair("Homage Team", "homage-team"), - Pair("BambooForestLM - Rừng Trúc Nhỏ", "bambooforestlm-rung-truc-nho"), - Pair("cherry109577599", "cherry109577599"), - Pair("D&A-Team", "da-team"), - Pair("Trương Dạ Thần", "truong-da-than"), - Pair("Lamour Translate Team", "lamour-translate-team"), - Pair("MyA", "mya"), - Pair("Cái Mung Biết Đi", "cai-mung-biet-di"), - Pair("Voi Hồng Mê Truyện", "voi-hong-me-truyen"), - Pair("Cầu Vồng Team", "cau-vong-team"), - Pair("IL Team", "IL-Team"), - Pair("minhdaoquangminh", "minhdaoquangminh"), - Pair("MẶT NẠ ẨN", "mat-na-an"), - Pair( - "UN EXUTOIRE - Truyện tranh BL siu cấp ngọt ngào", - "un-exutoire-truyen-tranh-bl-siu-cap-ngot-ngao", - ), - Pair("chunnyyi", "chunnyyi"), - Pair("Ieyama Akari : Con heo lười biếng", "ieyama-akari-con-heo-luoi-bieng"), - Pair("CMJ - Color Mien MS-June Team", "cmj-color-mien-ms-june-team"), - Pair("Chốn Anh Đào", "chon-anh-dao"), - Pair("Alone", "alone"), - Pair("Gạch óng", "gach-ong"), - Pair("Sugar Plum", "sugar-plum"), - Pair("Chiếc Nấm Dậy Từ 4h Sáng", "chiec-nam-day-tu-4h-sang"), - Pair("Mận Vải Team", "man-vai-team"), - Pair("Bắp Cải", "bap-cai"), - Pair("boylove", "boylove"), - Pair("hoa, lá và cỏ~", "hoa-la-va-co"), - Pair("bé rồng", "be-rong"), - Pair("Mr Ewan", "mr-ewan"), - Pair("DL2110", "dl2110"), - Pair("LĐTA- Lâu Đài Tình Ái", "ldta-lau-dai-tinh-ai"), - Pair("Novalunosis Team", "novalunosis-team"), - Pair("Đứa con của qwỷ", "dua-con-cua-qwy"), - Pair("Nhà của na", "nha-cua-na"), - Pair("Chan chan team", "chan-chan-team"), - Pair("Nơi đu BL", "noi-du-bl"), - Pair("Sữa Chuối Sicula", "sua-chuoi-sicula"), - Pair("Team nhà cừu", "team-nha-cuu"), - Pair("Solo Forever", "solo-forever"), - Pair("DEUXX", "deuxx"), - Pair("DHHS", "dhhs-truyen"), - Pair("CÁ TRONG AO", "ca-trong-ao"), - Pair("Paprika", "paprika"), - Pair("Dream's Hope", "dreams-hope"), - Pair("Name", "name"), - Pair("hổ con team", "ho-con-team"), - Pair("Thư đam mê truyện tranh", "thu-dam-me-truyen-tranh"), - Pair("ốc sên bé nhỏ", "oc-sen-be-nho"), - Pair("Thiên Bình Xanh", "thien-binh-xanh"), - Pair("BUYA TEAM", "buya-team"), - Pair("đời ỉa nên dịch truyện một mình", "doi-ia-nen-dich-truyen-mot-minh"), - Pair("ko có", "ko-co"), - Pair("Chiên su", "chien-su"), - Pair("Shion Cafeteria", "shion-cafeteria"), - Pair("Kumi Kumi Eats Apple", "kumi-kumi-eats-apple"), - Pair("Kazuma", "kazuma"), - Pair("NhómĐuBeĐeXuyênVũTrụ", "nhomdubedexuyenvutru"), - Pair("Team gấu con", "team-gau-con"), - Pair("xu cà na", "xu-ca-na"), - Pair("Oshima", "oshima"), - Pair("一屋赞客 Gia đình cầu vồng", "一屋赞客-gia-dinh-cau-vong"), - Pair("Hoa cúc subteam", "hoa-cuc-subteam"), - Pair("Ngủ trong hòm", "ngu-trong-hom"), - Pair("My Sweetheart", "my-sweetheart"), - Pair("TM.Team", "tmteam"), - Pair("Nhà Của Nhím", "nha-cua-nhim"), - Pair("Lạc Võng Không Buồn", "lac-vong-khong-buon"), - Pair("Là tui đây", "itsjustme"), - Pair("Nông Trại Team", "nong-trai-team"), - Pair("Viktor", "Viktor"), - Pair("Tình yêu thương tích", "tinh-yeu-thuong-tich"), - Pair("Wonderwall Translation Team", "wonderwall-translation-team"), - Pair("Cepharin Translation Team", "cepharin-translation-team"), - Pair("Anh", "anh"), - Pair("Hắc Pháp Sư", "hac-phap-su"), - Pair("dichgiacodon", "dichgiacodon"), - Pair("Omeletto Teamwork", "omeletto-teamwork-2"), - Pair("Cá nóc", "ca-noc"), - Pair("Orebite", "orebite"), - Pair("Tĩnh Xứ An Thân", "tinh-xu-an-than"), - Pair("Melinoe BL", "melinoe-bl"), - Pair("Souja Team", "souja-team"), - Pair("Lowe Mopodog", "lowe-mopodog"), - Pair("Thiên Lâm Dương", "thien-lam-duong"), - Pair("Dược Yểu", "duoc-yeu-0807-0708"), - Pair("Tiểu Lam Translator", "Tieu_lam_Translator"), - Pair("sugar team", "sugar-team"), - Pair("Benilasco comics", "benilasco-comics"), - Pair("Lynner", "lynner"), - Pair("Mê manga", "me-manga"), - Pair("N Team", "n-team"), - Pair("Youth Train Team", "youth-train-team"), - Pair("Min Garuko:)", "min-garuko"), - Pair("Tharn", "tharn"), - Pair("PlayerTeam", "playerteam"), - Pair("Doris", "doris"), - Pair("Dưa Leo Cúc", "dua-leo-cuc"), - Pair("Ổ Mèo Hoang 1108", "o-meo-hoang-1108"), - Pair("Ổ sìn Boylove", "o-sin-boylove"), - Pair("Tổ Chức Áo Đen", "to-chuc-ao-den"), - Pair("Tấm Vé Khứ Hồi", "tam-ve-khu-hoi"), - Pair("Cá Chà Bặc Team", "ca-cha-bac-team"), - Pair("ĐU BÊ ĐÊ LÀ ĐỘNG LỰC HỌC CỦA TỚ", "du-be-de-la-dong-luc-hoc-cua-to"), - Pair("XJunD Team", "xjund-team"), - Pair("Alazyone", "alazyone"), - Pair("KuroshitoBL", "kuroshitobl"), - Pair("Awesome Knights Team", "awesome-knights-team"), - Pair("Cacty Cat", "cacty-cat"), - Pair("Nhà Của Mộc", "nha-cua-moc"), - Pair("Ổ Truyện See Tình", "o-truyen-see-tinh"), - Pair("Dream Bamboo team", "dream-bamboo-team1"), - Pair("Lylyi Dã Lang", "lylyi-da-lang"), - Pair("Gương Kia Ngự Ở Trên Giường", "guong-kia-ngu-o-tren-giuong"), - Pair("Manhwa With Kry Min", "manhwa-with-kry-min"), - Pair("Lôi vũ", "loi-vu"), - Pair("Mr. Midnight", "mr-midnight"), - Pair("Sân bay Hai Tám", "airport28"), - Pair("LườiNênDịch", "luoinendich"), - Pair("Koo7mau", "koo7mau"), - Pair("Dịch Tiểu Thuyết & Truyện Tranh", "dich-tieu-thuyet-truyen-tranh"), - Pair("Eric Team", "eric-team"), - Pair("Sunie Team", "sunie-team"), - Pair("Zunzuizeteam", "zunzuizeteam"), - Pair("Cat Vanizi", "cat-vanizi"), - Pair("MINELOVEBL", "minelovebl"), - Pair("Glowing D Translation", "glowing-d-translation"), - Pair("Kumi Kumi Eat Apple", "kumi-kumi-eat-apple"), - Pair("Dreaming Land", "dreaming-land"), - Pair("Truyện Nhà Mêy", "truyen-nha-mey"), - Pair("Thánh Địa Của Hủ", "thanh-dia-cua-hu"), - Pair("Le méchant t'aime", "le-mechant-taime"), - Pair("XJunD t eam", "xjund-t-eam"), - Pair("Con gà thích đu BL", "con-ga-thich-du-bl"), - Pair("Forsythia Team", "forsythia-team"), - Pair("Hoàng thiên", "hoang-thien"), - Pair("Peach cucumber", "peach-cucumber"), - Pair("Himiko Comics", "himiko-comics"), - Pair("daylaju", "daylaju"), - Pair("Team Củ Quýt", "team-cu-quyt"), - Pair("Mê Truyện", "me-truyen"), - Pair("Dreaming Land", "age"), - Pair("Tiểu Quán Bạch Dạ", "tieu-quan-bach-da"), - Pair("LuRabi", "lurabi"), - Pair("Ổ Truyện Của Mèo Lười", "o-truyen-cua-meo-luoi"), - Pair("Ô Dã Team", "o-da-team"), - Pair("No mercy team", "no-mercy-team"), - Pair("Team Lười", "team-luoi"), - Pair("Quả Vải Team", "Quả_vải_team"), - Pair("Eyu thánh thiện", "eyu-thanh-thien"), - Pair("Mấy chú gấu trúc", "may-chu-gau-truc"), - Pair("Hell's Gate", "hells-gate"), - Pair("20s Team", "20s-team"), - Pair("Sweetie Team", "sweetie-team"), - Pair("Luna's Team", "lunas-team"), - Pair("Xoài Mọng Nước", "xoai-mong-nuoc"), - Pair("Arena Of Valor V-trans Fanpage", "arena-of-valor-v-trans-fanpage"), - Pair("Oce Sunny 1507", "oce-sunny-1507"), - Pair("CNKT", "cnkt"), - Pair("White Rabbit", "White-Rabbit"), - Pair("Nora Translation Team", "nora-translation-team"), - Pair("Cat Vanizii", "cat-vanizii"), - Pair("Góc của Mật Đào", "goc-cua-mat-dao"), - Pair("Lovely Grillove&Boylove Team", "lovely-grilloveboylove-team"), - Pair("Silver Snake", "silver_snake"), - Pair( - "zanh siu đẹp trai 12 cá tính và thích đu bê đê", - "zanh-siu-dep-trai-12-ca-tinh-va-thich-du-be-de", - ), - Pair("Động Tà Răm", "dong-ta-ram"), - Pair("Chiếc Kẹo Đường Đáng Eo", "chiec-keo-duong-dang-eo"), - Pair("Xịp Xịp Team", "xip-xip-team"), - Pair("PAB Honey Translation Team", "pab-honey-translation-team"), - Pair("Boo Boo", "boo-boo"), - Pair("Trà Đào Mật Ong", "tra-dao-mat-ong"), - Pair("Manga2G", "manga2g"), - Pair("Ổ của một đứa rãnh rỗi", "o-cua-mot-dua-ranh-roi"), - Pair("Tủ Truyện Nhỏ Của JC", "tu-truyen-nho-cua-jc"), - Pair("Mí Casa traditinal team", "mi-casa-traditinal-team"), - Pair("Mebede", "mebede"), - Pair("Vì Đồng Tiền Mà Bán Mạng", "vi-dong-tien-ma-ban-mang"), - Pair("Bóng bảy màu", "bong-bay-mau"), - Pair("Dear No One", "dear-no-one"), - Pair("Tiêu Trần Lự", "tieu-tran-lu"), - Pair("LLC", "llc"), - Pair("Tình iu có màu j", "tinh-iu-co-mau-j"), - Pair("Ăn mày comics", "an-may-comics"), - Pair("Mèo nhỏ xinh xinh", "meo-nho-xinh-xinh"), - Pair("Tiệm truyện nhà Mía", "tiem-truyen-nha-mia"), - Pair("Con Ắc Wủy Tà Găm =))", "con-ac-wuy-ta-gam"), - Pair("Sweetie Team . Ver 2", "sweetie-team-ver-2"), - Pair("NELLCOMIC", "NELLCOMIC"), - Pair("Pocky Berry", "pocky-berry"), - Pair("I Purple U", "i-purple-u"), - Pair("Ủa?", "ua"), - Pair("Noiry team", "noiry-team"), - Pair("Michael aka Hoàng", "michael-aka-hoang"), - Pair("Nghèo Nàn Team", "ngheo-nan-team"), - Pair("Nhà Của Nhím Nhỏ", "nha-cua-nhim-nho"), - Pair("Con ká tập dịch bê đê", "conkatapdichb3d3_"), - Pair("Meow", "meow"), - Pair("BuBuChaCha", "bubuchacha"), - Pair("Fiore d'erba", "fiore-derba"), - Pair("BuBuChaCha Team", "bubuchacha-team"), - Pair("Death Team", "death-team"), - Pair("Claire Lucinda", "clairelucinda"), - Pair("Black Moon", "black-moon"), - Pair("Claire Lucinda", "claire-lucinda"), - Pair("Banana Đảm Đang", "banana-dam-dang"), - Pair("BER", "ber"), - Pair("Duck team", "duck-team"), - Pair("Nivemeo", "nivemeo"), - Pair("Chi Chi Chành Chành", "chi-chi-chanh-chanh"), - ), - ) - - companion object { - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QMangaUrlActivity.kt b/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QMangaUrlActivity.kt deleted file mode 100644 index e3c6aad801..0000000000 --- a/src/vi/qmanga/src/eu/kanade/tachiyomi/extension/vi/qmanga/QMangaUrlActivity.kt +++ /dev/null @@ -1,33 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.qmanga - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class QMangaUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 0 && pathSegments[0].endsWith(".html")) { - val id = pathSegments[0].removeSuffix(".html") - try { - startActivity( - Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${QManga.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - }, - ) - } catch (e: ActivityNotFoundException) { - Log.e("QMangaUrlActivity", e.toString()) - } - } else { - Log.e("QMangaUrlActivity", "Could not parse URI from intent $intent") - } - finish() - exitProcess(0) - } -} diff --git a/src/vi/truyengihot/AndroidManifest.xml b/src/vi/truyengihot/AndroidManifest.xml index 7436ac50e9..5d1d4d1c90 100644 --- a/src/vi/truyengihot/AndroidManifest.xml +++ b/src/vi/truyengihot/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".vi.truyengihot.TruyenGiHotUrlActivity" android:excludeFromRecents="true" diff --git a/src/vi/truyenqq/AndroidManifest.xml b/src/vi/truyenqq/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/vi/truyenqq/AndroidManifest.xml +++ b/src/vi/truyenqq/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/vi/truyentranh8/AndroidManifest.xml b/src/vi/truyentranh8/AndroidManifest.xml deleted file mode 100644 index aeb21ac92b..0000000000 --- a/src/vi/truyentranh8/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".vi.truyentranh8.TruyenTranh8UrlActivity" - android:excludeFromRecents="true" - android:exported="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="truyentranh86.com" - android:pathPattern="/truyen-tranh/..*" - android:scheme="http" /> - </intent-filter> - </activity> - </application> -</manifest> \ No newline at end of file diff --git a/src/vi/truyentranh8/build.gradle b/src/vi/truyentranh8/build.gradle deleted file mode 100644 index ad2d7c42c6..0000000000 --- a/src/vi/truyentranh8/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Truyện Tranh 8' - pkgNameSuffix = 'vi.truyentranh8' - extClass = '.TruyenTranh8' - extVersionCode = 1 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/truyentranh8/res/mipmap-hdpi/ic_launcher.png b/src/vi/truyentranh8/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d3256b65ef..0000000000 Binary files a/src/vi/truyentranh8/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranh8/res/mipmap-mdpi/ic_launcher.png b/src/vi/truyentranh8/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 80504a3cd0..0000000000 Binary files a/src/vi/truyentranh8/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranh8/res/mipmap-xhdpi/ic_launcher.png b/src/vi/truyentranh8/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d550acddd3..0000000000 Binary files a/src/vi/truyentranh8/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranh8/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/truyentranh8/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6183b98cd1..0000000000 Binary files a/src/vi/truyentranh8/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranh8/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/truyentranh8/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6ee854e8c7..0000000000 Binary files a/src/vi/truyentranh8/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranh8/res/web_hi_res_512.png b/src/vi/truyentranh8/res/web_hi_res_512.png deleted file mode 100644 index 0f11492397..0000000000 Binary files a/src/vi/truyentranh8/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8.kt b/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8.kt deleted file mode 100644 index 042e2cdc83..0000000000 --- a/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8.kt +++ /dev/null @@ -1,404 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.truyentranh8 - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Evaluator -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.TimeUnit - -class TruyenTranh8 : ParsedHttpSource() { - - override val name = "Truyện Tranh 8" - - override val baseUrl = "http://truyentranh86.com" - - override val lang = "vi" - - override val supportsLatest = true - - override val client = network.cloudflareClient.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) - .build() - - override fun headersBuilder() = Headers.Builder() - .add("Referer", "$baseUrl/") - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0") - - private val dateFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - } - - private val floatingNumberRegex = Regex("""([+-]?(?:[0-9]*[.])?[0-9]+)""") - - override fun popularMangaRequest(page: Int) = GET( - baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("search.php") - addQueryParameter("act", "search") - addQueryParameter("sort", "xem") - addQueryParameter("view", "thumb") - addQueryParameter("page", page.toString()) - }.build().toString(), - headers, - ) - - override fun popularMangaNextPageSelector(): String = "div#tblChap p.page a:contains(Cuối)" - - override fun popularMangaSelector(): String = "div#tblChap figure.col" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - setUrlWithoutDomain(element.select("figcaption h3 a").first()!!.attr("href")) - title = element.select("figcaption h3 a").first()!!.text().replace("[TT8] ", "") - thumbnail_url = element.select("img").first()!!.attr("abs:src") - } - - override fun latestUpdatesRequest(page: Int) = GET( - baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("search.php") - addQueryParameter("act", "search") - addQueryParameter("sort", "chap") - addQueryParameter("view", "thumb") - addQueryParameter("page", page.toString()) - }.build().toString(), - headers, - ) - - override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() - - override fun latestUpdatesSelector(): String = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return when { - query.startsWith(PREFIX_ID_SEARCH) -> { - val id = query.removePrefix(PREFIX_ID_SEARCH).trim() - if (id.isEmpty()) { - throw Exception("ID tìm kiếm không hợp lệ.") - } - fetchMangaDetails(SManga.create().apply { url = "/truyen-tranh/$id/" }) - .map { MangasPage(listOf(it), false) } - } - else -> super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET( - baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("search.php") - addQueryParameter("act", "timnangcao") - addQueryParameter("view", "thumb") - addQueryParameter("page", page.toString()) - - if (query.isNotEmpty()) { - addQueryParameter("q", query) - } - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is SortByFilter -> addQueryParameter("sort", filter.toUriPart()) - is SearchTypeFilter -> addQueryParameter("andor", filter.toUriPart()) - is ForFilter -> if (filter.state != 0) { - addQueryParameter("danhcho", filter.toUriPart()) - } - is AgeFilter -> if (filter.state != 0) { - addQueryParameter("DoTuoi", filter.toUriPart()) - } - is StatusFilter -> if (filter.state != 0) { - addQueryParameter("TinhTrang", filter.toUriPart()) - } - is OriginFilter -> if (filter.state != 0) { - addQueryParameter("quocgia", filter.toUriPart()) - } - is ReadingModeFilter -> if (filter.state != 0) { - addQueryParameter("KieuDoc", filter.toUriPart()) - } - is YearFilter -> if (filter.state.isNotEmpty()) { - addQueryParameter("NamPhaHanh", filter.state) - } - is UserFilter -> if (filter.state.isNotEmpty()) { - addQueryParameter("u", filter.state) - } - is AuthorFilter -> if (filter.state.isNotEmpty()) { - addQueryParameter("TacGia", filter.state) - } - is SourceFilter -> if (filter.state.isNotEmpty()) { - addQueryParameter("Nguon", filter.state) - } - is GenreList -> { - addQueryParameter( - "baogom", - filter.state - .filter { it.state == Filter.TriState.STATE_INCLUDE } - .joinToString(",") { it.id }, - ) - addQueryParameter( - "khonggom", - filter.state - .filter { it.state == Filter.TriState.STATE_EXCLUDE } - .joinToString(",") { it.id }, - ) - } - else -> {} - } - } - }.build().toString(), - headers, - ) - - override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() - - override fun searchMangaSelector(): String = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h1.fs-5").first()!!.text().replace("Truyện Tranh ", "") - - author = document.select("span[itemprop=author]").toList() - .filter { it.text().isNotEmpty() } - .joinToString(", ") { it.text() } - - thumbnail_url = document.select("img.thumbnail").first()!!.attr("abs:src") - - genre = document.select("a[itemprop=genre]").toList() - .filter { it.text().isNotEmpty() } - .joinToString(", ") { it.text() } - - status = when (document.select("ul.mangainfo b:contains(Tình Trạng) + a").first()!!.text().trim()) { - "Đang tiến hành" -> SManga.ONGOING - "Đã hoàn thành" -> SManga.COMPLETED - "Tạm ngưng" -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - - val descnode = document.select("div.card-body.border-start.border-info.border-3").first()!! - descnode.select(Evaluator.Tag("br")).prepend("\\n") - - description = if (descnode.select("p").any()) { - descnode.select("p").joinToString("\n") { - it.text().replace("\\n", "\n").replace("\n ", "\n") - }.trim() - } else { - descnode.text().replace("\\n", "\n").replace("\n ", "\n").trim() - } - } - - override fun chapterListSelector() = "ul#ChapList li" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").first()!!.attr("abs:href")) - name = element.text().replace(element.select("time").first()!!.text(), "") - date_upload = runCatching { - dateFormatter.parse(element.select("time").first()!!.attr("datetime"))?.time - }.getOrNull() ?: 0L - - val match = floatingNumberRegex.find(name) - chapter_number = if (name.lowercase().startsWith("vol")) { - match?.groups?.get(2) - } else { - match?.groups?.get(1) - }?.value?.toFloat() ?: -1f - } - - override fun pageListParse(document: Document) = document.select("div.page-chapter") - .mapIndexed { i, elem -> - Page(i, "", elem.select("img").first()!!.attr("abs:src")) - } - - override fun imageUrlParse(document: Document): String = throw Exception("Not used") - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) { - fun toUriPart() = vals[state].second - } - - private class YearFilter : Filter.Text("Năm phát hành") - private class UserFilter : Filter.Text("Đăng bởi thành viên") - private class AuthorFilter : Filter.Text("Tên tác giả") - private class SourceFilter : Filter.Text("Nguồn/Nhóm dịch") - private class SearchTypeFilter : UriPartFilter( - "Kiểu tìm", - arrayOf( - Pair("AND/và", "and"), - Pair("OR/hoặc", "or"), - ), - ) - private class ForFilter : UriPartFilter( - "Dành cho", - arrayOf( - Pair("Bất kì", ""), - Pair("Con gái", "gai"), - Pair("Con trai", "trai"), - Pair("Con nít", "nit"), - ), - ) - private class AgeFilter : UriPartFilter( - "Bất kỳ", - arrayOf( - Pair("Bất kì", ""), - Pair("= 13", "13"), - Pair("= 14", "14"), - Pair("= 15", "15"), - Pair("= 16", "16"), - Pair("= 17", "17"), - Pair("= 18", "18"), - ), - ) - private class StatusFilter : UriPartFilter( - "Tình trạng", - arrayOf( - Pair("Bất kì", ""), - Pair("Đang dịch", "Ongoing"), - Pair("Hoàn thành", "Complete"), - Pair("Tạm ngưng", "Drop"), - ), - ) - private class OriginFilter : UriPartFilter( - "Quốc gia", - arrayOf( - Pair("Bất kì", ""), - Pair("Nhật Bản", "nhat"), - Pair("Trung Quốc", "trung"), - Pair("Hàn Quốc", "han"), - Pair("Việt Nam", "vietnam"), - ), - ) - private class ReadingModeFilter : UriPartFilter( - "Kiểu đọc", - arrayOf( - Pair("Bất kì", ""), - Pair("Chưa xác định", "chưa xác định"), - Pair("Phải qua trái", "xem từ phải qua trái"), - Pair("Trái qua phải", "xem từ trái qua phải"), - ), - ) - private class SortByFilter : UriPartFilter( - "Sắp xếp theo", - arrayOf( - Pair("Chap mới", "chap"), - Pair("Truyện mới", "truyen"), - Pair("Xem nhiều", "xem"), - Pair("Theo ABC", "ten"), - Pair("Số Chương", "sochap"), - ), - 2, - ) - open class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) - override fun getFilterList() = FilterList( - GenreList(getGenreList()), - SortByFilter(), - SearchTypeFilter(), - ForFilter(), - AgeFilter(), - StatusFilter(), - OriginFilter(), - ReadingModeFilter(), - YearFilter(), - UserFilter(), - AuthorFilter(), - SourceFilter(), - ) - - private fun getGenreList() = listOf( - Genre("Phát Hành Tại TT8", "106"), - Genre("Truyện Màu", "113"), - Genre("Webtoons", "112"), - Genre("Manga - Truyện Nhật", "141"), - Genre("Action - Hành động", "52"), - Genre("Adult - Người lớn", "53"), - Genre("Adventure - Phiêu lưu", "65"), - Genre("Anime", "107"), - Genre("Biseinen", "123"), - Genre("Bishounen", "122"), - Genre("Comedy - Hài hước", "50"), - Genre("Doujinshi", "72"), - Genre("Drama", "73"), - Genre("Ecchi", "74"), - Genre("Fantasy", "75"), - Genre("Gender Bender - Đổi giới tính", "76"), - Genre("Harem", "77"), - Genre("Historical - Lịch sử", "78"), - Genre("Horror - Kinh dị", "79"), - Genre("Isekai - Xuyên không", "139"), - Genre("Josei", "80"), - Genre("Live-action - Live Action", "81"), - Genre("Macgic", "138"), - Genre("Magic - Phép thuật", "116"), - Genre("Martial Arts - Martial-Arts", "84"), - Genre("Mature - Trưởng thành", "85"), - Genre("Mecha - Robot", "86"), - Genre("Mystery - Bí ẩn", "87"), - Genre("One-shot", "88"), - Genre("Psychological - Tâm lý", "89"), - Genre("Romance - Tình cảm", "90"), - Genre("School Life - Học đường", "91"), - Genre("Sci fi - Khoa học viễn tưởng", "92"), - Genre("Seinen", "93"), - Genre("Shoujo", "94"), - Genre("Shoujo Ai", "66"), - Genre("Shounen", "96"), - Genre("Shounen Ai", "97"), - Genre("Slash", "121"), - Genre("Slice-of-Life - Đời sống", "98"), - Genre("Smut", "99"), - Genre("Soft Yaoi - Soft-Yaoi", "100"), - Genre("Sports - Thể thao", "101"), - Genre("Supernatural - Siêu nhiên", "102"), - Genre("Tạp chí truyện tranh", "103"), - Genre("Tragedy - Bi kịch", "104"), - Genre("Trap - Crossdressing", "115"), - Genre("Yaoi", "114"), - Genre("Yaoi Hardcore", "120"), - Genre("Yuri", "111"), - Genre("Manhua - Truyện Trung", "82"), - Genre("Bách Hợp", "128"), - Genre("Chuyển sinh", "134"), - Genre("Cổ đại", "135"), - Genre("Cung đình", "144"), - Genre("Giới giải trí", "146"), - Genre("Hậu cung", "145"), - Genre("Huyền Huyễn", "132"), - Genre("Khoa Huyễn", "130"), - Genre("Lịch Sử", "131"), - Genre("Ngôn tình", "127"), - Genre("Ngọt sủng", "148"), - Genre("Ngược", "143"), - Genre("Người đóng góp", "147"), - Genre("Nữ Cường", "136"), - Genre("Tổng tài", "137"), - Genre("Trọng Sinh", "126"), - Genre("Trường học", "142"), - Genre("Tu chân - tu tiên", "140"), - Genre("Võng Du", "125"), - Genre("Xuyên không", "124"), - Genre("Đam Mỹ", "108"), - Genre("Đô thị", "129"), - Genre("Manhwa - Truyện Hàn", "83"), - Genre("Boy love", "133"), - Genre("Thriller - Giết người, sát nhân, máu me", "149"), - Genre("Truyện Tranh Việt", "51"), - Genre("Cướp bồ - NTR, Netorare", "118"), - Genre("Hướng dẫn vẽ!", "109"), - Genre("Truyện scan", "105"), - Genre("Comic - truyện Âu Mĩ", "71"), - ) - - companion object { - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8UrlActivity.kt b/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8UrlActivity.kt deleted file mode 100644 index e620212f7a..0000000000 --- a/src/vi/truyentranh8/src/eu/kanade/tachiyomi/extension/vi/truyentranh8/TruyenTranh8UrlActivity.kt +++ /dev/null @@ -1,36 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.truyentranh8 - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class TruyenTranh8UrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - - try { - startActivity( - Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${TruyenTranh8.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - }, - ) - } catch (e: ActivityNotFoundException) { - Log.e("TruyenTranh8UrlActivity", e.toString()) - } - } else { - Log.e("TruyenTranh8UrlActivity", "Could not parse URL from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/vi/yurineko/AndroidManifest.xml b/src/vi/yurineko/AndroidManifest.xml index 2c0409d963..37590f6ca4 100644 --- a/src/vi/yurineko/AndroidManifest.xml +++ b/src/vi/yurineko/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".vi.yurineko.YuriNekoUrlActivity" diff --git a/src/zh/baimangu/AndroidManifest.xml b/src/zh/baimangu/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/baimangu/AndroidManifest.xml +++ b/src/zh/baimangu/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/baozimanhua/AndroidManifest.xml b/src/zh/baozimanhua/AndroidManifest.xml index d4f0e92296..e838905602 100644 --- a/src/zh/baozimanhua/AndroidManifest.xml +++ b/src/zh/baozimanhua/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".zh.baozimanhua.BaoziUrlActivity" @@ -29,4 +28,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/zh/baozimanhua/CHANGELOG.md b/src/zh/baozimanhua/CHANGELOG.md index 7d0a406cac..75714349fc 100644 --- a/src/zh/baozimanhua/CHANGELOG.md +++ b/src/zh/baozimanhua/CHANGELOG.md @@ -1,3 +1,18 @@ +## 1.3.22 (2023-08-09) + +- 设置里新增一些镜像网址 +- 解析章节时确保镜像网址生效 + +## 1.3.21 (2023-08-08) + +- 设置里新增一些镜像网址 +- 修复部分情况下无法解析章节的问题 +- 修复所有图片无法加载的问题 + +## 1.3.20 (2023-07-29) + +- 修复需要验证时图片无法加载的问题 + ## 1.3.19 (2023-04-03) - 设置章节日期时不再错误地检查应用版本 diff --git a/src/zh/baozimanhua/build.gradle b/src/zh/baozimanhua/build.gradle index ebf6dbcb74..313485175c 100644 --- a/src/zh/baozimanhua/build.gradle +++ b/src/zh/baozimanhua/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Baozi Manhua' pkgNameSuffix = 'zh.baozimanhua' extClass = '.Baozi' - extVersionCode = 19 + extVersionCode = 22 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozi.kt b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozi.kt index ac27cb5d8e..3c3ca6a766 100644 --- a/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozi.kt +++ b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozi.kt @@ -37,7 +37,15 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { private val preferences: SharedPreferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - override val baseUrl = "https://${preferences.getString(MIRROR_PREF, MIRRORS[0])}" + private val domain: String = run { + val mirrors = MIRRORS + val domain = preferences.getString(MIRROR_PREF, null) ?: return@run mirrors[0] + if (domain in mirrors) return@run domain + preferences.edit().remove(MIRROR_PREF).apply() + mirrors[0] + } + + override val baseUrl = "https://$domain" override val lang = "zh" @@ -51,10 +59,11 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { .rateLimit(2) .addInterceptor(bannerInterceptor) .addNetworkInterceptor(MissingImageInterceptor) + .addNetworkInterceptor(RedirectDomainInterceptor(domain)) .build() override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") + .add("Referer", "https://$domain/") override fun chapterListSelector() = throw UnsupportedOperationException("Not used.") @@ -135,26 +144,31 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { } override fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.fromCallable { - val pageNumberSelector = Evaluator.Class("comic-text__amp") - val pageList = ArrayList<Page>(0) - var url = baseUrl + chapter.url - var i = 0 + val pathToUrl = LinkedHashMap<String, String>() + var request = GET(baseUrl + chapter.url, headers).newBuilder() + .tag(RedirectDomainInterceptor.Tag::class, RedirectDomainInterceptor.Tag()).build() while (true) { - val document = client.newCall(GET(url, headers)).execute().asJsoup() - document.select(".comic-contain amp-img").dropWhile { element -> - element.selectFirst(pageNumberSelector)!!.text().substringBefore('/').toInt() <= i - }.mapTo(pageList) { element -> - Page(i++, imageUrl = element.attr("src")) + val document = client.newCall(request).execute().asJsoup() + for (element in document.select(".comic-contain amp-img")) { + val imageUrl = element.attr("data-src") + val path = imageUrl.substring(imageUrl.indexOf('/', startIndex = 8)) // Skip "https://" + pathToUrl[path] = imageUrl } - url = document.selectFirst(Evaluator.Id("next-chapter")) + val url = document.selectFirst(Evaluator.Id("next-chapter")) ?.takeIf { val text = it.text() text == "下一页" || text == "下一頁" } ?.attr("href") ?: break + request = GET(url, headers) } - pageList + pathToUrl.values.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + } + + override fun imageRequest(page: Page): Request { + val url = page.imageUrl!!.replace(".baozicdn.com", ".baozimh.com") + return GET(url, headers) } override fun pageListParse(document: Document) = throw UnsupportedOperationException("Not used.") @@ -189,7 +203,7 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { // impossible to search a manga and use the filters return if (query.isNotEmpty()) { - val baseUrl = baseUrl.replace("webmota.com", "baozimh.com") + val baseUrl = baseUrl.replace(".dinnerku.com", ".baozimh.com") val url = baseUrl.toHttpUrl().newBuilder() .addEncodedPathSegment("search") .addQueryParameter("q", query) @@ -222,14 +236,15 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { override fun setupPreferenceScreen(screen: PreferenceScreen) { ListPreference(screen.context).apply { + val mirrors = MIRRORS + key = MIRROR_PREF title = "使用镜像网址" - entries = MIRRORS - entryValues = MIRRORS + entries = mirrors + entryValues = mirrors summary = "已选择:%s\n" + - "重启生效,切换简繁体后需要迁移才能刷新漫画标题。\n" + - "搜索漫画时自动使用 baozimh.com 域名以避免出错。" - setDefaultValue(MIRRORS[0]) + "重启生效,切换简繁体后需要迁移才能刷新漫画标题。" + setDefaultValue(mirrors[0]) }.let { screen.addPreference(it) } ListPreference(screen.context).apply { @@ -251,8 +266,7 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { summary = "已选择:%s\n" + "部分作品的章节顺序错误,最新章节总是显示为一个旧章节,导致检查更新时新章节被错标为已读。" + "开启后,将会正确判断新章节和已读情况,但是错误的章节顺序不会改变。" + - "如果作品有章节标号重复,开启或关闭后第一次刷新会导致它们的阅读状态同步。" + - "开启或关闭强力模式后第一次刷新会将所有未标号的章节标记为未读。" + "警告:修改此设置后第一次刷新可能会导致已读状态出现错乱,请谨慎使用。" entries = arrayOf("关闭", "开启 (对有标号的章节有效)", "强力模式 (对所有章节有效)") entryValues = arrayOf(CHAPTER_ORDER_DISABLED, CHAPTER_ORDER_ENABLED, CHAPTER_ORDER_AGGRESSIVE) setDefaultValue(CHAPTER_ORDER_DISABLED) @@ -263,13 +277,22 @@ class Baozi : ParsedHttpSource(), ConfigurableSource { const val ID_SEARCH_PREFIX = "id:" private const val MIRROR_PREF = "MIRROR" - private val MIRRORS = arrayOf( + private val MIRRORS get() = arrayOf( "cn.baozimh.com", - "cn.webmota.com", "tw.baozimh.com", - "tw.webmota.com", "www.baozimh.com", + "cn.webmota.com", + "tw.webmota.com", "www.webmota.com", + "cn.kukuc.co", + "tw.kukuc.co", + "www.kukuc.co", + "cn.czmanga.com", + "tw.czmanga.com", + "www.czmanga.com", + "cn.dinnerku.com", + "tw.dinnerku.com", + "www.dinnerku.com", ) private const val DEFAULT_LEVEL = BaoziBanner.NORMAL.toString() diff --git a/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/RedirectDomainInterceptor.kt b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/RedirectDomainInterceptor.kt new file mode 100644 index 0000000000..c39564b4df --- /dev/null +++ b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/RedirectDomainInterceptor.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.zh.baozimanhua + +import okhttp3.Interceptor +import okhttp3.Response + +class RedirectDomainInterceptor(private val domain: String) : Interceptor { + + class Tag + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + if (!response.isRedirect || request.tag(Tag::class) == null) return response + + val location = response.header("Location")!! + val newLocation = request.url.resolve(location)!!.newBuilder().host(domain).build() + return response.newBuilder().header("Location", newLocation.toString()).build() + } +} diff --git a/src/zh/baozimhorg/AndroidManifest.xml b/src/zh/baozimhorg/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/baozimhorg/AndroidManifest.xml +++ b/src/zh/baozimhorg/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/bh3/AndroidManifest.xml b/src/zh/bh3/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/bh3/AndroidManifest.xml +++ b/src/zh/bh3/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/boylove/AndroidManifest.xml b/src/zh/boylove/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/boylove/AndroidManifest.xml +++ b/src/zh/boylove/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/comicabc/AndroidManifest.xml b/src/zh/comicabc/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/comicabc/AndroidManifest.xml +++ b/src/zh/comicabc/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/dmzj/AndroidManifest.xml b/src/zh/dmzj/AndroidManifest.xml index 7121a9eb6d..dfea2cf99a 100644 --- a/src/zh/dmzj/AndroidManifest.xml +++ b/src/zh/dmzj/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/zh/happymh/AndroidManifest.xml b/src/zh/happymh/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/happymh/AndroidManifest.xml +++ b/src/zh/happymh/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/iqiyi/AndroidManifest.xml b/src/zh/iqiyi/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/iqiyi/AndroidManifest.xml +++ b/src/zh/iqiyi/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/jinmantiantang/AndroidManifest.xml b/src/zh/jinmantiantang/AndroidManifest.xml index 1d597445ea..7a74de2235 100644 --- a/src/zh/jinmantiantang/AndroidManifest.xml +++ b/src/zh/jinmantiantang/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity @@ -33,4 +32,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/zh/jinmantiantang/build.gradle b/src/zh/jinmantiantang/build.gradle index 69c77936df..58d14f38f0 100644 --- a/src/zh/jinmantiantang/build.gradle +++ b/src/zh/jinmantiantang/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Jinman Tiantang' pkgNameSuffix = 'zh.jinmantiantang' extClass = '.Jinmantiantang' - extVersionCode = 37 + extVersionCode = 40 isNsfw = true } diff --git a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt index 5cfab811cf..f187d0a6ac 100644 --- a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt +++ b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt @@ -48,7 +48,7 @@ class Jinmantiantang : ParsedHttpSource(), ConfigurableSource { preferences.getString(MAINSITE_RATELIMIT_PREF, MAINSITE_RATELIMIT_PREF_DEFAULT)!!.toInt(), preferences.getString(MAINSITE_RATELIMIT_PERIOD, MAINSITE_RATELIMIT_PERIOD_DEFAULT)!!.toLong(), ) - .addInterceptor(updateUrlInterceptor) + .apply { interceptors().add(0, updateUrlInterceptor) } .addInterceptor(ScrambledImageInterceptor).build() // 点击量排序(人气) diff --git a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/JinmantiantangPreferences.kt b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/JinmantiantangPreferences.kt index 7c17a1aab8..d981225361 100644 --- a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/JinmantiantangPreferences.kt +++ b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/JinmantiantangPreferences.kt @@ -93,7 +93,7 @@ private val SITE_ENTRIES_ARRAY get() = arrayOf( "jmcomic1.me", ) -private const val DEFAULT_LIST = "jm-comic2.org,jm-comic3.org,jm-comic1.org" +private const val DEFAULT_LIST = "jm-comic3.art,jm-comic1.art,jm-comic2.ark" private const val DEFAULT_LIST_PREF = "defaultBaseUrlList" private const val URL_LIST_PREF = "baseUrlList" @@ -132,12 +132,12 @@ class UpdateUrlInterceptor(private val preferences: SharedPreferences) : Interce response.close() Result.success(response) } catch (e: Throwable) { - if (chain.call().isCanceled()) throw e + if (chain.call().isCanceled() || e.message?.contains("Cloudflare") == true) throw e Result.failure(e) } if (isUpdated || updateUrl(chain)) { - throw IOException("镜像网址已自动更新,请在插件设置中选择合适的镜像网址并重启应用") + throw IOException("镜像网址已自动更新,请在插件设置中选择合适的镜像网址并重启应用(如果反复提示,可能是服务器故障)") } return failedResponse.getOrThrow() } diff --git a/src/zh/kuaikanmanhua/AndroidManifest.xml b/src/zh/kuaikanmanhua/AndroidManifest.xml index 66ea74250d..c88d319665 100644 --- a/src/zh/kuaikanmanhua/AndroidManifest.xml +++ b/src/zh/kuaikanmanhua/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".zh.kuaikanmanhua.KuaikanmanhuaUrlActivity" @@ -27,4 +26,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/zh/mangabz/AndroidManifest.xml b/src/zh/mangabz/AndroidManifest.xml index d4f898a9ea..3e4f471f91 100644 --- a/src/zh/mangabz/AndroidManifest.xml +++ b/src/zh/mangabz/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/zh/manhuadb/AndroidManifest.xml b/src/zh/manhuadb/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/manhuadb/AndroidManifest.xml +++ b/src/zh/manhuadb/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/manhuagui/AndroidManifest.xml b/src/zh/manhuagui/AndroidManifest.xml index 1fb6b00657..5724a8a1b0 100644 --- a/src/zh/manhuagui/AndroidManifest.xml +++ b/src/zh/manhuagui/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity diff --git a/src/zh/manhuagui/build.gradle b/src/zh/manhuagui/build.gradle index fe81edcc42..e94c073bce 100644 --- a/src/zh/manhuagui/build.gradle +++ b/src/zh/manhuagui/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'ManHuaGui' pkgNameSuffix = 'zh.manhuagui' extClass = '.Manhuagui' - extVersionCode = 16 + extVersionCode = 17 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt index 2432b822e9..b988ae0510 100644 --- a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt +++ b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt @@ -43,14 +43,15 @@ import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale -class Manhuagui : ConfigurableSource, ParsedHttpSource() { +class Manhuagui( + override val name: String = "漫画柜", + override val lang: String = "zh", +) : ConfigurableSource, ParsedHttpSource() { private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) } - override val name = "漫画柜" - private val baseHost = if (preferences.getBoolean(USE_MIRROR_URL_PREF, false)) { "mhgui.com" } else { @@ -63,7 +64,6 @@ class Manhuagui : ConfigurableSource, ParsedHttpSource() { } else { "https://www.$baseHost" } - override val lang = "zh" override val supportsLatest = true private val imageServer = arrayOf("https://i.hamreus.com", "https://cf.hamreus.com") diff --git a/src/zh/manhuaren/AndroidManifest.xml b/src/zh/manhuaren/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/manhuaren/AndroidManifest.xml +++ b/src/zh/manhuaren/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/manhuaren/build.gradle b/src/zh/manhuaren/build.gradle index 118de011f6..5a8474ca23 100644 --- a/src/zh/manhuaren/build.gradle +++ b/src/zh/manhuaren/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Manhuaren' pkgNameSuffix = 'zh.manhuaren' extClass = '.Manhuaren' - extVersionCode = 11 + extVersionCode = 14 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt b/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt index c78ef819a9..38aa8df491 100644 --- a/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt +++ b/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.zh.manhuaren import android.text.format.DateFormat +import android.util.Base64 +import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -12,18 +14,26 @@ import okhttp3.CacheControl import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import okio.Buffer import org.json.JSONArray import org.json.JSONObject import java.net.URLEncoder +import java.security.KeyFactory import java.security.MessageDigest +import java.security.spec.X509EncodedKeySpec import java.text.SimpleDateFormat -import java.util.ArrayList import java.util.Date import java.util.Locale import java.util.UUID import java.util.concurrent.TimeUnit.MINUTES +import javax.crypto.Cipher +import kotlin.random.Random +import kotlin.random.nextUBytes class Manhuaren : HttpSource() { override val lang = "zh" @@ -34,45 +44,170 @@ class Manhuaren : HttpSource() { private val pageSize = 20 private val baseHttpUrl = baseUrl.toHttpUrlOrNull()!! - private val c = "4e0a48e1c0b54041bce9c8f0e036124d" - private val cacheControl: CacheControl by lazy { CacheControl.Builder().maxAge(10, MINUTES).build() } - private val userId = (100000000..4294967295).random().toString() + private val gsnSalt = "4e0a48e1c0b54041bce9c8f0e036124d" + private val encodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFCg289dTws27v8GtqIffkP4zgFR+MYIuUIeVO5AGiBV0rfpRh5gg7i8RrT12E9j6XwKoe3xJz1khDnPc65P5f7CJcNJ9A8bj7Al5K4jYGxz+4Q+n0YzSllXPit/Vz/iW5jFdlP6CTIgUVwvIoGEL2sS4cqqqSpCDKHSeiXh9CtMsktc6YyrSN+8mQbBvoSSew18r/vC07iQiaYkClcs7jIPq9tuilL//2uR9kWn5jsp8zHKVjmXuLtHDhM9lObZGCVJwdlN2KDKTh276u/pzQ1s5u8z/ARtK26N8e5w8mNlGcHcHfwyhjfEQurvrnkqYH37+12U3jGk5YNHGyOPcwIDAQAB" + private val imei: String by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { generateIMEI() } + private val token: String by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { fetchToken() } + private var userId = "-1" + private var lastUsedTime = "" + + private fun randomNumber(length: Int): String { + var str = "" + for (i in 1..length) { + str += (0..9).random().toString() + } + return str + } + + private fun addLuhnCheckDigit(str: String): String { + var sum = 0 + str.toCharArray().forEachIndexed { i, it -> + var v = Character.getNumericValue(it) + sum += if (i % 2 == 0) { + v + } else { + v *= 2 + if (v < 10) { + v + } else { + v - 9 + } + } + } + var checkDigit = sum % 10 + if (checkDigit != 0) { + checkDigit = 10 - checkDigit + } + + return "$str$checkDigit" + } + + private fun generateIMEI(): String { + return addLuhnCheckDigit(randomNumber(14)) + } + + private fun generateSimSerialNumber(): String { + return addLuhnCheckDigit("891253${randomNumber(12)}") + } + + private fun fetchToken(): String { + val res = client.newCall(getAnonyUser()).execute() + val body = JSONObject(res.body.string()) + val response = body.getJSONObject("response") + val tokenResult = response.getJSONObject("tokenResult") + val scheme = tokenResult.getString("scheme") + val parameter = tokenResult.getString("parameter") + + userId = response.getString("userId") + lastUsedTime = generateLastUsedTime() + return "$scheme $parameter" + } + + private fun generateLastUsedTime(): String { + return ((Date().time / 1000) * 1000).toString() + } + + private fun encrypt(message: String): String { + val x509EncodedKeySpec = X509EncodedKeySpec(Base64.decode(encodedPublicKey, Base64.DEFAULT)) + val publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec) + val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") + cipher.init(Cipher.ENCRYPT_MODE, publicKey) + + return Base64.encodeToString(cipher.doFinal(message.toByteArray()), Base64.NO_WRAP) + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun getAnonyUser(): Request { + val url = baseHttpUrl.newBuilder() + .addPathSegments("v1/user/createAnonyUser2") + .build() + + val simSerialNumber = generateSimSerialNumber() + val mac = Random.nextUBytes(6) + .joinToString(":") { it.toString(16).padStart(2, '0') } + val androidId = Random.nextUBytes(8) + .joinToString("") { it.toString(16).padStart(2, '0') } + .replaceFirst("^0+".toRegex(), "") + .uppercase() + + val keysMap = ArrayList<HashMap<String, Any?>>().apply { + add( + HashMap<String, Any?>().apply { + put("key", encrypt(androidId)) // https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID + put("keyType", "2") + }, + ) + add( + HashMap<String, Any?>().apply { + put("key", encrypt(UUID.randomUUID().toString())) + put("keyType", "-1") + }, + ) + } + val bodyMap = HashMap<String, Any?>().apply { + put("keys", keysMap) + } + + return myPost( + url, + JSONObject(bodyMap).toString() + .replaceFirst("^/+".toRegex(), "") + .toRequestBody("application/json".toMediaTypeOrNull()), + ) + } + + private fun addGsnHash(request: Request): Request { + val isPost = request.method == "POST" + + val params = request.url.queryParameterNames.toMutableSet() + val bodyBuffer = Buffer() + if (isPost) { + params.add("body") + request.body?.writeTo(bodyBuffer) + } - private fun generateGSNHash(url: HttpUrl): String { - var s = c + "GET" - url.queryParameterNames.toSortedSet().forEach { + var str = gsnSalt + request.method + params.toSortedSet().forEach { if (it != "gsn") { - s += it - s += urlEncode(url.queryParameterValues(it)[0]) + val value = if (isPost && it == "body") bodyBuffer.readUtf8() else request.url.queryParameter(it) + str += "$it${urlEncode(value)}" } } - s += c - return hashString("MD5", s) + str += gsnSalt + + val gsn = hashString("MD5", str) + val newUrl = request.url.newBuilder() + .addQueryParameter("gsn", gsn) + .build() + + return request.newBuilder() + .url(newUrl) + .build() } - private fun myGet(url: HttpUrl): Request { + private fun myRequest(url: HttpUrl, method: String, body: RequestBody?): Request { val now = DateFormat.format("yyyy-MM-dd+HH:mm:ss", Date()).toString() - val realUrl = url.newBuilder() + val newUrl = url.newBuilder() .setQueryParameter("gsm", "md5") .setQueryParameter("gft", "json") .setQueryParameter("gak", "android_manhuaren2") .setQueryParameter("gat", "") .setQueryParameter("gui", userId) - .setQueryParameter("gts", now) // timestamp yyyy-MM-dd+HH:mm:ss + .setQueryParameter("gts", now) .setQueryParameter("gut", "0") // user type .setQueryParameter("gem", "1") - .setQueryParameter("gaui", "1") + .setQueryParameter("gaui", userId) .setQueryParameter("gln", "") // location .setQueryParameter("gcy", "US") // country .setQueryParameter("gle", "zh") // language - .setQueryParameter("gcl", "dm5") // umeng channel + .setQueryParameter("gcl", "dm5") // Umeng channel .setQueryParameter("gos", "1") // OS (int) - .setQueryParameter("gov", "22_5.1.1") // "{Build.VERSION.SDK_INT}_{Build.VERSION.RELEASE}" + .setQueryParameter("gov", "33_13") // "{Build.VERSION.SDK_INT}_{Build.VERSION.RELEASE}" .setQueryParameter("gav", "7.0.1") // app version - .setQueryParameter("gdi", "358240051111110") // device info - .setQueryParameter("gfcl", "dm5") // umeng channel config - .setQueryParameter("gfut", "1688140800000") // first used time - .setQueryParameter("glut", "1688140800000") // last used time + .setQueryParameter("gdi", imei) + .setQueryParameter("gfcl", "dm5") // Umeng channel config + .setQueryParameter("gfut", lastUsedTime) // first used time + .setQueryParameter("glut", lastUsedTime) // last used time .setQueryParameter("gpt", "com.mhr.mangamini") // package name .setQueryParameter("gciso", "us") // https://developer.android.com/reference/android/telephony/TelephonyManager#getSimCountryIso() .setQueryParameter("glot", "") // longitude @@ -87,11 +222,28 @@ class Manhuaren : HttpSource() { .setQueryParameter("glcn", "") // country name .setQueryParameter("glcc", "") // country code .setQueryParameter("gflcc", "") // first location country code + .build() + + return addGsnHash( + Request.Builder() + .method(method, body) + .url(newUrl) + .headers(headers) + .build(), + ) + } - return Request.Builder() - .url(realUrl.setQueryParameter("gsn", generateGSNHash(realUrl.build())).build()) - .headers(headers) - .cacheControl(cacheControl) + private fun myPost(url: HttpUrl, body: RequestBody?): Request { + return myRequest(url, "POST", body).newBuilder() + .cacheControl(CacheControl.Builder().noCache().noStore().build()) + .build() + } + + private fun myGet(url: HttpUrl): Request { + val authorization = token + return myRequest(url, "GET", null).newBuilder() + .addHeader("Authorization", authorization) + .cacheControl(CacheControl.Builder().maxAge(10, MINUTES).build()) .build() } @@ -100,27 +252,27 @@ class Manhuaren : HttpSource() { put("at", -1) put("av", "7.0.1") // app version put("ciso", "us") // https://developer.android.com/reference/android/telephony/TelephonyManager#getSimCountryIso() - put("cl", "dm5") // umeng channel + put("cl", "dm5") // Umeng channel put("cy", "US") // country - put("di", "358240051111110") // device info - put("dm", "Android SDK built for x86") // Build.MODEL - put("fcl", "dm5") // umeng channel config + put("di", imei) + put("dm", "Pixel 6 Pro") // https://developer.android.com/reference/android/os/Build#MODEL + put("fcl", "dm5") // Umeng channel config put("ft", "mhr") // from type - put("fut", "1688140800000") // first used time + put("fut", lastUsedTime) // first used time put("installation", "dm5") put("le", "zh") // language put("ln", "") // location - put("lut", "1688140800000") // last used time - put("nt", 4) + put("lut", lastUsedTime) // last used time + put("nt", 3) put("os", 1) // OS (int) - put("ov", "22_5.1.1") // "{Build.VERSION.SDK_INT}_{Build.VERSION.RELEASE}" + put("ov", "33_13") // "{Build.VERSION.SDK_INT}_{Build.VERSION.RELEASE}" put("pt", "com.mhr.mangamini") // package name - put("rn", "1440x2952") // screen "{width}x{height}" + put("rn", "1400x3120") // screen "{width}x{height}" put("st", 0) } val yqppMap = HashMap<String, Any?>().apply { put("ciso", "us") // https://developer.android.com/reference/android/telephony/TelephonyManager#getSimCountryIso() - put("laut", "0") // is allow location (0 or 1) + put("laut", "0") // is allow location ("0" or "1") put("lot", "") // longitude put("lat", "") // latitude put("cut", "GMT+8") // time zone @@ -140,7 +292,7 @@ class Manhuaren : HttpSource() { add("yq_is_anonymous", "1") add("x-request-id", UUID.randomUUID().toString()) add("X-Yq-Yqpp", JSONObject(yqppMap).toString()) - add("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; Android SDK built for x86 Build/LMY48X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36") + add("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 13; Pixel 6 Pro Build/TQ3A.230705.001)") } } @@ -161,7 +313,7 @@ class Manhuaren : HttpSource() { } private fun urlEncode(str: String?): String { - return URLEncoder.encode(str, "UTF-8") + return URLEncoder.encode(str ?: "", "UTF-8") .replace("+", "%20") .replace("%7E", "~") .replace("*", "%2A") @@ -202,7 +354,7 @@ class Manhuaren : HttpSource() { .addQueryParameter("start", (pageSize * (page - 1)).toString()) .addQueryParameter("limit", pageSize.toString()) .addQueryParameter("sort", "0") - .addPathSegments("/v2/manga/getCategoryMangas") + .addPathSegments("v2/manga/getCategoryMangas") .build() return myGet(url) } @@ -214,7 +366,7 @@ class Manhuaren : HttpSource() { .addQueryParameter("start", (pageSize * (page - 1)).toString()) .addQueryParameter("limit", pageSize.toString()) .addQueryParameter("sort", "1") - .addPathSegments("/v2/manga/getCategoryMangas") + .addPathSegments("v2/manga/getCategoryMangas") .build() return myGet(url) } @@ -233,30 +385,33 @@ class Manhuaren : HttpSource() { .addQueryParameter("limit", pageSize.toString()) if (query != "") { url = url.addQueryParameter("keywords", query) - .addPathSegments("/v1/search/getSearchManga") - return myGet(url.build()) - } - filters.forEach { filter -> - when (filter) { - is SortFilter -> url = url.setQueryParameter("sort", filter.getId()) - is CategoryFilter -> { - url = url.setQueryParameter("subCategoryId", filter.getId()) - .setQueryParameter("subCategoryType", filter.getType()) + .addPathSegments("v1/search/getSearchManga") + } else { + filters.forEach { filter -> + when (filter) { + is SortFilter -> { + url = url.setQueryParameter("sort", filter.getId()) + } + is CategoryFilter -> { + url = url.setQueryParameter("subCategoryId", filter.getId()) + .setQueryParameter("subCategoryType", filter.getType()) + } + else -> {} } - else -> {} } + url = url.addPathSegments("v2/manga/getCategoryMangas") } - url = url.addPathSegments("/v2/manga/getCategoryMangas") return myGet(url.build()) } override fun searchMangaParse(response: Response): MangasPage { val res = response.body.string() val obj = JSONObject(res).getJSONObject("response") - if (obj.has("result")) { - return mangasFromJSONArray(obj.getJSONArray("result")) - } - return mangasFromJSONArray(obj.getJSONArray("mangas")) + return mangasFromJSONArray( + obj.getJSONArray( + if (obj.has("result")) "result" else "mangas", + ), + ) } override fun mangaDetailsParse(response: Response) = SManga.create().apply { @@ -359,6 +514,14 @@ class Manhuaren : HttpSource() { override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") + override fun imageRequest(page: Page): Request { + val newHeaders = headersBuilder() + .set("Referer", "http://www.dm5.com/dm5api/") + .build() + + return GET(page.imageUrl!!, newHeaders) + } + override fun getFilterList() = FilterList( SortFilter( "状态", diff --git a/src/zh/manwa/AndroidManifest.xml b/src/zh/manwa/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/manwa/AndroidManifest.xml +++ b/src/zh/manwa/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/manwa/build.gradle b/src/zh/manwa/build.gradle index 97fcbfb328..f83184cba2 100644 --- a/src/zh/manwa/build.gradle +++ b/src/zh/manwa/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Manwa' pkgNameSuffix = 'zh.manwa' extClass = '.Manwa' - extVersionCode = 2 + extVersionCode = 4 isNsfw = true } diff --git a/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt index c88511d185..ad2d11d8ea 100644 --- a/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt +++ b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.zh.manwa import android.app.Application import android.content.SharedPreferences import android.net.Uri +import androidx.preference.CheckBoxPreference import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.GET @@ -19,6 +20,8 @@ import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Cookie +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -27,6 +30,7 @@ import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -61,12 +65,14 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { originalResponse } } + override val client: OkHttpClient = network.client.newBuilder() .addNetworkInterceptor(rewriteOctetStream) .build() - // Popular + private val baseHttpUrl = baseUrl.toHttpUrlOrNull() + // Popular override fun popularMangaRequest(page: Int) = GET("$baseUrl/rank", headers) override fun popularMangaNextPageSelector(): String? = null override fun popularMangaSelector(): String = "#rankList_2 > a" @@ -77,7 +83,6 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { } // Latest - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/getUpdate?page=${page * 15 - 15}&date=", headers) override fun latestUpdatesParse(response: Response): MangasPage { // Get image host @@ -99,6 +104,7 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { val totalPage = jsonObject["total"]!!.jsonPrimitive.int return MangasPage(mangas, totalPage > currentPage + 15) } + override fun latestUpdatesNextPageSelector() = throw Exception("Not used") override fun latestUpdatesSelector() = throw Exception("Not used") override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used") @@ -138,18 +144,36 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { url = element.attr("href") name = element.text() } + override fun chapterListParse(response: Response): List<SChapter> { return super.chapterListParse(response).reversed() } - // Pages + override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { + client.newCall(GET("$baseUrl/static/images/pv.gif")).execute() + return super.fetchPageList(chapter) + } + // Pages override fun pageListRequest(chapter: SChapter): Request { return GET("$baseUrl${chapter.url}?img_host=${preferences.getString(IMAGE_HOST_KEY, IMAGE_HOST_ENTRY_VALUES[0])}", headers) } override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("#cp_img > img[data-r-src]").forEachIndexed { index, it -> + val cssQuery = "#cp_img > div.img-content > img[data-r-src]" + val elements = document.select(cssQuery) + if (elements.size == 3) { + val darkReader = document.selectFirst("#cp_img p") + if (darkReader != null) { + if (preferences.getBoolean(AUTO_CLEAR_COOKIE_KEY, false)) { + clearCookies() + } + + throw Exception(darkReader.text()) + } + } + + elements.forEachIndexed { index, it -> add(Page(index, "", it.attr("data-r-src"))) } } @@ -164,11 +188,32 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { entryValues = IMAGE_HOST_ENTRY_VALUES setDefaultValue(IMAGE_HOST_ENTRY_VALUES[0]) }.let { screen.addPreference(it) } + + CheckBoxPreference(screen.context).apply { + key = AUTO_CLEAR_COOKIE_KEY + title = "自动删除 Cookie" + + setDefaultValue(false) + }.let { screen.addPreference(it) } + } + + private fun clearCookies() { + if (baseHttpUrl == null) { + return + } + val cookies = client.cookieJar.loadForRequest(baseHttpUrl) + val obsoletedCookies = cookies.map { + val cookie = Cookie.parse(baseHttpUrl, "${it.name}=; Max-Age=-1")!! + cookie + } + client.cookieJar.saveFromResponse(baseHttpUrl, obsoletedCookies) } companion object { private const val IMAGE_HOST_KEY = "IMG_HOST" private val IMAGE_HOST_ENTRIES = arrayOf("图源1", "图源2", "图源3") private val IMAGE_HOST_ENTRY_VALUES = arrayOf("1", "2", "3") + + private const val AUTO_CLEAR_COOKIE_KEY = "CLEAR_COOKIE" } } diff --git a/src/zh/noyacg/AndroidManifest.xml b/src/zh/noyacg/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/noyacg/AndroidManifest.xml +++ b/src/zh/noyacg/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/picacomic/AndroidManifest.xml b/src/zh/picacomic/AndroidManifest.xml index 55dea899bb..8072ee00db 100644 --- a/src/zh/picacomic/AndroidManifest.xml +++ b/src/zh/picacomic/AndroidManifest.xml @@ -1,3 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension" /> \ No newline at end of file +<manifest /> diff --git a/src/zh/roumanwu/AndroidManifest.xml b/src/zh/roumanwu/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/roumanwu/AndroidManifest.xml +++ b/src/zh/roumanwu/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/roumanwu/build.gradle b/src/zh/roumanwu/build.gradle index 2fe3e0a41f..343a0162a0 100644 --- a/src/zh/roumanwu/build.gradle +++ b/src/zh/roumanwu/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Roumanwu' pkgNameSuffix = 'zh.roumanwu' extClass = '.Roumanwu' - extVersionCode = 4 + extVersionCode = 6 isNsfw = true } diff --git a/src/zh/roumanwu/src/eu/kanade/tachiyomi/extension/zh/roumanwu/Roumanwu.kt b/src/zh/roumanwu/src/eu/kanade/tachiyomi/extension/zh/roumanwu/Roumanwu.kt index 61db16dd34..7581e7f2a8 100644 --- a/src/zh/roumanwu/src/eu/kanade/tachiyomi/extension/zh/roumanwu/Roumanwu.kt +++ b/src/zh/roumanwu/src/eu/kanade/tachiyomi/extension/zh/roumanwu/Roumanwu.kt @@ -116,7 +116,7 @@ class Roumanwu : HttpSource(), ConfigurableSource { private const val MIRROR_PREF_SUMMARY = "使用镜像网址。重启软件生效。" // 地址: https://rou.pub/dizhi - private val MIRRORS = arrayOf("https://rouman5.com", "https://rm01.xyz") + private val MIRRORS = arrayOf("https://rouman5.com", "https://roum2.xyz") private val MIRRORS_DESC = arrayOf("主站", "镜像") private const val MIRROR_DEFAULT = 1.toString() // use mirror diff --git a/src/zh/sixmh/AndroidManifest.xml b/src/zh/sixmh/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/sixmh/AndroidManifest.xml +++ b/src/zh/sixmh/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/sixmh/build.gradle b/src/zh/sixmh/build.gradle index 3514b5dfbf..978f7daa1e 100644 --- a/src/zh/sixmh/build.gradle +++ b/src/zh/sixmh/build.gradle @@ -6,7 +6,7 @@ ext { extName = '6Manhua / Qixi Manhua' pkgNameSuffix = 'zh.sixmh' extClass = '.SixMH' - extVersionCode = 8 + extVersionCode = 9 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/sixmh/src/eu/kanade/tachiyomi/extension/zh/sixmh/SixMH.kt b/src/zh/sixmh/src/eu/kanade/tachiyomi/extension/zh/sixmh/SixMH.kt index 0fb5519399..0207f5259c 100644 --- a/src/zh/sixmh/src/eu/kanade/tachiyomi/extension/zh/sixmh/SixMH.kt +++ b/src/zh/sixmh/src/eu/kanade/tachiyomi/extension/zh/sixmh/SixMH.kt @@ -182,6 +182,7 @@ class SixMH : HttpSource(), ConfigurableSource { override fun pageListParse(response: Response): List<Page> { val result = Unpacker.unpack(response.body.string(), "[", "]") .ifEmpty { return emptyList() } + .replace("\\u0026", "&") .replace("\\", "") .removeSurrounding("\"").split("\",\"") return result.mapIndexed { i, url -> Page(i, imageUrl = url) } @@ -213,7 +214,7 @@ class SixMH : HttpSource(), ConfigurableSource { const val MIRROR_PREF = "MIRROR" /** Note: mirror index affects [chapterListParse] */ - val MIRRORS get() = arrayOf("6mh67.com", "qiximh3.com") + val MIRRORS get() = arrayOf("sixmanhua.com", "qiximh3.com") val MIRROR_NAMES get() = arrayOf("6漫画", "七夕漫画") private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } diff --git a/src/zh/tencentcomics/AndroidManifest.xml b/src/zh/tencentcomics/AndroidManifest.xml index a06be62c15..bb75b7458e 100644 --- a/src/zh/tencentcomics/AndroidManifest.xml +++ b/src/zh/tencentcomics/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <activity android:name=".zh.tencentcomics.TencentComicsUrlActivity" @@ -27,4 +26,4 @@ </intent-filter> </activity> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/src/zh/terrahistoricus/AndroidManifest.xml b/src/zh/terrahistoricus/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/terrahistoricus/AndroidManifest.xml +++ b/src/zh/terrahistoricus/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/vomic/AndroidManifest.xml b/src/zh/vomic/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/vomic/AndroidManifest.xml +++ b/src/zh/vomic/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/vomic/build.gradle b/src/zh/vomic/build.gradle index e52e592d3f..4ba950d954 100644 --- a/src/zh/vomic/build.gradle +++ b/src/zh/vomic/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'vomic' pkgNameSuffix = 'zh.vomic' extClass = '.Vomic' - extVersionCode = 2 + extVersionCode = 4 } apply from: "$rootDir/common.gradle" diff --git a/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt index 240760b363..a1bb24a2d1 100644 --- a/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt +++ b/src/zh/vomic/src/eu/kanade/tachiyomi/extension/zh/vomic/Vomic.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.zh.vomic import android.app.Application import android.util.Base64 -import androidx.preference.ListPreference +import androidx.preference.EditTextPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.ConfigurableSource @@ -21,6 +21,7 @@ import okhttp3.Response import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale import javax.crypto.Cipher @@ -40,13 +41,30 @@ class Vomic : HttpSource(), ConfigurableSource { private val apiUrl: String init { - val mirrors = MIRRORS - val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - .getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1) - baseUrl = "http://" + mirrors[mirrorIndex] - apiUrl = "http://" + mirrors[mirrorIndex].replace("www.", "api.") + val domain = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000).getString(DOMAIN_PREF, DEFAULT_DOMAIN)!! + if (domain.startsWith("www.") || domain.startsWith("api.")) { + val tld = domain.substring(4) + baseUrl = "http://www.$tld" + apiUrl = "http://api.$tld" + } else { + val url = "http://$domain" + baseUrl = url + apiUrl = url + } } + override val client = network.client.newBuilder().addInterceptor { chain -> + try { + val response = chain.proceed(chain.request()) + if (response.isSuccessful) { + return@addInterceptor response + } + response.close() + } catch (_: Throwable) { + } + throw IOException("请在插件设置中修改网址") + }.build() + override fun headersBuilder() = Headers.Builder().add("User-Agent", System.getProperty("http.agent")!!) override fun popularMangaRequest(page: Int) = GET("$apiUrl/api/v1/rank/rank-data?rank_id=1&page=$page", headers) @@ -150,14 +168,11 @@ class Vomic : HttpSource(), ConfigurableSource { } override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - val mirrors = MIRRORS - key = MIRROR_PREF - title = "镜像网址" - summary = "%s\n重启生效" - entries = mirrors - entryValues = Array(mirrors.size) { it.toString() } - setDefaultValue("0") + EditTextPreference(screen.context).apply { + key = DOMAIN_PREF + title = "网址" + summary = "不带 http:// 前缀,重启生效\n备选网址:$DEFAULT_DOMAIN 或 119.23.243.52" + setDefaultValue(DEFAULT_DOMAIN) }.let(screen::addPreference) } @@ -167,8 +182,8 @@ class Vomic : HttpSource(), ConfigurableSource { json.decodeFromString<ResponseDto<T>>(body.string()).data companion object { - private const val MIRROR_PREF = "MIRROR" - private val MIRRORS get() = arrayOf("www.vomicmh.com", "www.iewoai.com") + private const val DOMAIN_PREF = "DOMAIN" + private const val DEFAULT_DOMAIN = "www.vomicmh.com" private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.ENGLISH) } } diff --git a/src/zh/wnacg/AndroidManifest.xml b/src/zh/wnacg/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/wnacg/AndroidManifest.xml +++ b/src/zh/wnacg/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/yidan/AndroidManifest.xml b/src/zh/yidan/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/yidan/AndroidManifest.xml +++ b/src/zh/yidan/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest /> diff --git a/src/zh/yidan/build.gradle b/src/zh/yidan/build.gradle index f4b77e6992..34440c22d0 100644 --- a/src/zh/yidan/build.gradle +++ b/src/zh/yidan/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Yidan Girl' pkgNameSuffix = 'zh.yidan' extClass = '.Yidan' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Dto.kt b/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Dto.kt index 73c90434e7..ad7a944648 100644 --- a/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Dto.kt +++ b/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Dto.kt @@ -15,7 +15,7 @@ class MangaDto( private val coverPic: String?, private val id: Int, ) { - fun toSManga() = SManga.create().apply { + fun toSManga(baseUrl: String) = SManga.create().apply { url = id.toString() title = this@MangaDto.title author = this@MangaDto.author @@ -29,7 +29,7 @@ class MangaDto( "5" in mhcate.split(",") -> SManga.COMPLETED else -> SManga.ONGOING } - thumbnail_url = coverPic + thumbnail_url = if (coverPic?.startsWith("http") == true) coverPic else baseUrl + coverPic initialized = true } } diff --git a/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Yidan.kt b/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Yidan.kt index 085fdb7895..65b3e5c73a 100644 --- a/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Yidan.kt +++ b/src/zh/yidan/src/eu/kanade/tachiyomi/extension/zh/yidan/Yidan.kt @@ -47,7 +47,7 @@ class Yidan : HttpSource(), ConfigurableSource { override fun popularMangaParse(response: Response): MangasPage { val listing: ListingDto = response.parseAs() - val mangas = listing.list.map { it.toSManga() } + val mangas = listing.list.map { it.toSManga(baseUrl) } val hasNextPage = run { val url = response.request.url val pageSize = url.queryParameter("pageSize")!!.toInt() @@ -84,7 +84,7 @@ class Yidan : HttpSource(), ConfigurableSource { } override fun mangaDetailsParse(response: Response) = - response.parseAs<MangaDto>().toSManga() + response.parseAs<MangaDto>().toSManga(baseUrl) override fun chapterListRequest(manga: SManga) = GET("$baseUrl/prod-api/app-api/vv/mh-episodes/list?mhid=${manga.url}", headers) @@ -132,6 +132,6 @@ class Yidan : HttpSource(), ConfigurableSource { companion object { private const val MIRROR_PREF = "MIRROR" - private val MIRRORS get() = arrayOf("ydan.cc", "ydan.vip", "dans.cc") + private val MIRRORS get() = arrayOf("ydan.cc", "yidan.one", "yidan.in", "yidan.info") } } diff --git a/src/zh/zerobyw/AndroidManifest.xml b/src/zh/zerobyw/AndroidManifest.xml index 30deb7f797..8072ee00db 100644 --- a/src/zh/zerobyw/AndroidManifest.xml +++ b/src/zh/zerobyw/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> +<manifest />