├── .idea
├── .name
├── .gitignore
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
├── compiler.xml
├── kotlinc.xml
├── migrations.xml
├── gradle.xml
└── jarRepositories.xml
├── AnimeScrapCommon
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── talent
│ │ │ └── animescrap_common
│ │ │ ├── model
│ │ │ ├── SimpleAnime.kt
│ │ │ ├── UpdateDetails.kt
│ │ │ ├── AnimeDetails.kt
│ │ │ ├── AnimeStreamLink.kt
│ │ │ └── AnimePlayingDetails.kt
│ │ │ ├── source
│ │ │ └── AnimeSource.kt
│ │ │ ├── sourceutils
│ │ │ ├── AndroidCookieJar.kt
│ │ │ ├── WebViewClientCompat.kt
│ │ │ ├── DDosGuardIntreceptor.kt
│ │ │ ├── WebViewInterceptor.kt
│ │ │ └── CloudflareInterceptor.kt
│ │ │ └── utils
│ │ │ └── Utils.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── talent
│ │ │ └── animescrap_common
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── talent
│ │ └── animescrap_common
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── animeSources
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── talent
│ │ │ └── animescrapsources
│ │ │ ├── SourceSelector.kt
│ │ │ └── animesources
│ │ │ ├── sourceCommonExtractors
│ │ │ └── AsianExtractor.kt
│ │ │ ├── AsianLoad.kt
│ │ │ ├── MyAsianTvSource.kt
│ │ │ ├── KissKhSource.kt
│ │ │ └── KawaiifuSource.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── talent
│ │ │ └── animescrapsources
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── talent
│ │ └── animescrapsources
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── cc.png
│ │ │ ├── bottom_nav_drawable.xml
│ │ │ ├── shadow_gradient.xml
│ │ │ ├── select_latest.xml
│ │ │ ├── select_fav.xml
│ │ │ ├── icon_triangle.xml
│ │ │ ├── subs_toggle_image_selector.xml
│ │ │ ├── gradient_shape.xml
│ │ │ ├── ic_baseline_navigate_before_24.xml
│ │ │ ├── ic_baseline_navigate_next_24.xml
│ │ │ ├── ic_baseline_play_arrow_24.xml
│ │ │ ├── ic_baseline_pause_24.xml
│ │ │ ├── ic_baseline_fast_forward_24.xml
│ │ │ ├── ic_trending_black_24.xml
│ │ │ ├── ic_baseline_fast_rewind_24.xml
│ │ │ ├── ic_baseline_arrow_back_24.xml
│ │ │ ├── ic_baseline_height_24.xml
│ │ │ ├── ic_baseline_fullscreen_24.xml
│ │ │ ├── ic_favorite_black_24.xml
│ │ │ ├── ic_broken_image.xml
│ │ │ ├── ic_play.xml
│ │ │ ├── background_selector.xml
│ │ │ ├── background_selector_accent.xml
│ │ │ ├── ic_search_black_24.xml
│ │ │ ├── ic_baseline_zoom_out_map_24.xml
│ │ │ ├── ic_outline_latest_black_24.xml
│ │ │ ├── ic_baseline_lock_open_24.xml
│ │ │ ├── ic_latest_black_24.xml
│ │ │ ├── ic_baseline_closed_caption_24.xml
│ │ │ ├── ic_baseline_favorite_border_24.xml
│ │ │ ├── ic_baseline_closed_caption_disabled_24.xml
│ │ │ ├── ic_baseline_screen_rotation_24.xml
│ │ │ ├── sort_numeric_reversed.xml
│ │ │ ├── sort_numeric_normal.xml
│ │ │ ├── ic_baseline_settings_24.xml
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ ├── ic_heart_minus.xml
│ │ │ ├── ic_episode.xml
│ │ │ └── ic_heart_plus.xml
│ │ ├── font
│ │ │ └── zcool_font.ttf
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_banner.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── values
│ │ │ ├── ic_banner_background.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── arrays.xml
│ │ │ ├── style_full_screen.xml
│ │ │ ├── themes.xml
│ │ │ ├── colors.xml
│ │ │ └── strings.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_banner.xml
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── anim
│ │ │ ├── fade_out.xml
│ │ │ ├── expand_in.xml
│ │ │ └── pop_in.xml
│ │ ├── values-v29
│ │ │ └── themes.xml
│ │ ├── layout
│ │ │ ├── bottom_sheet_layout.xml
│ │ │ ├── spinner_dropdown_item.xml
│ │ │ ├── activity_splash.xml
│ │ │ ├── fragment_favorite.xml
│ │ │ ├── fragment_trending.xml
│ │ │ ├── fragment_latest.xml
│ │ │ ├── fragment_search.xml
│ │ │ ├── fragment_player.xml
│ │ │ ├── episode_bottom_sheet_layout.xml
│ │ │ ├── double_tap_overlay.xml
│ │ │ ├── landscape_cover_cardview_item.xml
│ │ │ ├── portrait_cover_cardview_item.xml
│ │ │ └── activity_main.xml
│ │ ├── menu
│ │ │ ├── topbar_menu.xml
│ │ │ ├── bottom_nav_menu.xml
│ │ │ └── rail_nav_menu.xml
│ │ ├── layout-land
│ │ │ └── portrait_cover_cardview_item.xml
│ │ ├── xml
│ │ │ └── root_preferences.xml
│ │ └── navigation
│ │ │ └── mobile_navigation.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── java
│ │ └── com
│ │ │ └── talent
│ │ │ └── animescrap
│ │ │ ├── AnimeScrapApplication.kt
│ │ │ ├── room
│ │ │ ├── LinksRoomDatabase.kt
│ │ │ ├── FavRoomModel.kt
│ │ │ └── LinkDao.kt
│ │ │ ├── di
│ │ │ ├── RepositoryModule.kt
│ │ │ ├── VideoPlayerModule.kt
│ │ │ └── DatabaseModule.kt
│ │ │ ├── ui
│ │ │ ├── viewmodels
│ │ │ │ ├── SearchViewModel.kt
│ │ │ │ ├── FavoriteViewModel.kt
│ │ │ │ ├── LatestViewModel.kt
│ │ │ │ ├── TrendingViewModel.kt
│ │ │ │ ├── AnimeStreamViewModel.kt
│ │ │ │ ├── UpdateViewModel.kt
│ │ │ │ └── AnimeDetailsViewModel.kt
│ │ │ ├── activities
│ │ │ │ └── SplashActivity.kt
│ │ │ └── fragments
│ │ │ │ ├── TrendingFragment.kt
│ │ │ │ ├── SearchFragment.kt
│ │ │ │ ├── FavoriteFragment.kt
│ │ │ │ └── LatestFragment.kt
│ │ │ ├── viewbindings
│ │ │ └── Bindings.kt
│ │ │ └── widgets
│ │ │ └── DoubleTapPlayerView.kt
│ │ └── AndroidManifest.xml
├── schemas
│ └── com.talent.animescrap.room.LinksRoomDatabase
│ │ ├── 1.json
│ │ ├── 2.json
│ │ └── 3.json
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── LICENSE
├── gradle.properties
├── .github
└── workflows
│ └── android.yml
└── gradlew.bat
/.idea/.name:
--------------------------------------------------------------------------------
1 | Anime Scrap
--------------------------------------------------------------------------------
/AnimeScrapCommon/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/animeSources/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/animeSources/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /debug
3 | /release
4 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/drawable/cc.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/font/zcool_font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/font/zcool_font.ttf
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xhdpi/ic_banner.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5C5050
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fakeyatogod/AnimeScrap/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/animeSources/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_banner_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F44545
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F44545
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/AnimeScrapApplication.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class AnimeScrapApplication : Application()
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/model/SimpleAnime.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.model
2 |
3 | data class SimpleAnime(
4 | val animeName: String,
5 | val animeImageURL: String,
6 | val animeLink: String
7 | )
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/model/UpdateDetails.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.model
2 |
3 | data class UpdateDetails(
4 | val isUpdateAvailable : Boolean,
5 | val link : String,
6 | val description : String
7 | )
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 27 14:44:22 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/model/AnimeDetails.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.model
2 |
3 | data class AnimeDetails(
4 | val animeName: String,
5 | val animeDesc: String,
6 | val animeCover: String,
7 | val animeEpisodes: Map>
8 | )
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/model/AnimeStreamLink.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.model
2 |
3 | data class AnimeStreamLink(
4 | val link: String,
5 | val subsLink: String,
6 | val isHls: Boolean,
7 | val extraHeaders: HashMap? = null
8 | )
9 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bottom_nav_drawable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/room/LinksRoomDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.room
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 |
6 | @Database(entities = [FavRoomModel::class], version = 3)
7 | abstract class LinksRoomDatabase : RoomDatabase() {
8 | abstract fun linkDao(): LinkDao
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shadow_gradient.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/select_latest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/select_fav.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_triangle.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | /.idea/deploymentTargetDropDown.xml
17 | /.idea/misc.xml
18 | .idea/misc.xml
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/subs_toggle_image_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_navigate_before_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_navigate_next_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v29/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_pause_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Anime Scrap"
16 | include ':app'
17 | include ':AnimeScrapCommon'
18 | include ':animeSources'
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_trending_black_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/animeSources/src/test/java/com/talent/animescrapsources/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/expand_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_fast_rewind_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/test/java/com/talent/animescrap_common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_height_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/spinner_dropdown_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_fullscreen_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/room/FavRoomModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.room
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "fav_table")
8 | data class FavRoomModel(
9 | @PrimaryKey @ColumnInfo(name = "favLink") val linkString: String,
10 | @ColumnInfo(name = "favPic") val picLinkString: String,
11 | @ColumnInfo(name = "favName") val nameString: String,
12 | @ColumnInfo(name = "favSource") val sourceString: String?
13 | )
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/model/AnimePlayingDetails.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.model
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.Keep
5 | import kotlinx.parcelize.Parcelize
6 |
7 | @Keep
8 | @Parcelize
9 | data class AnimePlayingDetails(
10 | val animeName: String,
11 | val animeUrl: String,
12 | var animeEpisodeIndex: String,
13 | val animeEpisodeMap: HashMap,
14 | val animeTotalEpisode: String,
15 | val epType: String
16 | ) : Parcelable
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite_black_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_broken_image.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_selector_accent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.di
2 |
3 | import com.talent.animescrap.repo.AnimeRepository
4 | import com.talent.animescrap.repo.AnimeRepositoryImpl
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import kotlinx.coroutines.ExperimentalCoroutinesApi
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | @ExperimentalCoroutinesApi
14 | abstract class RepositoryModule {
15 |
16 | @Binds
17 | abstract fun bindAnimeRepository(repository: AnimeRepositoryImpl): AnimeRepository
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_black_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_zoom_out_map_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/topbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_outline_latest_black_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_lock_open_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_latest_black_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_closed_caption_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_favorite_border_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/source/AnimeSource.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.source
2 |
3 | import com.talent.animescrap_common.model.AnimeDetails
4 | import com.talent.animescrap_common.model.AnimeStreamLink
5 | import com.talent.animescrap_common.model.SimpleAnime
6 |
7 | interface AnimeSource {
8 | suspend fun animeDetails(contentLink: String): AnimeDetails
9 | suspend fun searchAnime(searchedText: String): ArrayList
10 | suspend fun latestAnime(): ArrayList
11 | suspend fun trendingAnime(): ArrayList
12 | suspend fun streamLink(
13 | animeUrl: String,
14 | animeEpCode: String,
15 | extras: List? = null
16 | ): AnimeStreamLink
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/di/VideoPlayerModule.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.di
2 |
3 | import android.app.Application
4 | import com.google.android.exoplayer2.ExoPlayer
5 | import com.google.android.exoplayer2.source.MediaSource
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.components.ViewModelComponent
10 | import dagger.hilt.android.scopes.ViewModelScoped
11 |
12 | @Module
13 | @InstallIn(ViewModelComponent::class)
14 | object VideoPlayerModule {
15 |
16 | @Provides
17 | @ViewModelScoped
18 | fun provideVideoPlayer(app: Application): ExoPlayer {
19 | return ExoPlayer.Builder(app)
20 | .setSeekForwardIncrementMs(10000)
21 | .setSeekBackIncrementMs(10000)
22 | .build()
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_closed_caption_disabled_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/animeSources/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/AnimeScrapCommon/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/room/LinkDao.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.room
2 |
3 | import androidx.room.*
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | @Dao
7 | interface LinkDao {
8 | @Query("SELECT * FROM fav_table where favSource = :sourceName")
9 | fun getLinks(sourceName: String? = "yugen"): Flow>
10 |
11 | @Insert(onConflict = OnConflictStrategy.IGNORE)
12 | fun insert(fav: FavRoomModel)
13 |
14 | @Delete
15 | fun deleteOne(fav: FavRoomModel)
16 |
17 | @Query("SELECT EXISTS (SELECT * FROM fav_table where favLink = :link AND favSource = :sourceName)")
18 | fun isItFav(link: String, sourceName: String): Boolean
19 |
20 | @Query("SELECT * FROM fav_table where favLink = :link AND favSource = :sourceName")
21 | fun getFav(link: String, sourceName: String): FavRoomModel
22 |
23 | }
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/androidTest/java/com/talent/animescrap_common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.talent.AnimeScrapCommon.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/animeSources/src/androidTest/java/com/talent/animescrapsources/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.talent.animescrap_animesources.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_screen_rotation_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/SourceSelector.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources
2 |
3 | import android.content.Context
4 | import com.talent.animescrap_common.source.AnimeSource
5 | import com.talent.animescrapsources.animesources.*
6 |
7 | class SourceSelector(context: Context) {
8 | val sourceMap: Map = mapOf(
9 | "yugen" to YugenSource(),
10 | "allanime" to AllAnimeSource(),
11 | "animepahe" to AnimePaheSource(),
12 | "kawaiifu" to KawaiifuSource(context),
13 | // "aniwave" to AniWaveSource(),
14 | "kiss_kh" to KissKhSource(),
15 | "asian_load" to AsianLoad(),
16 | "my_asian_tv" to MyAsianTvSource(),
17 | )
18 |
19 | fun getSelectedSource(selectedSource: String): AnimeSource {
20 | if (selectedSource in sourceMap.keys) {
21 | return sourceMap[selectedSource]!!
22 | }
23 | return sourceMap["yugen"]!!
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sort_numeric_reversed.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sort_numeric_normal.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/pop_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
21 |
22 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/rail_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/schemas/com.talent.animescrap.room.LinksRoomDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "1481ce57d78c039fc2dc3f35286e9f58",
6 | "entities": [
7 | {
8 | "tableName": "fav_table",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fav` TEXT NOT NULL, PRIMARY KEY(`fav`))",
10 | "fields": [
11 | {
12 | "fieldPath": "linkString",
13 | "columnName": "fav",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | }
17 | ],
18 | "primaryKey": {
19 | "columnNames": [
20 | "fav"
21 | ],
22 | "autoGenerate": false
23 | },
24 | "indices": [],
25 | "foreignKeys": []
26 | }
27 | ],
28 | "views": [],
29 | "setupQueries": [
30 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
31 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1481ce57d78c039fc2dc3f35286e9f58')"
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Yato
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontwarn org.bouncycastle.jsse.BCSSLSocket
24 | -dontwarn org.bouncycastle.jsse.BCSSLParameters
25 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
26 | -dontwarn org.conscrypt.*
27 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters
28 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket
29 | -dontwarn org.openjsse.net.ssl.OpenJSSE
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/SearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.SimpleAnime
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.withContext
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class SearchViewModel @Inject constructor(
17 | private val animeRepository: AnimeRepository
18 | ) : ViewModel() {
19 | private val _searchedAnimeList = MutableLiveData>()
20 | val searchedAnimeList: LiveData> = _searchedAnimeList
21 |
22 | fun searchAnime(searchUrl: String) {
23 | viewModelScope.launch {
24 | withContext(Dispatchers.IO) {
25 | animeRepository.searchAnimeFromSite(searchUrl).apply {
26 | _searchedAnimeList.postValue(this@apply)
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/FavoriteViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.SimpleAnime
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.withContext
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class FavoriteViewModel @Inject constructor(
17 | private val animeRepository: AnimeRepository
18 | ) : ViewModel() {
19 |
20 | private val _favoriteAnimeList = MutableLiveData>().apply {
21 | getFavorites()
22 | }
23 | val favoriteAnimeList: LiveData> = _favoriteAnimeList
24 |
25 | fun getFavorites() {
26 | viewModelScope.launch {
27 | withContext(Dispatchers.IO) {
28 | animeRepository.getFavoritesFromRoom().collect {
29 | _favoriteAnimeList.postValue(it)
30 | }
31 | }
32 | }
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/LatestViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.SimpleAnime
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.withContext
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class LatestViewModel @Inject constructor(
17 | private val animeRepository: AnimeRepository
18 | ) : ViewModel() {
19 | private val _latestAnimeList =
20 | MutableLiveData>().apply { getLatestAnimeList() }
21 | val latestAnimeList: LiveData> = _latestAnimeList
22 |
23 | fun getLatestAnimeList() {
24 | viewModelScope.launch {
25 | withContext(Dispatchers.IO) {
26 | animeRepository.getLatestAnimeFromSite().apply {
27 | _latestAnimeList.postValue(this@apply)
28 | }
29 | }
30 | }
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_settings_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/TrendingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.SimpleAnime
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.withContext
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class TrendingViewModel @Inject constructor(
17 | private val animeRepository: AnimeRepository
18 | ) : ViewModel() {
19 | private val _trendingAnimeList =
20 | MutableLiveData>().apply { getTrendingAnimeList() }
21 | val trendingAnimeList: LiveData> = _trendingAnimeList
22 |
23 |
24 | fun getTrendingAnimeList() {
25 | viewModelScope.launch {
26 | withContext(Dispatchers.IO) {
27 | animeRepository.getTrendingAnimeFromSite().apply {
28 | _trendingAnimeList.postValue(this@apply)
29 | }
30 | }
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Yugen
5 |
6 | - AllAnime
7 |
8 | - KissKh
9 | - AnimePahe
10 | - Kawaiifu
11 | - MarinMoe
12 | - AsianLoad
13 | - MyAsianTv
14 |
15 |
16 |
17 | - yugen
18 |
19 | - allanime
20 |
21 | - kiss_kh
22 | - animepahe
23 | - kawaiifu
24 | - marin_moe
25 | - asian_load
26 | - my_asian_tv
27 |
28 |
29 |
30 |
31 | - Off
32 | - On
33 | - Follow system
34 |
35 |
36 |
37 | - off
38 | - on
39 | - follow_system
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart_minus.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_episode.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
15 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/AnimeStreamViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.AnimeStreamLink
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.withContext
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class AnimeStreamViewModel @Inject constructor(
17 | private val animeRepository: AnimeRepository
18 | ) : ViewModel() {
19 |
20 | private val _animeStreamLink: MutableLiveData = MutableLiveData()
21 | val animeStreamLink: LiveData = _animeStreamLink
22 |
23 | fun setAnimeLink(animeUrl: String, animeEpCode: String, extras: List) {
24 | viewModelScope.launch {
25 | withContext(Dispatchers.IO) {
26 | println("STREAM GET LINK")
27 | animeRepository.getStreamLink(animeUrl, animeEpCode, extras).apply {
28 | _animeStreamLink.postValue(this@apply)
29 | }
30 | }
31 | }
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/di/DatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.di
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import androidx.room.migration.Migration
6 | import androidx.sqlite.db.SupportSQLiteDatabase
7 | import com.talent.animescrap.room.LinkDao
8 | import com.talent.animescrap.room.LinksRoomDatabase
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.components.SingletonComponent
13 | import javax.inject.Singleton
14 |
15 |
16 | @InstallIn(SingletonComponent::class)
17 | @Module
18 | object DatabaseModule {
19 |
20 | private val MIGRATION_2_3 = object : Migration(2, 3) {
21 | override fun migrate(database: SupportSQLiteDatabase) {
22 | database.execSQL("ALTER TABLE fav_table ADD COLUMN favSource TEXT DEFAULT 'yugen'")
23 | }
24 | }
25 |
26 | @Provides
27 | @Singleton
28 | fun provideAppDatabase(
29 | application: Application,
30 | ): LinksRoomDatabase {
31 | return Room
32 | .databaseBuilder(application, LinksRoomDatabase::class.java, "fav-db")
33 | .addMigrations(MIGRATION_2_3)
34 | .fallbackToDestructiveMigration()
35 | .build()
36 | }
37 |
38 | @Provides
39 | @Singleton
40 | fun provideLinkDao(appDatabase: LinksRoomDatabase): LinkDao = appDatabase.linkDao()
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/viewbindings/Bindings.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.viewbindings
2 |
3 | import android.widget.ImageView
4 | import androidx.databinding.BindingAdapter
5 | import coil.ImageLoader
6 | import coil.load
7 | import com.facebook.shimmer.Shimmer
8 | import com.facebook.shimmer.ShimmerDrawable
9 | import com.talent.animescrap.R
10 | import com.talent.animescrap_common.utils.Utils
11 |
12 | private val shimmer =
13 | Shimmer.AlphaHighlightBuilder()// The attributes for a ShimmerDrawable is set by this builder
14 | .setDuration(1200) // how long the shimmering animation takes to do one full sweep
15 | .setBaseAlpha(0.6f) //the alpha of the underlying children
16 | .setHighlightAlpha(0.9f) // the shimmer alpha amount
17 | .setDirection(Shimmer.Direction.LEFT_TO_RIGHT)
18 | .setAutoStart(true)
19 | .build()
20 | /* A function that is called when the `image` attribute is used in the layout. */
21 | @BindingAdapter("image")
22 | fun ImageView.setImage(url: String?) {
23 | val imageLoader = ImageLoader.Builder(context)
24 | .okHttpClient { Utils.httpClient }
25 | .build()
26 | if (!url.isNullOrEmpty())
27 | load(url, imageLoader) {
28 | crossfade(true)
29 | placeholder(ShimmerDrawable().apply {
30 | setShimmer(shimmer)
31 | })
32 | error(R.drawable.ic_broken_image)
33 | build()
34 | }
35 | }
--------------------------------------------------------------------------------
/AnimeScrapCommon/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-parcelize'
5 | }
6 |
7 | android {
8 | namespace 'com.talent.AnimeScrapCommon'
9 | compileSdk 33
10 |
11 | defaultConfig {
12 | minSdk 23
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | kotlinOptions {
29 | jvmTarget = '1.8'
30 | }
31 | }
32 |
33 | dependencies {
34 |
35 | implementation 'androidx.core:core-ktx:1.12.0'
36 | implementation 'androidx.appcompat:appcompat:1.6.1'
37 | implementation 'com.google.android.material:material:1.10.0'
38 |
39 | // Network
40 | implementation 'org.jsoup:jsoup:1.15.2' // Web scraping tool
41 | implementation 'com.google.code.gson:gson:2.9.0' // Json Parser
42 | implementation 'com.squareup.okhttp3:okhttp:4.10.0'
43 |
44 | testImplementation 'junit:junit:4.13.2'
45 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
47 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/app/schemas/com.talent.animescrap.room.LinksRoomDatabase/2.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 2,
5 | "identityHash": "3faf319f6e2d4a27d936dc0c9ea3a5ef",
6 | "entities": [
7 | {
8 | "tableName": "fav_table",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`favLink` TEXT NOT NULL, `favPic` TEXT NOT NULL, `favName` TEXT NOT NULL, PRIMARY KEY(`favLink`))",
10 | "fields": [
11 | {
12 | "fieldPath": "linkString",
13 | "columnName": "favLink",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "picLinkString",
19 | "columnName": "favPic",
20 | "affinity": "TEXT",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "nameString",
25 | "columnName": "favName",
26 | "affinity": "TEXT",
27 | "notNull": true
28 | }
29 | ],
30 | "primaryKey": {
31 | "columnNames": [
32 | "favLink"
33 | ],
34 | "autoGenerate": false
35 | },
36 | "indices": [],
37 | "foreignKeys": []
38 | }
39 | ],
40 | "views": [],
41 | "setupQueries": [
42 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
43 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3faf319f6e2d4a27d936dc0c9ea3a5ef')"
44 | ]
45 | }
46 | }
--------------------------------------------------------------------------------
/animeSources/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.talent.animescrap_animesources'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | minSdk 23
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation project(path: ':AnimeScrapCommon')
35 |
36 |
37 | implementation 'androidx.core:core-ktx:1.12.0'
38 | implementation 'androidx.appcompat:appcompat:1.6.1'
39 | implementation 'com.google.android.material:material:1.10.0'
40 |
41 | // Network
42 | implementation 'org.jsoup:jsoup:1.15.2' // Web scraping tool
43 | implementation 'com.google.code.gson:gson:2.9.0' // Json Parser
44 | implementation 'com.squareup.okhttp3:okhttp:4.10.0'
45 |
46 | testImplementation 'junit:junit:4.13.2'
47 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_heart_plus.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/sourceutils/AndroidCookieJar.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.sourceutils
2 |
3 | import android.webkit.CookieManager
4 | import okhttp3.Cookie
5 | import okhttp3.CookieJar
6 | import okhttp3.HttpUrl
7 |
8 | class AndroidCookieJar : CookieJar {
9 |
10 | private val manager = CookieManager.getInstance()
11 |
12 | override fun saveFromResponse(url: HttpUrl, cookies: List) {
13 | val urlString = url.toString()
14 |
15 | cookies.forEach { manager.setCookie(urlString, it.toString()) }
16 | }
17 |
18 | override fun loadForRequest(url: HttpUrl): List {
19 | return get(url)
20 | }
21 |
22 | fun get(url: HttpUrl): List {
23 | val cookies = manager.getCookie(url.toString())
24 |
25 | return if (cookies != null && cookies.isNotEmpty()) {
26 | cookies.split(";").mapNotNull { Cookie.parse(url, it) }
27 | } else {
28 | emptyList()
29 | }
30 | }
31 |
32 | fun remove(url: HttpUrl, cookieNames: List? = null, maxAge: Int = -1): Int {
33 | val urlString = url.toString()
34 | val cookies = manager.getCookie(urlString) ?: return 0
35 |
36 | fun List.filterNames(): List {
37 | return if (cookieNames != null) {
38 | this.filter { it in cookieNames }
39 | } else {
40 | this
41 | }
42 | }
43 |
44 | return cookies.split(";")
45 | .map { it.substringBefore("=") }
46 | .filterNames()
47 | .onEach { manager.setCookie(urlString, "$it=;Max-Age=$maxAge") }
48 | .count()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/schemas/com.talent.animescrap.room.LinksRoomDatabase/3.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 3,
5 | "identityHash": "131e8c62a69c6d6c2f410f2644d422a0",
6 | "entities": [
7 | {
8 | "tableName": "fav_table",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`favLink` TEXT NOT NULL, `favPic` TEXT NOT NULL, `favName` TEXT NOT NULL, `favSource` TEXT, PRIMARY KEY(`favLink`))",
10 | "fields": [
11 | {
12 | "fieldPath": "linkString",
13 | "columnName": "favLink",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "picLinkString",
19 | "columnName": "favPic",
20 | "affinity": "TEXT",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "nameString",
25 | "columnName": "favName",
26 | "affinity": "TEXT",
27 | "notNull": true
28 | },
29 | {
30 | "fieldPath": "sourceString",
31 | "columnName": "favSource",
32 | "affinity": "TEXT",
33 | "notNull": false
34 | }
35 | ],
36 | "primaryKey": {
37 | "columnNames": [
38 | "favLink"
39 | ],
40 | "autoGenerate": false
41 | },
42 | "indices": [],
43 | "foreignKeys": []
44 | }
45 | ],
46 | "views": [],
47 | "setupQueries": [
48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '131e8c62a69c6d6c2f410f2644d422a0')"
50 | ]
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
11 |
12 |
13 |
14 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ "main" , "common" ]
6 |
7 | jobs:
8 | build:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: set up JDK 17
15 | uses: actions/setup-java@v3
16 | with:
17 | java-version: '17'
18 | distribution: 'temurin'
19 | cache: gradle
20 |
21 | - name: Grant execute permission for gradlew
22 | run: chmod +x gradlew
23 |
24 | - name: Build Release APK
25 | run: ./gradlew assembleRelease
26 |
27 | - uses: r0adkll/sign-android-release@v1
28 | name: Sign app APK
29 | # ID used to access action output
30 | id: sign_app
31 | with:
32 | releaseDirectory: app/build/outputs/apk/release
33 | signingKeyBase64: ${{ secrets.BASE64KEY }}
34 | alias: ${{ secrets.ALIAS }}
35 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
36 | keyPassword: ${{ secrets.KEY_PASSWORD }}
37 | env:
38 | # override default build-tools version (29.0.3) -- optional
39 | BUILD_TOOLS_VERSION: "34.0.0"
40 |
41 | # Example use of `signedReleaseFile` output -- not needed
42 | - uses: actions/upload-artifact@v2
43 | with:
44 | name: Signed app bundle
45 | path: ${{steps.sign_app.outputs.signedReleaseFile}}
46 |
47 | - name: Set current date as env variable
48 | run: echo "date_now=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
49 |
50 | - uses: marvinpinto/action-automatic-releases@latest
51 | with:
52 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
53 | automatic_release_tag: "latest"
54 | draft: true
55 | title: " Dev Release ${{ env.date_now }}"
56 | files: ${{steps.sign_app.outputs.signedReleaseFile}}
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/activities/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.activities
2 |
3 | import android.content.Intent
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import android.os.Looper
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.appcompat.app.AppCompatDelegate
10 | import androidx.preference.PreferenceManager
11 | import com.google.android.material.color.DynamicColors
12 | import com.talent.animescrap.R
13 | import dagger.hilt.android.AndroidEntryPoint
14 |
15 | @AndroidEntryPoint
16 | class SplashActivity : AppCompatActivity() {
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_splash)
20 |
21 | // Set dynamic colors
22 | val settingsPreferenceManager = PreferenceManager.getDefaultSharedPreferences(this)
23 | settingsPreferenceManager.getBoolean("dynamic_colors", true).also {
24 | if (it) DynamicColors.applyToActivitiesIfAvailable(application)
25 | }
26 | settingsPreferenceManager.getString("dark_mode", "follow_system").also {
27 | when (it.toString()) {
28 | "on" -> {
29 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
30 | }
31 | "off" -> {
32 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
33 | }
34 | else -> {
35 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
36 | }
37 | }
38 | }
39 | val intent = Intent(this, MainActivity::class.java)
40 |
41 | if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
42 | startActivity(intent)
43 | finish()
44 | } else {
45 | Handler(Looper.getMainLooper()).postDelayed({
46 | startActivity(intent)
47 | finish()
48 | }, 400)
49 | }
50 |
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_favorite.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
25 |
26 |
27 |
35 |
36 |
40 |
41 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_trending.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
25 |
26 |
27 |
35 |
36 |
40 |
41 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_latest.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
25 |
26 |
27 |
35 |
36 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/UpdateViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap.BuildConfig
8 | import com.talent.animescrap_common.model.UpdateDetails
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.launch
11 | import kotlinx.coroutines.withContext
12 | import org.jsoup.Jsoup
13 |
14 | class UpdateViewModel : ViewModel() {
15 | private val githubReleaseLink = "https://github.com/fakeyatogod/AnimeScrap/releases/latest"
16 | private val githubAPKLink =
17 | "https://github.com/fakeyatogod/AnimeScrap/releases/download/TAG/AnimeScrap-vTAG.apk"
18 |
19 | private val _isUpdateAvailable = MutableLiveData().apply {
20 | checkForNewUpdate()
21 | }
22 | val isUpdateAvailable: LiveData = _isUpdateAvailable
23 |
24 | fun checkForNewUpdate() {
25 | val currentVersion = BuildConfig.VERSION_NAME
26 | viewModelScope.launch {
27 | withContext(Dispatchers.IO) {
28 | try {
29 | val doc = Jsoup.connect(githubReleaseLink).get()
30 | val updateDesc = try {
31 | doc.select(".markdown-body").text()
32 | } catch (_: Exception) {
33 | "No Update Description found"
34 | }
35 | println("Update desc = $updateDesc")
36 | val latestVersion =
37 | Regex("\\d+\\.\\d+\\.\\d+").find(doc.toString())?.value
38 | println("$currentVersion == $latestVersion = ${latestVersion == currentVersion}")
39 | _isUpdateAvailable.postValue(
40 | UpdateDetails(
41 | latestVersion != currentVersion,
42 | githubAPKLink.replace("TAG", latestVersion ?: currentVersion),
43 | updateDesc
44 | )
45 | )
46 | } catch (_: Exception) {
47 | }
48 | }
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/animesources/sourceCommonExtractors/AsianExtractor.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources.animesources.sourceCommonExtractors
2 |
3 | import android.net.Uri
4 | import android.util.Base64
5 | import com.google.gson.JsonParser
6 | import com.talent.animescrap_common.utils.Utils
7 | import org.jsoup.Jsoup
8 | import javax.crypto.Cipher
9 | import javax.crypto.spec.IvParameterSpec
10 | import javax.crypto.spec.SecretKeySpec
11 |
12 | class AsianExtractor {
13 | private val key = "93422192433952489752342908585752"
14 | private val iv = "9262859232435825"
15 |
16 | public fun getAsianStreamLink(embedLink: String): String {
17 | val id = Uri.parse(embedLink).getQueryParameter("id")!!
18 | val embedDoc = Jsoup.parse(Utils.get(embedLink))
19 | val scriptValue = embedDoc.select("script[data-name='crypto']").attr("data-value")
20 |
21 | val encryptedKey = encryptAES(id, key, iv)
22 | val decryptedToken = decryptAES(scriptValue, key, iv)
23 |
24 | val url =
25 | "https://${Uri.parse(embedLink).host}/encrypt-ajax.php?id=$encryptedKey&alias=$decryptedToken"
26 | println(url)
27 | val data = JsonParser.parseString(Utils.get(url)).asJsonObject["data"].asString
28 | println(data)
29 | return JsonParser.parseString(
30 | decryptAES(
31 | data,
32 | key,
33 | iv
34 | )
35 | ).asJsonObject["source"].asJsonArray.first().asJsonObject["file"].asString
36 | }
37 |
38 | private fun encryptAES(data: String, key: String, iv: String): String {
39 | val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
40 | val secretKey = SecretKeySpec(key.toByteArray(), "AES")
41 | val ivSpec = IvParameterSpec(iv.toByteArray())
42 | cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
43 | val encryptedBytes = cipher.doFinal(data.toByteArray())
44 | return Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
45 | }
46 |
47 | private fun decryptAES(encryptedData: String, key: String, iv: String): String {
48 | val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
49 | val secretKey = SecretKeySpec(key.toByteArray(), "AES")
50 | val ivSpec = IvParameterSpec(iv.toByteArray())
51 | cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
52 | val decryptedBytes = cipher.doFinal(Base64.decode(encryptedData, Base64.DEFAULT))
53 | return String(decryptedBytes)
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
25 |
26 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_player.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
27 |
28 |
33 |
34 |
43 |
44 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style_full_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/viewmodels/AnimeDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.viewmodels
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.talent.animescrap_common.model.AnimeDetails
8 | import com.talent.animescrap.repo.AnimeRepository
9 | import com.talent.animescrap.room.FavRoomModel
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.withContext
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class AnimeDetailsViewModel @Inject constructor(
18 | private val animeRepository: AnimeRepository
19 | ) : ViewModel() {
20 | private val _animeDetails = MutableLiveData()
21 | val animeDetails: LiveData = _animeDetails
22 |
23 | fun getAnimeDetails(contentLink: String) {
24 | viewModelScope.launch {
25 | withContext(Dispatchers.IO) {
26 | animeRepository.getAnimeDetailsFromSite(contentLink).apply {
27 | _animeDetails.postValue(this@apply)
28 | }
29 | }
30 | }
31 | }
32 |
33 |
34 | private val _isAnimeFav = MutableLiveData()
35 | val isAnimeFav: LiveData = _isAnimeFav
36 |
37 | fun checkFavorite(animeLink: String, sourceName: String) {
38 | viewModelScope.launch {
39 | withContext(Dispatchers.IO) {
40 | animeRepository.checkFavoriteFromRoom(animeLink, sourceName).apply {
41 | if (this) _isAnimeFav.postValue(true)
42 | else _isAnimeFav.postValue(false)
43 | }
44 | }
45 | }
46 | }
47 |
48 | fun removeFav(animeLink: String, sourceName: String) {
49 | viewModelScope.launch {
50 | withContext(Dispatchers.IO) {
51 | animeRepository.removeFavFromRoom(animeLink, sourceName)
52 | _isAnimeFav.postValue(false)
53 | }
54 | }
55 | }
56 |
57 | fun addToFav(animeLink: String, animeName: String, animeCoverLink: String, sourceName: String) {
58 | viewModelScope.launch {
59 | withContext(Dispatchers.IO) {
60 | animeRepository.addFavToRoom(
61 | FavRoomModel(
62 | animeLink,
63 | animeCoverLink,
64 | animeName,
65 | sourceName
66 | )
67 | )
68 | _isAnimeFav.postValue(true)
69 | }
70 | }
71 |
72 | }
73 | }
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.utils
2 |
3 | import com.google.gson.JsonElement
4 | import com.google.gson.JsonParser
5 | import com.talent.animescrap_common.sourceutils.AndroidCookieJar
6 | import okhttp3.FormBody
7 | import okhttp3.OkHttpClient
8 | import okhttp3.Request
9 | import org.jsoup.Jsoup
10 | import org.jsoup.nodes.Document
11 | import java.util.concurrent.TimeUnit
12 |
13 | object Utils {
14 |
15 | var httpClient = OkHttpClient.Builder()
16 | .cookieJar(AndroidCookieJar())
17 | .connectTimeout(30, TimeUnit.SECONDS)
18 | .readTimeout(30, TimeUnit.SECONDS)
19 | .callTimeout(2, TimeUnit.MINUTES)
20 | .build()
21 |
22 | fun get(url: String,
23 | mapOfHeaders: Map? = null
24 | ): String {
25 | val requestBuilder = Request.Builder().url(url)
26 | if (!mapOfHeaders.isNullOrEmpty()) {
27 | mapOfHeaders.forEach{
28 | requestBuilder.addHeader(it.key, it.value)
29 | }
30 | }
31 | return httpClient.newCall(requestBuilder.build())
32 | .execute().body!!.string()
33 | }
34 |
35 | fun post(url: String, mapOfHeaders: Map? = null, payload: Map? = null): String {
36 | val requestBuilder = Request.Builder().url(url)
37 |
38 | if (!mapOfHeaders.isNullOrEmpty()) {
39 | mapOfHeaders.forEach {
40 | requestBuilder.addHeader(it.key, it.value)
41 | }
42 | }
43 |
44 | val requestBody = payload?.let {
45 | FormBody.Builder().apply {
46 | it.forEach { (key, value) ->
47 | add(key, value)
48 | }
49 | }.build()
50 | }
51 |
52 | if (requestBody != null) {
53 | requestBuilder.post(requestBody)
54 | }
55 |
56 | val response = httpClient.newCall(requestBuilder.build()).execute()
57 | return response.body?.string() ?: ""
58 | }
59 |
60 | fun getJsoup(
61 | url: String,
62 | mapOfHeaders: Map? = null
63 | ): Document {
64 | return Jsoup.parse(get(url, mapOfHeaders))
65 | }
66 |
67 | fun getJson(
68 | url: String,
69 | mapOfHeaders: Map? = null
70 | ): JsonElement? {
71 | return JsonParser.parseString(get(url, mapOfHeaders))
72 | }
73 |
74 | fun postJson(
75 | url: String,
76 | mapOfHeaders: Map? = null,
77 | payload: Map? = null
78 | ): JsonElement? {
79 | val res = post(url, mapOfHeaders, payload)
80 | return JsonParser.parseString(res)
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/episode_bottom_sheet_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
33 |
34 |
35 |
42 |
43 |
53 |
54 |
55 |
64 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/sourceutils/WebViewClientCompat.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.sourceutils
2 |
3 | import android.annotation.TargetApi
4 | import android.os.Build
5 | import android.webkit.*
6 |
7 |
8 | abstract class WebViewClientCompat : WebViewClient() {
9 |
10 | open fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
11 | return false
12 | }
13 |
14 | open fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? {
15 | return null
16 | }
17 |
18 | open fun onReceivedErrorCompat(
19 | view: WebView,
20 | errorCode: Int,
21 | description: String?,
22 | failingUrl: String,
23 | isMainFrame: Boolean,
24 | ) {
25 | }
26 |
27 | @TargetApi(Build.VERSION_CODES.N)
28 | final override fun shouldOverrideUrlLoading(
29 | view: WebView,
30 | request: WebResourceRequest,
31 | ): Boolean {
32 | return shouldOverrideUrlCompat(view, request.url.toString())
33 | }
34 |
35 | final override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
36 | return shouldOverrideUrlCompat(view, url)
37 | }
38 |
39 | final override fun shouldInterceptRequest(
40 | view: WebView,
41 | request: WebResourceRequest,
42 | ): WebResourceResponse? {
43 | return shouldInterceptRequestCompat(view, request.url.toString())
44 | }
45 |
46 | final override fun shouldInterceptRequest(
47 | view: WebView,
48 | url: String,
49 | ): WebResourceResponse? {
50 | return shouldInterceptRequestCompat(view, url)
51 | }
52 |
53 | final override fun onReceivedError(
54 | view: WebView,
55 | request: WebResourceRequest,
56 | error: WebResourceError,
57 | ) {
58 | onReceivedErrorCompat(
59 | view,
60 | error.errorCode,
61 | error.description?.toString(),
62 | request.url.toString(),
63 | request.isForMainFrame,
64 | )
65 | }
66 |
67 | final override fun onReceivedError(
68 | view: WebView,
69 | errorCode: Int,
70 | description: String?,
71 | failingUrl: String,
72 | ) {
73 | onReceivedErrorCompat(view, errorCode, description, failingUrl, failingUrl == view.url)
74 | }
75 |
76 | final override fun onReceivedHttpError(
77 | view: WebView,
78 | request: WebResourceRequest,
79 | error: WebResourceResponse,
80 | ) {
81 | onReceivedErrorCompat(
82 | view,
83 | error.statusCode,
84 | error.reasonPhrase,
85 | request.url
86 | .toString(),
87 | request.isForMainFrame,
88 | )
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/double_tap_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
26 |
27 |
33 |
34 |
40 |
41 |
47 |
48 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/sourceutils/DDosGuardIntreceptor.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.sourceutils
2 |
3 | import android.webkit.CookieManager
4 | import okhttp3.*
5 |
6 | class DdosGuardInterceptor(private val client: OkHttpClient) : Interceptor {
7 |
8 | private val cookieManager by lazy { CookieManager.getInstance() }
9 |
10 | private fun GET(url: String): Request {
11 | return Request.Builder().url(url).build()
12 | }
13 | override fun intercept(chain: Interceptor.Chain): Response {
14 | val originalRequest = chain.request()
15 | val response = chain.proceed(originalRequest)
16 |
17 | // Check if DDos-GUARD is on
18 | if (response.code !in ERROR_CODES || response.header("Server") !in SERVER_CHECK) {
19 | return response
20 | }
21 |
22 | response.close()
23 | val cookies = cookieManager.getCookie(originalRequest.url.toString())
24 | val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
25 | cookies.split(";").mapNotNull { Cookie.parse(originalRequest.url, it) }
26 | } else {
27 | emptyList()
28 | }
29 | val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
30 | if (!ddg2Cookie?.value.isNullOrEmpty()) {
31 | return chain.proceed(originalRequest)
32 | }
33 |
34 | val newCookie = getNewCookie(originalRequest.url) ?: return chain.proceed(originalRequest)
35 | val newCookieHeader = buildString {
36 | (oldCookie + newCookie).forEachIndexed { index, cookie ->
37 | if (index > 0) append("; ")
38 | append(cookie.name).append('=').append(cookie.value)
39 | }
40 | }
41 |
42 | return chain.proceed(originalRequest.newBuilder().addHeader("cookie", newCookieHeader).build())
43 | }
44 |
45 | fun getNewCookie(url: HttpUrl): Cookie? {
46 | val cookies = cookieManager.getCookie(url.toString())
47 | val oldCookie = if (cookies != null && cookies.isNotEmpty()) {
48 | cookies.split(";").mapNotNull { Cookie.parse(url, it) }
49 | } else {
50 | emptyList()
51 | }
52 | val ddg2Cookie = oldCookie.firstOrNull { it.name == "__ddg2_" }
53 | if (!ddg2Cookie?.value.isNullOrEmpty()) {
54 | return ddg2Cookie
55 | }
56 | val wellKnown = client.newCall(GET("https://check.ddos-guard.net/check.js"))
57 | .execute().body!!.string()
58 | .substringAfter("'", "")
59 | .substringBefore("'", "")
60 | val checkUrl = "${url.scheme}://${url.host + wellKnown}"
61 | return client.newCall(GET(checkUrl)).execute().header("set-cookie")?.let {
62 | Cookie.parse(url, it)
63 | }
64 | }
65 |
66 | companion object {
67 | private val ERROR_CODES = listOf(403)
68 | private val SERVER_CHECK = listOf("ddos-guard")
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/landscape_cover_cardview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
28 |
29 |
33 |
34 |
41 |
42 |
45 |
46 |
54 |
55 |
56 |
57 |
58 |
59 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/portrait_cover_cardview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
28 |
29 |
33 |
34 |
41 |
42 |
45 |
46 |
54 |
55 |
56 |
57 |
58 |
59 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/portrait_cover_cardview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
28 |
29 |
33 |
34 |
41 |
42 |
45 |
46 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
13 |
14 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 |
48 |
49 |
55 |
56 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
26 |
27 |
31 |
32 |
36 |
37 |
38 |
42 |
43 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/animesources/AsianLoad.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources.animesources
2 |
3 | import com.talent.animescrapsources.animesources.sourceCommonExtractors.AsianExtractor
4 | import com.talent.animescrap_common.model.AnimeDetails
5 | import com.talent.animescrap_common.model.AnimeStreamLink
6 | import com.talent.animescrap_common.model.SimpleAnime
7 | import com.talent.animescrap_common.source.AnimeSource
8 | import com.talent.animescrap_common.utils.Utils.get
9 | import org.jsoup.Jsoup
10 |
11 | class AsianLoad : AnimeSource {
12 | private val mainUrl = "https://pladrac.net"
13 | override suspend fun animeDetails(contentLink: String): AnimeDetails {
14 | val url = "$mainUrl${contentLink}"
15 | val doc = Jsoup.parse(get(url))
16 | val animeCover = doc.selectFirst(".video-block")!!.getElementsByTag("img").attr("src")
17 | val animeName = doc.selectFirst(".video-details .date")!!.text()
18 | val animDesc = doc.selectFirst(".video-details .post-entry")!!.text()
19 |
20 | val eps = doc.selectFirst(".listing")!!.select("li")
21 | val subMap = mutableMapOf()
22 | var totalEp = eps.size
23 | eps.forEach { epLi ->
24 | val link = epLi.getElementsByTag("a").attr("href")
25 | // val name = epLi.select(".name").text().replace(animeName,"")
26 | subMap[totalEp.toString()] = link
27 | totalEp--
28 | }
29 |
30 | val epMap = mutableMapOf("DEFAULT" to subMap)
31 |
32 | return AnimeDetails(
33 | animeName,
34 | animDesc,
35 | animeCover,
36 | epMap
37 | )
38 | }
39 |
40 |
41 | override suspend fun searchAnime(searchedText: String): ArrayList {
42 | val searchUrl = "$mainUrl/search.html?keyword=${searchedText}"
43 | return getItems(searchUrl)
44 | }
45 |
46 | private fun getItems(url: String): ArrayList {
47 | val animeList = arrayListOf()
48 | val doc = Jsoup.parse(get(url))
49 | val allInfo = doc.getElementsByClass("video-block")
50 | for (item in allInfo) {
51 | val itemImage = item.getElementsByTag("img").attr("src")
52 | val itemName = item.getElementsByClass("name").text().substringBefore("Episode ")
53 | val itemLink = item.getElementsByTag("a").attr("href")
54 | animeList.add(
55 | SimpleAnime(
56 | itemName,
57 | itemImage,
58 | itemLink
59 | )
60 | )
61 | }
62 | return animeList
63 | }
64 |
65 | override suspend fun latestAnime(): ArrayList {
66 | return getItems(mainUrl)
67 | }
68 |
69 | override suspend fun trendingAnime(): ArrayList {
70 | return getItems("$mainUrl/popular")
71 |
72 | }
73 |
74 | override suspend fun streamLink(
75 | animeUrl: String,
76 | animeEpCode: String,
77 | extras: List?
78 | ): AnimeStreamLink {
79 | // Get the link of episode
80 | val animeEpUrl = "$mainUrl$animeEpCode"
81 | val doc = Jsoup.parse(get(animeEpUrl))
82 |
83 | val embedLink = "https:" + doc.selectFirst(".play-video")!!.getElementsByTag("iframe")
84 | .attr("src")
85 | println(embedLink)
86 | val link = AsianExtractor().getAsianStreamLink(embedLink)
87 | println(link)
88 | return AnimeStreamLink(link, "", true)
89 |
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/sourceutils/WebViewInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.sourceutils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.webkit.WebSettings
6 | import android.webkit.WebView
7 | import okhttp3.Headers
8 | import okhttp3.Interceptor
9 | import okhttp3.Request
10 | import okhttp3.Response
11 | import java.util.*
12 | import java.util.concurrent.CountDownLatch
13 | import java.util.concurrent.TimeUnit
14 |
15 | abstract class WebViewInterceptor(private val context: Context) : Interceptor {
16 |
17 |
18 | /**
19 | * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
20 | * blocking the main thread too much. If used too often we could consider moving it to the
21 | * Application class.
22 | */
23 | private val initWebView by lazy {
24 | try {
25 | WebSettings.getDefaultUserAgent(context)
26 | } catch (_: Exception) {
27 | // Avoid some crashes like when Chrome/WebView is being updated.
28 | }
29 | }
30 |
31 | abstract fun shouldIntercept(response: Response): Boolean
32 |
33 | abstract fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response
34 |
35 | override fun intercept(chain: Interceptor.Chain): Response {
36 | val request = chain.request()
37 | val response = chain.proceed(request)
38 | if (!shouldIntercept(response)) {
39 | return response
40 | }
41 | initWebView
42 |
43 | return intercept(chain, request, response)
44 | }
45 |
46 | fun parseHeaders(headers: Headers): Map {
47 | return headers
48 | // Keeping unsafe header makes web-view throw [net::ERR_INVALID_ARGUMENT]
49 | .filter { (name, value) ->
50 | isRequestHeaderSafe(name, value)
51 | }
52 | .groupBy(keySelector = { (name, _) -> name }) { (_, value) -> value }
53 | .mapValues { it.value.getOrNull(0).orEmpty() }
54 | }
55 |
56 | fun CountDownLatch.awaitFor30Seconds() {
57 | await(30, TimeUnit.SECONDS)
58 | }
59 |
60 | @SuppressLint("SetJavaScriptEnabled")
61 | fun createWebView(request: Request): WebView {
62 | return WebView(context).apply {
63 | with(settings) {
64 | javaScriptEnabled = true
65 | domStorageEnabled = true
66 | databaseEnabled = true
67 | useWideViewPort = true
68 | loadWithOverviewMode = true
69 | cacheMode = WebSettings.LOAD_DEFAULT
70 | }
71 | // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
72 | settings.userAgentString = request.header("User-Agent")
73 | }
74 | }
75 | }
76 |
77 | // Based on [IsRequestHeaderSafe] in https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc
78 | private fun isRequestHeaderSafe(_name: String, _value: String): Boolean {
79 | val name = _name.lowercase(Locale.ENGLISH)
80 | val value = _value.lowercase(Locale.ENGLISH)
81 | if (name in unsafeHeaderNames || name.startsWith("proxy-")) return false
82 | if (name == "connection" && value == "upgrade") return false
83 | return true
84 | }
85 |
86 | private val unsafeHeaderNames = listOf(
87 | "content-length",
88 | "host",
89 | "trailer",
90 | "te",
91 | "upgrade",
92 | "cookie2",
93 | "keep-alive",
94 | "transfer-encoding",
95 | "set-cookie"
96 | )
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/fragments/TrendingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.fragments
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import androidx.preference.PreferenceManager
12 | import androidx.recyclerview.widget.GridLayoutManager
13 | import com.talent.animescrap.adapter.AnimeRecyclerAdapter
14 | import com.talent.animescrap.databinding.FragmentTrendingBinding
15 | import com.talent.animescrap.ui.viewmodels.TrendingViewModel
16 | import dagger.hilt.android.AndroidEntryPoint
17 |
18 | @AndroidEntryPoint
19 | class TrendingFragment : Fragment() {
20 |
21 | private var _binding: FragmentTrendingBinding? = null
22 | private val trendingViewModel: TrendingViewModel by viewModels()
23 | private val selectedSource by lazy {
24 | PreferenceManager
25 | .getDefaultSharedPreferences(requireContext())
26 | .getString("source", "yugen")
27 | }
28 | private val rvAdapter by lazy {
29 | AnimeRecyclerAdapter(if (selectedSource == "animepahe" || selectedSource == "kiss_kh") "landscape card" else "portrait card")
30 | }
31 |
32 | // This property is only valid between onCreateView and
33 | // onDestroyView.
34 | private val binding get() = _binding!!
35 |
36 | override fun onCreateView(
37 | inflater: LayoutInflater,
38 | container: ViewGroup?,
39 | savedInstanceState: Bundle?
40 | ): View {
41 |
42 | _binding = FragmentTrendingBinding.inflate(inflater, container, false)
43 |
44 | binding.progressbarInMain.visibility = View.VISIBLE
45 | if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
46 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
47 | } else {
48 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
49 | }
50 | binding.recyclerView.adapter = rvAdapter
51 | binding.recyclerView.setHasFixedSize(true)
52 |
53 | trendingViewModel.trendingAnimeList.observe(viewLifecycleOwner) {
54 | binding.progressbarInMain.visibility = View.GONE
55 | if (it.isNotEmpty()) {
56 | binding.errorCard.visibility = View.GONE
57 | } else {
58 | binding.errorCard.visibility = View.VISIBLE
59 | }
60 |
61 | rvAdapter.submitList(it)
62 |
63 | if (binding.swipeContainer.isRefreshing) {
64 | binding.swipeContainer.isRefreshing = false
65 | }
66 | }
67 |
68 | binding.swipeContainer.setOnRefreshListener { trendingViewModel.getTrendingAnimeList() }
69 |
70 | return binding.root
71 | }
72 |
73 | override fun onDestroyView() {
74 | super.onDestroyView()
75 | _binding = null
76 | }
77 |
78 | override fun onConfigurationChanged(newConfig: Configuration) {
79 | super.onConfigurationChanged(newConfig)
80 | if (activity != null) {
81 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
82 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
83 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
84 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/fragments/SearchFragment.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.fragments
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.core.widget.addTextChangedListener
10 | import androidx.fragment.app.Fragment
11 | import androidx.fragment.app.viewModels
12 | import androidx.preference.PreferenceManager
13 | import androidx.recyclerview.widget.GridLayoutManager
14 | import com.talent.animescrap.adapter.AnimeRecyclerAdapter
15 | import com.talent.animescrap.databinding.FragmentSearchBinding
16 | import com.talent.animescrap.ui.viewmodels.SearchViewModel
17 | import dagger.hilt.android.AndroidEntryPoint
18 | import java.util.*
19 |
20 | @AndroidEntryPoint
21 | class SearchFragment : Fragment() {
22 |
23 | private var _binding: FragmentSearchBinding? = null
24 | private val binding get() = _binding!!
25 | private val searchViewModel by viewModels()
26 | private val selectedSource by lazy {
27 | PreferenceManager
28 | .getDefaultSharedPreferences(requireContext())
29 | .getString("source", "yugen")
30 | }
31 | private val rvAdapter by lazy {
32 | AnimeRecyclerAdapter(if (selectedSource == "kiss_kh") "landscape card" else "portrait card")
33 | }
34 |
35 | override fun onCreateView(
36 | inflater: LayoutInflater, container: ViewGroup?,
37 | savedInstanceState: Bundle?
38 | ): View {
39 | _binding = FragmentSearchBinding.inflate(inflater, container, false)
40 |
41 | binding.progressbarInMain.visibility = View.GONE
42 | if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
43 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
44 | } else {
45 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
46 | }
47 | binding.recyclerView.adapter = rvAdapter
48 |
49 | searchViewModel.searchedAnimeList.observe(viewLifecycleOwner) { animeList ->
50 | binding.progressbarInMain.visibility = View.GONE
51 | binding.recyclerView.setHasFixedSize(true)
52 |
53 | rvAdapter.submitList(animeList) {
54 | if (animeList.isNotEmpty())
55 | binding.recyclerView.scrollToPosition(0)
56 | }
57 | }
58 |
59 | binding.textInputEditText.addTextChangedListener {
60 | val searchedText =
61 | it.toString().lowercase(Locale.ENGLISH).replace("[^A-Za-z0-9]".toRegex(), " ")
62 | .trim().replace("\\s+".toRegex(), " ").replace(" ", "+")
63 |
64 | if (searchedText.length >= 3) {
65 | binding.progressbarInMain.visibility = View.VISIBLE
66 | binding.recyclerView.visibility = View.VISIBLE
67 | searchViewModel.searchAnime(searchedText)
68 | }
69 | }
70 |
71 | return binding.root
72 | }
73 |
74 | override fun onConfigurationChanged(newConfig: Configuration) {
75 | super.onConfigurationChanged(newConfig)
76 | if (activity != null) {
77 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
78 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
79 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
80 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/fragments/FavoriteFragment.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.fragments
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import androidx.preference.PreferenceManager
12 | import androidx.recyclerview.widget.GridLayoutManager
13 | import com.talent.animescrap.adapter.AnimeRecyclerAdapter
14 | import com.talent.animescrap.databinding.FragmentFavoriteBinding
15 | import com.talent.animescrap.ui.viewmodels.FavoriteViewModel
16 | import dagger.hilt.android.AndroidEntryPoint
17 |
18 | @AndroidEntryPoint
19 | class FavoriteFragment : Fragment() {
20 |
21 | private var _binding: FragmentFavoriteBinding? = null
22 | private val favoriteViewModel: FavoriteViewModel by viewModels()
23 | private val selectedSource by lazy {
24 | PreferenceManager
25 | .getDefaultSharedPreferences(requireContext())
26 | .getString("source", "yugen")
27 | }
28 | private val rvAdapter by lazy {
29 | AnimeRecyclerAdapter(if (selectedSource == "kiss_kh") "landscape card" else "portrait card")
30 | }
31 |
32 | // This property is only valid between onCreateView and
33 | // onDestroyView.
34 | private val binding get() = _binding!!
35 |
36 | override fun onCreateView(
37 | inflater: LayoutInflater,
38 | container: ViewGroup?,
39 | savedInstanceState: Bundle?
40 | ): View {
41 |
42 | _binding = FragmentFavoriteBinding.inflate(inflater, container, false)
43 |
44 | binding.progressbarInMain.visibility = View.VISIBLE
45 | if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
46 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
47 | } else {
48 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
49 | }
50 | binding.recyclerView.adapter = rvAdapter
51 | binding.recyclerView.setHasFixedSize(true)
52 |
53 | favoriteViewModel.favoriteAnimeList.observe(viewLifecycleOwner) {
54 | binding.progressbarInMain.visibility = View.GONE
55 | if (it.isNotEmpty()) {
56 | binding.errorCard.visibility = View.GONE
57 | } else {
58 | binding.errorCard.visibility = View.VISIBLE
59 | }
60 | rvAdapter.submitList(it)
61 | if (binding.swipeContainer.isRefreshing) {
62 | binding.swipeContainer.isRefreshing = false
63 | }
64 | }
65 |
66 | binding.swipeContainer.setOnRefreshListener { favoriteViewModel.getFavorites() }
67 |
68 | return binding.root
69 | }
70 |
71 | override fun onResume() {
72 | super.onResume()
73 | favoriteViewModel.getFavorites()
74 |
75 | }
76 |
77 | override fun onDestroyView() {
78 | super.onDestroyView()
79 | _binding = null
80 | }
81 |
82 | override fun onConfigurationChanged(newConfig: Configuration) {
83 | super.onConfigurationChanged(newConfig)
84 | if (activity != null) {
85 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
86 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
87 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
88 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
89 | }
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/animesources/MyAsianTvSource.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources.animesources
2 |
3 | import com.talent.animescrapsources.animesources.sourceCommonExtractors.AsianExtractor
4 | import com.talent.animescrap_common.model.AnimeDetails
5 | import com.talent.animescrap_common.model.AnimeStreamLink
6 | import com.talent.animescrap_common.model.SimpleAnime
7 | import com.talent.animescrap_common.source.AnimeSource
8 | import com.talent.animescrap_common.utils.Utils.get
9 | import org.jsoup.Jsoup
10 | import org.jsoup.select.Elements
11 |
12 | class MyAsianTvSource : AnimeSource {
13 | private val mainUrl = "https://myasiantv.cx"
14 | override suspend fun animeDetails(contentLink: String): AnimeDetails {
15 | val url = "$mainUrl${contentLink}"
16 | val doc = Jsoup.parse(get(url))
17 | val animeCover = doc.selectFirst(".poster")!!.getElementsByTag("img").attr("src")
18 | val animeName = doc.selectFirst(".movie h1")!!.text()
19 | val animDesc = doc.selectFirst(".info")!!.text()
20 |
21 | val lastEpUrl = doc.selectFirst(".list-episode a")!!.attr("href")
22 | val lastEp = lastEpUrl.substringAfterLast("episode-").toInt()
23 | val epPrefix = lastEpUrl.replaceAfterLast("episode-", "")
24 | val subMap = mutableMapOf()
25 |
26 | for (ep in 1..lastEp) {
27 | subMap["$ep"] = epPrefix + ep
28 | }
29 |
30 | val epMap = mutableMapOf("DEFAULT" to subMap)
31 |
32 | return AnimeDetails(
33 | animeName,
34 | animDesc,
35 | animeCover,
36 | epMap
37 | )
38 | }
39 |
40 |
41 | override suspend fun searchAnime(searchedText: String): ArrayList {
42 | val searchUrl = "$mainUrl/search.html?key=${searchedText}"
43 | println(searchUrl)
44 | val allInfo = Jsoup.parse(get(searchUrl)).select(".items > li")
45 | println(allInfo)
46 | return getItems(allInfo)
47 | }
48 |
49 | private fun getItems(allInfo: Elements): ArrayList {
50 | val animeList = arrayListOf()
51 | for (item in allInfo) {
52 | val itemImage = item.getElementsByTag("img").attr("src")
53 | val itemName = item.getElementsByTag("img").attr("alt")
54 | val itemLink = item.getElementsByTag("a").attr("href")
55 | animeList.add(
56 | SimpleAnime(
57 | itemName,
58 | itemImage,
59 | itemLink
60 | )
61 | )
62 | }
63 | return animeList
64 | }
65 |
66 | override suspend fun latestAnime(): ArrayList {
67 | return getItems(
68 | Jsoup.parse(get("$mainUrl/show/goblin")).select("#sidebarlist-2 div > a")
69 | )
70 | }
71 |
72 | override suspend fun trendingAnime(): ArrayList {
73 | return getItems(
74 | Jsoup.parse(get("$mainUrl/anclytic.html?id=3"))
75 | .getElementsByTag("div")
76 | )
77 |
78 | }
79 |
80 | override suspend fun streamLink(
81 | animeUrl: String,
82 | animeEpCode: String,
83 | extras: List?
84 | ): AnimeStreamLink {
85 | // Get the link of episode
86 | val animeEpUrl = "$mainUrl$animeEpCode"
87 | println(animeEpUrl)
88 | val doc = Jsoup.parse(get(animeEpUrl))
89 | val embedLink =
90 | "https:" + doc.getElementsByAttribute("data-video").first()!!.attr("data-video")
91 | println(embedLink)
92 | val link = AsianExtractor().getAsianStreamLink(embedLink)
93 | println(link)
94 | return AnimeStreamLink(link, "", true)
95 |
96 | }
97 |
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/ui/fragments/LatestFragment.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.ui.fragments
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import androidx.preference.PreferenceManager
12 | import androidx.recyclerview.widget.GridLayoutManager
13 | import com.talent.animescrap.adapter.AnimeRecyclerAdapter
14 | import com.talent.animescrap.databinding.FragmentLatestBinding
15 | import com.talent.animescrap.ui.viewmodels.LatestViewModel
16 | import dagger.hilt.android.AndroidEntryPoint
17 |
18 | @AndroidEntryPoint
19 | class LatestFragment : Fragment() {
20 |
21 | private var _binding: FragmentLatestBinding? = null
22 | private val latestViewModel: LatestViewModel by viewModels()
23 | private val selectedSource by lazy {
24 | PreferenceManager
25 | .getDefaultSharedPreferences(requireContext())
26 | .getString("source", "yugen")
27 | }
28 | private val rvAdapter by lazy {
29 | AnimeRecyclerAdapter(
30 | if (selectedSource in arrayListOf(
31 | "animepahe",
32 | "kiss_kh",
33 | "marin_moe"
34 | )
35 | ) "landscape card" else "portrait card"
36 | )
37 | }
38 |
39 | // This property is only valid between onCreateView and
40 | // onDestroyView.
41 | private val binding get() = _binding!!
42 |
43 | override fun onCreateView(
44 | inflater: LayoutInflater,
45 | container: ViewGroup?,
46 | savedInstanceState: Bundle?
47 | ): View {
48 |
49 | _binding = FragmentLatestBinding.inflate(inflater, container, false)
50 | binding.progressbarInMain.visibility = View.VISIBLE
51 | if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
52 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
53 | } else {
54 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
55 | }
56 | binding.recyclerView.adapter = rvAdapter
57 | binding.recyclerView.setHasFixedSize(true)
58 |
59 | latestViewModel.latestAnimeList.observe(viewLifecycleOwner) {
60 | binding.progressbarInMain.visibility = View.GONE
61 | if (it.isNotEmpty()) {
62 | binding.errorCard.visibility = View.GONE
63 | } else {
64 | binding.errorCard.visibility = View.VISIBLE
65 | }
66 |
67 | if (binding.swipeContainer.isRefreshing) {
68 | rvAdapter.submitList(it) {
69 | binding.recyclerView.smoothScrollToPosition(0)
70 | }
71 | binding.swipeContainer.isRefreshing = false
72 | } else {
73 | rvAdapter.submitList(it)
74 | }
75 | }
76 |
77 | binding.swipeContainer.setOnRefreshListener { latestViewModel.getLatestAnimeList() }
78 |
79 | return binding.root
80 | }
81 |
82 | override fun onDestroyView() {
83 | super.onDestroyView()
84 | _binding = null
85 | }
86 |
87 | override fun onConfigurationChanged(newConfig: Configuration) {
88 | super.onConfigurationChanged(newConfig)
89 | if (activity != null) {
90 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
91 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 2)
92 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
93 | binding.recyclerView.layoutManager = GridLayoutManager(activity as Context, 4)
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/res/navigation/mobile_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
16 |
20 |
21 |
22 |
23 |
28 |
31 |
35 |
36 |
37 |
38 |
43 |
46 |
50 |
51 |
52 |
57 |
60 |
64 |
65 |
66 |
67 |
72 |
76 |
79 |
80 |
81 |
85 |
86 |
91 |
95 |
96 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-android'
5 | id 'androidx.navigation.safeargs'
6 | id 'dagger.hilt.android.plugin'
7 | id 'kotlin-parcelize'
8 | id 'kotlin-kapt'
9 | }
10 |
11 | android {
12 |
13 | namespace 'com.talent.animescrap'
14 | compileSdk 34
15 |
16 | defaultConfig {
17 | applicationId "com.talent.animescrap"
18 | minSdk 23
19 | targetSdk 34
20 | versionCode 18
21 | versionName "2.5.3"
22 | archivesBaseName = "AnimeScrap"
23 |
24 | kapt {
25 | arguments {
26 | arg("room.schemaLocation", "$projectDir/schemas".toString())
27 | arg("room.incremental", "true")
28 | arg("room.expandProjection", "true")
29 | }
30 | correctErrorTypes true
31 | }
32 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
33 | }
34 |
35 | buildTypes {
36 | release {
37 | minifyEnabled true
38 | shrinkResources true
39 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
40 | }
41 | debug {
42 | applicationIdSuffix ".debug"
43 | }
44 | }
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_17
47 | targetCompatibility JavaVersion.VERSION_17
48 | }
49 | kotlinOptions {
50 | jvmTarget = '17'
51 | }
52 | packagingOptions {
53 | resources {
54 | excludes += ['META-INF/atomicfu.kotlin_module']
55 | }
56 | }
57 |
58 | buildFeatures {
59 | viewBinding true
60 | dataBinding true
61 | }
62 | }
63 |
64 | dependencies {
65 |
66 | implementation 'androidx.core:core-ktx:1.12.0'
67 | implementation 'androidx.appcompat:appcompat:1.6.1'
68 | implementation 'com.google.android.material:material:1.10.0'
69 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
70 | implementation project(path: ':AnimeScrapCommon')
71 | implementation project(path: ':animeSources')
72 |
73 | // ExoPlayer + HLS + UI + MediaSession
74 | def exo_version = "2.19.1"
75 | implementation "com.google.android.exoplayer:exoplayer:$exo_version"
76 | implementation "com.google.android.exoplayer:exoplayer-ui:$exo_version"
77 | implementation "com.google.android.exoplayer:exoplayer-hls:$exo_version"
78 | implementation "com.google.android.exoplayer:extension-mediasession:$exo_version"
79 |
80 | // MVVM
81 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
82 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
83 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
84 |
85 | // Navigation - Jetpack
86 | implementation 'androidx.navigation:navigation-fragment-ktx:2.7.4'
87 | implementation 'androidx.navigation:navigation-ui-ktx:2.7.4'
88 |
89 | // Network
90 | implementation 'org.jsoup:jsoup:1.15.2' // Web scraping tool
91 | implementation 'com.squareup.okhttp3:okhttp:4.10.0'
92 | implementation 'com.google.code.gson:gson:2.9.0' // Json Parser
93 | implementation 'io.coil-kt:coil:2.2.2' // Photo from network
94 |
95 | // Room components
96 | def roomVersion = '2.6.0'
97 | implementation "androidx.room:room-ktx:$roomVersion"
98 | kapt "androidx.room:room-compiler:$roomVersion"
99 | androidTestImplementation "androidx.room:room-testing:$roomVersion"
100 |
101 | // Swipe to Refresh
102 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
103 |
104 | // Preference / Settings
105 | implementation 'androidx.preference:preference-ktx:1.2.1'
106 |
107 | // Hilt dependency injection
108 | implementation "com.google.dagger:hilt-android:2.44.2"
109 | kapt "com.google.dagger:hilt-compiler:2.44.2"
110 |
111 | // Shimmer
112 | implementation 'com.facebook.shimmer:shimmer:0.5.0'
113 |
114 | // Test
115 | testImplementation 'junit:junit:4.13.2'
116 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
117 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/talent/animescrap/widgets/DoubleTapPlayerView.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap.widgets
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.media.session.PlaybackState
6 | import android.os.Handler
7 | import android.os.Looper
8 | import android.util.AttributeSet
9 | import android.view.GestureDetector
10 | import android.view.MotionEvent
11 | import androidx.core.view.GestureDetectorCompat
12 | import com.google.android.exoplayer2.ui.StyledPlayerView
13 |
14 | class DoubleTapPlayerView @JvmOverloads constructor(
15 | context: Context,
16 | attrs: AttributeSet? = null,
17 | defStyleAttr: Int = 0
18 | ) : StyledPlayerView(context, attrs, defStyleAttr) {
19 |
20 | companion object {
21 | const val SEEK_SECONDS = 10
22 | const val SEEK_MILLISECONDS = SEEK_SECONDS * 1000
23 | }
24 |
25 | lateinit var doubleTapOverlay: DoubleTapOverlay
26 | private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
27 | private var isDoubleTapping = false
28 | private val handler = Handler(Looper.getMainLooper())
29 | private val stopDoubleTap = Runnable {
30 | isDoubleTapping = false
31 | }
32 |
33 | override fun onSingleTapUp(e: MotionEvent): Boolean {
34 | if (isDoubleTapping) {
35 | handleDoubleTap(e.x, e.y)
36 | }
37 | return true
38 | }
39 |
40 | override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
41 | if (!isDoubleTapping)
42 | performClick()
43 | return true
44 | }
45 |
46 | override fun onDoubleTap(e: MotionEvent): Boolean {
47 | if (!isDoubleTapping)
48 | keepDoubleTapping()
49 | return true
50 | }
51 |
52 | override fun onDoubleTapEvent(e: MotionEvent): Boolean {
53 | if (e.actionMasked == MotionEvent.ACTION_UP && isDoubleTapping)
54 | handleDoubleTap(e.x, e.y)
55 | return true
56 | }
57 |
58 | fun cancelDoubleTap() {
59 | handler.removeCallbacks(stopDoubleTap)
60 | isDoubleTapping = false
61 | }
62 |
63 | fun keepDoubleTapping() {
64 | handler.removeCallbacks(stopDoubleTap)
65 | isDoubleTapping = true
66 | handler.postDelayed(stopDoubleTap, 700)
67 | }
68 | }
69 | private val gestureDetector = GestureDetectorCompat(context, gestureListener)
70 |
71 | @SuppressLint("ClickableViewAccessibility")
72 | override fun onTouchEvent(event: MotionEvent): Boolean {
73 | gestureDetector.onTouchEvent(event)
74 | return true
75 | }
76 |
77 | fun handleDoubleTap(x: Float, y: Float) {
78 | player?.let { player ->
79 | if (player.playbackState == PlaybackState.STATE_ERROR ||
80 | player.playbackState == PlaybackState.STATE_NONE ||
81 | player.playbackState == PlaybackState.STATE_STOPPED
82 | )
83 | gestureListener.cancelDoubleTap()
84 | else if (player.currentPosition > 500 && x < doubleTapOverlay.width * 0.35)
85 | triggerSeek(false, x, y)
86 | else if (player.currentPosition < player.duration && x > doubleTapOverlay.width * 0.65)
87 | triggerSeek(true, x, y)
88 | }
89 | }
90 |
91 | private fun triggerSeek(forward: Boolean, x: Float, y: Float) {
92 | doubleTapOverlay.showAnimation(forward, x, y)
93 | player?.let { player ->
94 | seekTo(
95 | if (forward)
96 | player.currentPosition + SEEK_MILLISECONDS
97 | else
98 | player.currentPosition - SEEK_MILLISECONDS
99 | )
100 | }
101 | }
102 |
103 | private fun seekTo(position: Long) {
104 | player?.let { player ->
105 | when {
106 | position <= 0 -> player.seekTo(0)
107 | position >= player.duration -> player.seekTo(player.duration)
108 | else -> {
109 | gestureListener.keepDoubleTapping()
110 | player.seekTo(position)
111 | }
112 | }
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FC8686
4 | #EE0000
5 | #B30000
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FFFFFFFF
11 | #FFFFFFFF
12 |
13 |
14 |
15 | #AE2D3D
16 | #AE2D3D
17 | #FFFFFF
18 | #FFDADA
19 | #40000B
20 | #9C4049
21 | #FFFFFF
22 | #FFDADA
23 | #40000C
24 | #9C4049
25 | #FFFFFF
26 | #FFDADA
27 | #40000C
28 | #BA1A1A
29 | #FFDAD6
30 | #FFFFFF
31 | #410002
32 | #FFFBFF
33 | #201A1A
34 | #FFFBFF
35 | #201A1A
36 | #F4DDDD
37 | #524343
38 | #857373
39 | #FBEEED
40 | #362F2F
41 | #FFB3B4
42 | #000000
43 | #AE2D3D
44 | #AE2D3D
45 | #FFB3B4
46 | #680017
47 | #8D1228
48 | #FFDADA
49 | #FFB3B5
50 | #5F121E
51 | #7D2933
52 | #FFDADA
53 | #FFB3B5
54 | #5F121E
55 | #7D2933
56 | #FFDADA
57 | #FFB4AB
58 | #93000A
59 | #690005
60 | #FFDAD6
61 | #201A1A
62 | #ECE0DF
63 | #201A1A
64 | #ECE0DF
65 | #524343
66 | #D7C1C1
67 | #9F8C8C
68 | #201A1A
69 | #ECE0DF
70 | #AE2D3D
71 | #000000
72 | #FFB3B4
73 | #FFB3B4
74 |
75 |
76 | #18FFFFFF
77 | #20EEEEEE
78 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Anime Scrap
3 |
4 | Trending
5 | Latest
6 | Favorite
7 | Search
8 | Anime Details
9 |
10 | Play
11 | Forward
12 | Rewind
13 | Auto
14 | rotate display
15 | Display scale
16 | unlock
17 | ZOOM
18 | STRETCHED
19 | FIT
20 | 0:00
21 | timer bar
22 |
23 | Favorite
24 | ImageView in Main
25 |
26 | Splash C2 Image
27 |
28 | IMAGE TITLE
29 | coverOfAnime
30 | Synopsis:
31 | Not Started Yet
32 | Select Episode
33 | Episode %1$s
34 | Last Watched: %1$s
35 | %1$s - %2$s
36 | Loading Episode
37 | %1$sx
38 | back to home
39 |
40 |
41 |
42 | Settings
43 | Will only work if you have MX Player installed on your device
44 | Use MX player
45 | Using internal player
46 | Using external player
47 | Use external video player
48 | Dynamic colors disabled
49 | Dynamic colors enabled
50 | Use dynamic colors
51 | Source
52 | Select Source
53 | Picture-in-picture disabled
54 | Picture-in-picture enabled
55 | Enable PiP
56 | Theme
57 | Player
58 |
59 | +%d seconds
60 | -%d seconds
61 |
62 | Something went wrong, unable to connect to the server. Please check your network connection
63 | Your Favorites will show here
64 | Hello blank fragment
65 | Player
66 | Dark Mode
67 | Update Available
68 | No Update Available
69 | Downloading Update
70 | Check for newer version of the app
71 | Check for updates
72 | Sort
73 | Enable Video Cache
74 | Video cache disabled in player
75 | Video cache enabled in player
76 | Autoplay next episode when the current episode ends
77 | Autoplay next episode
78 | Press Back Again to Exit Player
79 |
80 |
--------------------------------------------------------------------------------
/AnimeScrapCommon/src/main/java/com/talent/animescrap_common/sourceutils/CloudflareInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrap_common.sourceutils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.webkit.*
6 | import androidx.core.content.ContextCompat
7 | import okhttp3.*
8 | import okhttp3.HttpUrl.Companion.toHttpUrl
9 | import java.io.IOException
10 | import java.util.*
11 | import java.util.concurrent.CountDownLatch
12 |
13 |
14 | class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
15 |
16 | private val executor = ContextCompat.getMainExecutor(context)
17 | val cookieManager = AndroidCookieJar()
18 |
19 | override fun shouldIntercept(response: Response): Boolean {
20 | // Check if Cloudflare anti-bot is on
21 | return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
22 | }
23 |
24 |
25 | override fun intercept(
26 | chain: Interceptor.Chain,
27 | request: Request,
28 | response: Response
29 | ): Response {
30 | try {
31 | response.close()
32 | cookieManager.remove(request.url, COOKIE_NAMES, 0)
33 | val oldCookie = cookieManager.get(request.url)
34 | .firstOrNull { it.name == "cf_clearance" }
35 | resolveWithWebView(request, oldCookie)
36 |
37 | return chain.proceed(request)
38 | }
39 | // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
40 | // we don't crash the entire app
41 | catch (e: CloudflareBypassException) {
42 | throw IOException("context.getString(R.string.information_cloudflare_bypass_failure)")
43 | } catch (e: Exception) {
44 | throw IOException(e)
45 | }
46 | }
47 |
48 | @SuppressLint("SetJavaScriptEnabled")
49 | private fun resolveWithWebView(originalRequest: Request, oldCookie: Cookie?) {
50 | // We need to lock this thread until the WebView finds the challenge solution url, because
51 | // OkHttp doesn't support asynchronous interceptors.
52 | val latch = CountDownLatch(1)
53 |
54 | var webView: WebView?
55 |
56 | var challengeFound = false
57 | var cloudflareBypassed = false
58 |
59 | val origRequestUrl = originalRequest.url.toString()
60 | val headers = parseHeaders(originalRequest.headers)
61 |
62 | executor.execute {
63 | webView = createWebView(originalRequest)
64 |
65 | webView?.webViewClient = object : WebViewClientCompat() {
66 | override fun onPageFinished(view: WebView, url: String) {
67 | fun isCloudFlareBypassed(): Boolean {
68 | return cookieManager.get(origRequestUrl.toHttpUrl())
69 | .firstOrNull { it.name == "cf_clearance" }
70 | .let { it != null && it != oldCookie }
71 | }
72 |
73 | if (isCloudFlareBypassed()) {
74 | cloudflareBypassed = true
75 | latch.countDown()
76 | }
77 |
78 | if (url == origRequestUrl && !challengeFound) {
79 | // The first request didn't return the challenge, abort.
80 | latch.countDown()
81 | }
82 | }
83 |
84 | override fun onReceivedErrorCompat(
85 | view: WebView,
86 | errorCode: Int,
87 | description: String?,
88 | failingUrl: String,
89 | isMainFrame: Boolean,
90 | ) {
91 | if (isMainFrame) {
92 | if (errorCode in ERROR_CODES) {
93 | // Found the Cloudflare challenge page.
94 | challengeFound = true
95 | } else {
96 | // Unlock thread, the challenge wasn't found.
97 | latch.countDown()
98 | }
99 | }
100 | }
101 | }
102 |
103 | webView?.loadUrl(origRequestUrl, headers)
104 | }
105 |
106 | latch.awaitFor30Seconds()
107 |
108 | // Throw exception if we failed to bypass Cloudflare
109 | if (!cloudflareBypassed) {
110 | throw CloudflareBypassException()
111 | }
112 | }
113 | }
114 |
115 | private val ERROR_CODES = listOf(403, 503)
116 | private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
117 | private val COOKIE_NAMES = listOf("cf_clearance")
118 |
119 | private class CloudflareBypassException : Exception()
120 |
121 |
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/animesources/KissKhSource.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources.animesources
2 |
3 | import com.talent.animescrap_common.model.AnimeDetails
4 | import com.talent.animescrap_common.model.AnimeStreamLink
5 | import com.talent.animescrap_common.model.SimpleAnime
6 | import com.talent.animescrap_common.utils.Utils.getJson
7 | import com.talent.animescrap_common.source.AnimeSource
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 |
11 |
12 | class KissKhSource : AnimeSource {
13 |
14 | private val mainUrl = "https://kisskh.co"
15 |
16 | override suspend fun animeDetails(contentLink: String): AnimeDetails =
17 | withContext(Dispatchers.IO) {
18 | val url = "$mainUrl/api/DramaList/Drama/$contentLink?isq=false"
19 | println(url)
20 | val res = getJson(url)!!.asJsonObject
21 |
22 | val animeCover = res["thumbnail"].asString
23 | println(animeCover)
24 | val animeName = res["title"].asString
25 | val animDesc = res["description"].asString
26 | val eps = res["episodes"].asJsonArray
27 | println(eps)
28 | val epMap = mutableMapOf()
29 | eps.reversed().forEach { ep ->
30 | epMap[ep.asJsonObject["number"].asInt.toString()] =
31 | ep.asJsonObject["id"].asString
32 |
33 | }
34 | return@withContext AnimeDetails(
35 | animeName,
36 | animDesc,
37 | animeCover,
38 | mapOf("SUB" to epMap)
39 | )
40 | }
41 |
42 |
43 | override suspend fun searchAnime(searchedText: String) = withContext(Dispatchers.IO) {
44 | val animeList = arrayListOf()
45 | val url = "$mainUrl/api/DramaList/Search?q=$searchedText&type=0"
46 | println(url)
47 | val res = getJson(url)!!.asJsonArray
48 | for (json in res) {
49 | val name = json.asJsonObject["title"].asString
50 | val image = json.asJsonObject["thumbnail"].asString
51 | val id = json.asJsonObject["id"].asString
52 | animeList.add(SimpleAnime(name, image, id))
53 | }
54 | return@withContext animeList
55 | }
56 |
57 | override suspend fun latestAnime(): ArrayList =
58 | withContext(Dispatchers.IO) {
59 | val url =
60 | "$mainUrl/api/DramaList/List?page=1&type=0&sub=0&country=0&status=0&order=2&pageSize=40"
61 | return@withContext getAnimeList(url)
62 | }
63 |
64 | override suspend fun trendingAnime(): ArrayList =
65 | withContext(Dispatchers.IO) {
66 | val url =
67 | "$mainUrl/api/DramaList/List?page=1&type=0&sub=0&country=0&status=0&order=1&pageSize=40"
68 | return@withContext getAnimeList(url)
69 | }
70 |
71 | private fun getAnimeList(url: String): ArrayList {
72 | val animeList = arrayListOf()
73 | val res = getJson(url)!!.asJsonObject["data"].asJsonArray
74 | for (json in res) {
75 | val name = json.asJsonObject["title"].asString
76 | val image = json.asJsonObject["thumbnail"].asString
77 | val id = json.asJsonObject["id"].asString
78 | animeList.add(SimpleAnime(name, image, id))
79 | }
80 | return animeList
81 | }
82 |
83 | override suspend fun streamLink(
84 | animeUrl: String,
85 | animeEpCode: String,
86 | extras: List?
87 | ): AnimeStreamLink =
88 | withContext(Dispatchers.IO) {
89 |
90 | println(animeUrl)
91 | println(animeEpCode)
92 |
93 | val url = "$mainUrl/api/DramaList/Episode/$animeEpCode.png?err=false&ts=&time="
94 | val res = getJson(url)!!.asJsonObject
95 | println(res)
96 |
97 | var subs = ""
98 | getJson("$mainUrl/api/Sub/$animeEpCode")?.asJsonArray
99 | .let {
100 | if (it != null && !it.isJsonNull && !it.isEmpty) {
101 | val subObj = it.first().asJsonObject
102 | subs = if (subObj["default"].asBoolean) subObj["src"].asString else ""
103 | }
104 | }
105 | println(res["Video"].asString)
106 | return@withContext AnimeStreamLink(
107 | if (!res["Video"].asString.contains("https")) "https:${res["Video"].asString}" else res["Video"].asString,
108 | subs,
109 | true,
110 | extraHeaders = hashMapOf(
111 | "referer" to "https://kisskh.me/", "origin" to "https://kisskh.me"
112 | )
113 | )
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/animeSources/src/main/java/com/talent/animescrapsources/animesources/KawaiifuSource.kt:
--------------------------------------------------------------------------------
1 | package com.talent.animescrapsources.animesources
2 |
3 | import android.content.Context
4 | import com.talent.animescrap_common.sourceutils.AndroidCookieJar
5 | import com.talent.animescrap_common.sourceutils.CloudflareInterceptor
6 | import com.talent.animescrap_common.model.AnimeDetails
7 | import com.talent.animescrap_common.model.AnimeStreamLink
8 | import com.talent.animescrap_common.model.SimpleAnime
9 | import com.talent.animescrap_common.source.AnimeSource
10 | import okhttp3.OkHttpClient
11 | import okhttp3.Request
12 | import org.jsoup.Jsoup
13 | import java.util.concurrent.TimeUnit
14 |
15 | class KawaiifuSource(context: Context) : AnimeSource {
16 | private val mainUrl = "https://kawaiifu.com"
17 | private val streamUrl = "https://domdom.stream"
18 |
19 | private val client = OkHttpClient.Builder()
20 | .cookieJar(AndroidCookieJar())
21 | .connectTimeout(30, TimeUnit.SECONDS)
22 | .readTimeout(30, TimeUnit.SECONDS)
23 | .callTimeout(2, TimeUnit.MINUTES)
24 | .addInterceptor(CloudflareInterceptor(context))
25 | .build()
26 |
27 | override suspend fun animeDetails(contentLink: String): AnimeDetails {
28 | println(contentLink)
29 |
30 | val url = if (!contentLink.contains("/others/")) "$streamUrl/anime${
31 | contentLink.replace("https://kawaiifu.com", "").replaceBefore("/", "")
32 | }".removeSuffix(".html")
33 | else contentLink
34 | println(url)
35 | val res = Jsoup.parse(get(url))
36 |
37 | val title = res.selectFirst(".desc .title")!!.text()
38 | val desc = res.select(".desc .wrap-desc").text()
39 | val image = res.selectFirst(".section .thumb img")!!.attr("src")
40 |
41 |
42 | val epItems = res.selectFirst(".list-ep")!!.select("a")
43 | // println(epItems)
44 | val map = mutableMapOf()
45 | for (epItem in epItems) {
46 | if (epItem.text().contains("WATCH"))
47 | map["1"] = epItem.selectFirst("a")!!.attr("href")
48 | else
49 | map[epItem.text().replace("Ep ", "")] = epItem.selectFirst("a")!!.attr("href")
50 | }
51 | return AnimeDetails(
52 | title,
53 | desc,
54 | image,
55 | mapOf("Default" to map)
56 | )
57 | }
58 |
59 | override suspend fun searchAnime(searchedText: String): ArrayList {
60 | val url = "$mainUrl?s=$searchedText"
61 | val res = Jsoup.parse(get(url))
62 | val items = res.select(".today-update .item")
63 | val animeList = arrayListOf()
64 | for (item in items) {
65 | animeList.add(
66 | SimpleAnime(
67 | item.selectFirst("img")!!.attr("alt"),
68 | item.selectFirst("img")!!.attr("src"),
69 | item.selectFirst("a")!!.attr("href")
70 | )
71 | )
72 | }
73 | println(animeList.first())
74 | return animeList
75 | }
76 |
77 | override suspend fun latestAnime(): ArrayList {
78 | val res = Jsoup.parse(get())
79 | val items = res.select(".today-update .item")
80 | val animeList = arrayListOf()
81 | for (item in items) {
82 | if (item.selectFirst(".cat").toString().contains("Others")) continue
83 | animeList.add(
84 | SimpleAnime(
85 | item.selectFirst("img")!!.attr("alt"),
86 | item.selectFirst("img")!!.attr("src"),
87 | item.selectFirst("a")!!.attr("href")
88 | )
89 | )
90 | }
91 | println(animeList.first())
92 | return animeList
93 | }
94 |
95 | override suspend fun trendingAnime(): ArrayList {
96 | val res = Jsoup.parse(get())
97 | val items = res.select(".section .list-film .item")
98 | val animeList = arrayListOf()
99 | for (item in items) {
100 | animeList.add(
101 | SimpleAnime(
102 | item.selectFirst("img")!!.attr("alt"),
103 | item.selectFirst("img")!!.attr("src"),
104 | item.selectFirst("a")!!.attr("href")
105 | )
106 | )
107 | }
108 | println(animeList.first())
109 | return animeList
110 | }
111 |
112 | private fun get(url: String = mainUrl): String {
113 | return client.newCall(Request.Builder().url(url).header("referer", mainUrl).build())
114 | .execute().body!!.string()
115 | }
116 |
117 | override suspend fun streamLink(
118 | animeUrl: String,
119 | animeEpCode: String,
120 | extras: List?
121 | ): AnimeStreamLink {
122 | println(animeUrl)
123 | println(animeEpCode) // link
124 | val resDoc = Jsoup.parse(get(animeEpCode))
125 | val link = resDoc.select(".section video source").attr("src")
126 | return AnimeStreamLink(link, "", link.contains(".m3u"))
127 | }
128 |
129 |
130 | }
--------------------------------------------------------------------------------