├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── discord.xml
├── gradle.xml
└── kotlinScripting.xml
├── LICENSE.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── debug
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── ani
│ │ └── saikou
│ │ ├── AnimeFragment.kt
│ │ ├── AnimePageAdapter.kt
│ │ ├── Functions.kt
│ │ ├── GenreActivity.kt
│ │ ├── HomeFragment.kt
│ │ ├── LoginFragment.kt
│ │ ├── MainActivity.kt
│ │ ├── MangaFragment.kt
│ │ ├── MangaPageAdapter.kt
│ │ ├── Network.kt
│ │ ├── NoInternet.kt
│ │ ├── anilist
│ │ ├── Anilist.kt
│ │ ├── AnilistMutations.kt
│ │ ├── AnilistQueries.kt
│ │ ├── AnilistViewModel.kt
│ │ ├── BannerImage.kt
│ │ ├── Genre.kt
│ │ ├── Login.kt
│ │ ├── UrlMedia.kt
│ │ └── api
│ │ │ ├── Character.kt
│ │ │ ├── Data.kt
│ │ │ ├── FuzzyDate.kt
│ │ │ ├── Media.kt
│ │ │ ├── Page.kt
│ │ │ ├── Recommendations.kt
│ │ │ ├── Staff.kt
│ │ │ ├── Studio.kt
│ │ │ └── User.kt
│ │ ├── anime
│ │ ├── Anime.kt
│ │ ├── AnimeSourceAdapter.kt
│ │ ├── AnimeWatchAdapter.kt
│ │ ├── AnimeWatchFragment.kt
│ │ ├── Episode.kt
│ │ ├── EpisodeAdapters.kt
│ │ ├── ExoplayerView.kt
│ │ ├── SelectorDialogFragment.kt
│ │ ├── SubtitleDialogFragment.kt
│ │ └── VideoCache.kt
│ │ ├── manga
│ │ ├── Manga.kt
│ │ ├── MangaChapter.kt
│ │ ├── MangaChapterAdapter.kt
│ │ ├── MangaReadAdapter.kt
│ │ ├── MangaReadFragment.kt
│ │ ├── MangaSourceAdapter.kt
│ │ └── mangareader
│ │ │ ├── BaseImageAdapter.kt
│ │ │ ├── DualPageAdapter.kt
│ │ │ ├── ImageAdapter.kt
│ │ │ ├── MangaReaderActivity.kt
│ │ │ ├── PreloadLinearLayoutManager.kt
│ │ │ ├── ReaderSettingsDialogFragment.kt
│ │ │ └── Swipy.kt
│ │ ├── media
│ │ ├── CalendarActivity.kt
│ │ ├── Character.kt
│ │ ├── CharacterAdapter.kt
│ │ ├── CharacterDetailsActivity.kt
│ │ ├── CharacterDetailsAdapter.kt
│ │ ├── GenreAdapter.kt
│ │ ├── Media.kt
│ │ ├── MediaAdaptor.kt
│ │ ├── MediaDetailsActivity.kt
│ │ ├── MediaDetailsViewModel.kt
│ │ ├── MediaInfoFragment.kt
│ │ ├── MediaListDialogFragment.kt
│ │ ├── MediaListDialogSmallFragment.kt
│ │ ├── OtherDetailsViewModel.kt
│ │ ├── ProgressAdapter.kt
│ │ ├── SearchActivity.kt
│ │ ├── SearchAdapter.kt
│ │ ├── SearchFilterBottomDialog.kt
│ │ ├── Selected.kt
│ │ ├── Source.kt
│ │ ├── SourceAdapter.kt
│ │ ├── SourceSearchDialogFragment.kt
│ │ ├── Studio.kt
│ │ ├── StudioActivity.kt
│ │ └── TitleAdapter.kt
│ │ ├── others
│ │ ├── .gitignore
│ │ ├── AniSkip.kt
│ │ ├── AnimeFillerList.kt
│ │ ├── AppUpdater.kt
│ │ ├── CustomBottomDialog.kt
│ │ ├── DisabledReports.kt
│ │ ├── GlideApp.kt
│ │ ├── Idiosyncrasy.kt
│ │ ├── ImageViewDialog.kt
│ │ ├── Kitsu.kt
│ │ ├── Mal.kt
│ │ ├── MalSyncBackup.kt
│ │ ├── OutlineTextView.kt
│ │ ├── ResettableTimer.kt
│ │ ├── SpoilerPlugin.kt
│ │ └── Xpandable.kt
│ │ ├── parsers
│ │ ├── AnimeParser.kt
│ │ ├── AnimeSources.kt
│ │ ├── BaseParser.kt
│ │ ├── BaseSources.kt
│ │ ├── MangaParser.kt
│ │ ├── MangaSources.kt
│ │ ├── VideoExtractor.kt
│ │ ├── anime
│ │ │ ├── AllAnime.kt
│ │ │ ├── AnimePahe.kt
│ │ │ ├── Animefenix.kt
│ │ │ ├── Animelatinohd.kt
│ │ │ ├── ConsumeBili.kt
│ │ │ ├── Gogo.kt
│ │ │ ├── Haho.kt
│ │ │ ├── Henaojara.kt
│ │ │ ├── HentaiFF.kt
│ │ │ ├── HentaiMama.kt
│ │ │ ├── HentaiStream.kt
│ │ │ ├── Jkanime.kt
│ │ │ ├── Marin.kt
│ │ │ ├── Monoschinos.kt
│ │ │ ├── NineAnime.kt
│ │ │ ├── TioAnime.kt
│ │ │ ├── Zoro.kt
│ │ │ └── extractors
│ │ │ │ ├── FPlayer.kt
│ │ │ │ ├── GogoCDN.kt
│ │ │ │ ├── OK.kt
│ │ │ │ ├── PStream.kt
│ │ │ │ ├── RapidCloud.kt
│ │ │ │ ├── StreamSB.kt
│ │ │ │ ├── StreamTape.kt
│ │ │ │ ├── VideoVard.kt
│ │ │ │ └── VizCloud.kt
│ │ └── manga
│ │ │ ├── AllAnime.kt
│ │ │ ├── ComickFun.kt
│ │ │ ├── Manga4Life.kt
│ │ │ ├── MangaBuddy.kt
│ │ │ ├── MangaDex.kt
│ │ │ ├── MangaHub.kt
│ │ │ ├── MangaKakalot.kt
│ │ │ ├── MangaKatana.kt
│ │ │ ├── MangaPill.kt
│ │ │ ├── MangaRead.kt
│ │ │ ├── MangaReaderTo.kt
│ │ │ ├── Manhwa18.kt
│ │ │ ├── NHentai.kt
│ │ │ ├── NineHentai.kt
│ │ │ └── Toonily.kt
│ │ ├── settings
│ │ ├── CurrentReaderSettings.kt
│ │ ├── Developer.kt
│ │ ├── DevelopersAdapter.kt
│ │ ├── DevelopersDialogFragment.kt
│ │ ├── FAQActivity.kt
│ │ ├── FAQAdapter.kt
│ │ ├── ForksDialogFragment.kt
│ │ ├── PlayerSettings.kt
│ │ ├── PlayerSettingsActivity.kt
│ │ ├── ReaderSettings.kt
│ │ ├── ReaderSettingsActivity.kt
│ │ ├── SettingsActivity.kt
│ │ ├── SettingsDialogFragment.kt
│ │ ├── TVConnectionActivity.kt
│ │ ├── UserInterfaceSettings.kt
│ │ └── UserInterfaceSettingsActivity.kt
│ │ ├── tv
│ │ ├── TVAnimeDetailFragment.kt
│ │ ├── TVAnimeDetailInfoFragment.kt
│ │ ├── TVAnimeFragment.kt
│ │ ├── TVDetailActivity.kt
│ │ ├── TVGridSelectorFragment.kt
│ │ ├── TVMainActivity.kt
│ │ ├── TVMediaPlayer.kt
│ │ ├── TVSearchFragment.kt
│ │ ├── TVSelectorFragment.kt
│ │ ├── TVSourceSelectorFragment.kt
│ │ ├── components
│ │ │ ├── ButtonListRow.kt
│ │ │ ├── CustomListRowPresenter.kt
│ │ │ ├── DetailsOverviewPresenter.kt
│ │ │ ├── HeaderOnlyRow.kt
│ │ │ ├── NonOverlappingFrameLayout.kt
│ │ │ └── SearchFragment.kt
│ │ ├── login
│ │ │ ├── NetworkTVConnection.kt
│ │ │ ├── TVLoginFragment.kt
│ │ │ └── TVNetworkLoginFragment.kt
│ │ ├── presenters
│ │ │ ├── AnimePresenter.kt
│ │ │ ├── ButtonListRowPresenter.kt
│ │ │ ├── CharacterPresenter.kt
│ │ │ ├── DetailActionsPresenter.kt
│ │ │ ├── DetailInfoActionPresenter.kt
│ │ │ ├── DetailsInfoPresenter.kt
│ │ │ ├── DetailsWatchPresenter.kt
│ │ │ ├── EpisodePresenter.kt
│ │ │ ├── GenresPresenter.kt
│ │ │ ├── HeaderRowPresenter.kt
│ │ │ ├── MainHeaderPresenter.kt
│ │ │ └── TagsPresenter.kt
│ │ └── utils
│ │ │ └── VideoPlayerGlue.kt
│ │ └── user
│ │ ├── ListActivity.kt
│ │ ├── ListFragment.kt
│ │ ├── ListViewModel.kt
│ │ └── ListViewPagerAdapter.kt
│ └── res
│ ├── anim
│ └── over_shoot.xml
│ ├── color
│ ├── button_switch_track.xml
│ ├── tab_layout_icon.xml
│ └── tab_layout_text.xml
│ ├── drawable-xhdpi
│ ├── banner.png
│ ├── banner2.png
│ └── saikouflush.png
│ ├── drawable
│ ├── anim_pause_to_play.xml
│ ├── anim_play_to_pause.xml
│ ├── anim_rewind.xml
│ ├── anim_skip.xml
│ ├── anim_splash.xml
│ ├── bottom_nav.xml
│ ├── control_background_40dp.xml
│ ├── ic_anilist.xml
│ ├── ic_baseline_screen_lock_portrait_24.xml
│ ├── ic_bmc_button.xml
│ ├── ic_discord.xml
│ ├── ic_github.xml
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_logo.xml
│ ├── ic_page_numbering.xml
│ ├── ic_round_accessible_forward_24.xml
│ ├── ic_round_add_circle_24.xml
│ ├── ic_round_add_circle_outline_24.xml
│ ├── ic_round_alpha_t_box_24.xml
│ ├── ic_round_amp_stories_24.xml
│ ├── ic_round_animation_24.xml
│ ├── ic_round_arrow_back_ios_new_24.xml
│ ├── ic_round_arrow_drop_down_24.xml
│ ├── ic_round_art_track_24.xml
│ ├── ic_round_audiotrack_24.xml
│ ├── ic_round_auto_awesome_24.xml
│ ├── ic_round_brightness_4_24.xml
│ ├── ic_round_brightness_auto_24.xml
│ ├── ic_round_brightness_high_24.xml
│ ├── ic_round_brightness_medium_24.xml
│ ├── ic_round_calendar_today_24.xml
│ ├── ic_round_cast_24.xml
│ ├── ic_round_close_24.xml
│ ├── ic_round_collections_bookmark_24.xml
│ ├── ic_round_color_24.xml
│ ├── ic_round_date_range_24.xml
│ ├── ic_round_dns_24.xml
│ ├── ic_round_download_24.xml
│ ├── ic_round_fast_forward_24.xml
│ ├── ic_round_fast_rewind_24.xml
│ ├── ic_round_favorite_24.xml
│ ├── ic_round_favorite_border_24.xml
│ ├── ic_round_filter_alt_24.xml
│ ├── ic_round_font_size_24.xml
│ ├── ic_round_format_text_24.xml
│ ├── ic_round_fullscreen_24.xml
│ ├── ic_round_grid_view_24.xml
│ ├── ic_round_heart_broken_24.xml
│ ├── ic_round_help_24.xml
│ ├── ic_round_high_quality_24.xml
│ ├── ic_round_home_24.xml
│ ├── ic_round_import_contacts_24.xml
│ ├── ic_round_info_24.xml
│ ├── ic_round_lock_24.xml
│ ├── ic_round_lock_open_24.xml
│ ├── ic_round_menu_book_24.xml
│ ├── ic_round_movie_filter_24.xml
│ ├── ic_round_new_releases_24.xml
│ ├── ic_round_notifications_active_24.xml
│ ├── ic_round_notifications_none_24.xml
│ ├── ic_round_pause_24.xml
│ ├── ic_round_person_24.xml
│ ├── ic_round_photo_size_select_actual_24.xml
│ ├── ic_round_picture_in_picture_alt_24.xml
│ ├── ic_round_play_arrow_24.xml
│ ├── ic_round_play_circle_24.xml
│ ├── ic_round_play_disabled_24.xml
│ ├── ic_round_playlist_add_24.xml
│ ├── ic_round_playlist_play_24.xml
│ ├── ic_round_reader_settings.xml
│ ├── ic_round_refresh_24.xml
│ ├── ic_round_remove_red_eye_24.xml
│ ├── ic_round_restaurant_24.xml
│ ├── ic_round_screen_rotation_24.xml
│ ├── ic_round_screen_rotation_alt_24.xml
│ ├── ic_round_sd_card_24.xml
│ ├── ic_round_search_24.xml
│ ├── ic_round_settings_24.xml
│ ├── ic_round_share_24.xml
│ ├── ic_round_skip_next_24.xml
│ ├── ic_round_skip_previous_24.xml
│ ├── ic_round_slow_motion_video_24.xml
│ ├── ic_round_smart_button_24.xml
│ ├── ic_round_sort_24.xml
│ ├── ic_round_source_24.xml
│ ├── ic_round_space_bar_24.xml
│ ├── ic_round_star_24.xml
│ ├── ic_round_straighten_24.xml
│ ├── ic_round_subtitles_24.xml
│ ├── ic_round_swipe_down_alt_24.xml
│ ├── ic_round_swipe_up_alt_24.xml
│ ├── ic_round_swipe_vertical_24.xml
│ ├── ic_round_sync_24.xml
│ ├── ic_round_touch_app_24.xml
│ ├── ic_round_translate_variant_24.xml
│ ├── ic_round_video_library_24.xml
│ ├── ic_round_video_settings_24.xml
│ ├── ic_round_view_array_24.xml
│ ├── ic_round_view_column_24.xml
│ ├── ic_round_view_comfy_24.xml
│ ├── ic_round_view_list_24.xml
│ ├── ic_round_volume_up_24.xml
│ ├── ic_skip.xml
│ ├── ic_telegram.xml
│ ├── ic_tvlogin.xml
│ ├── ic_upi_icon.xml
│ ├── item_ongoing.xml
│ ├── item_score.xml
│ ├── item_type.xml
│ ├── item_user_score.xml
│ ├── linear_gradient_bg.xml
│ ├── linear_gradient_black.xml
│ ├── linear_gradient_black_horizontal.xml
│ ├── linear_gradient_nav.xml
│ ├── round_corner.xml
│ ├── rounded_top_nav.xml
│ ├── shape_corner_16dp.xml
│ ├── spinner_icon.xml
│ ├── spinner_icon_manga.xml
│ └── ui_bg.xml
│ ├── font
│ ├── poppins.ttf
│ ├── poppins_bold.ttf
│ ├── poppins_family.xml
│ ├── poppins_semi_bold.ttf
│ └── poppins_thin.ttf
│ ├── layout-land
│ └── activity_media.xml
│ ├── layout
│ ├── activity_character.xml
│ ├── activity_exoplayer.xml
│ ├── activity_faq.xml
│ ├── activity_genre.xml
│ ├── activity_list.xml
│ ├── activity_main.xml
│ ├── activity_manga_reader.xml
│ ├── activity_media.xml
│ ├── activity_no_internet.xml
│ ├── activity_player_settings.xml
│ ├── activity_reader_settings.xml
│ ├── activity_search.xml
│ ├── activity_settings.xml
│ ├── activity_studio.xml
│ ├── activity_user_interface_settings.xml
│ ├── bottom_sheet_current_reader_settings.xml
│ ├── bottom_sheet_custom.xml
│ ├── bottom_sheet_developers.xml
│ ├── bottom_sheet_image.xml
│ ├── bottom_sheet_media_list.xml
│ ├── bottom_sheet_media_list_small.xml
│ ├── bottom_sheet_search_filter.xml
│ ├── bottom_sheet_selector.xml
│ ├── bottom_sheet_settings.xml
│ ├── bottom_sheet_source_search.xml
│ ├── bottom_sheet_subtitles.xml
│ ├── exo_styled_player_control_view.xml
│ ├── exo_styled_player_view.xml
│ ├── fragment_anime.xml
│ ├── fragment_anime_watch.xml
│ ├── fragment_home.xml
│ ├── fragment_list.xml
│ ├── fragment_login.xml
│ ├── fragment_manga.xml
│ ├── fragment_media_info.xml
│ ├── fragment_tv_connection.xml
│ ├── item_anime_page.xml
│ ├── item_anime_watch.xml
│ ├── item_chapter_list.xml
│ ├── item_character.xml
│ ├── item_character_details.xml
│ ├── item_chip.xml
│ ├── item_count_down.xml
│ ├── item_developer.xml
│ ├── item_dropdown.xml
│ ├── item_dual_page.xml
│ ├── item_episode_compact.xml
│ ├── item_episode_grid.xml
│ ├── item_episode_list.xml
│ ├── item_episodes_recyclerview.xml
│ ├── item_genre.xml
│ ├── item_image.xml
│ ├── item_manga_page.xml
│ ├── item_media_compact.xml
│ ├── item_media_large.xml
│ ├── item_media_page.xml
│ ├── item_media_page_small.xml
│ ├── item_progressbar.xml
│ ├── item_quels.xml
│ ├── item_question.xml
│ ├── item_recyclerview.xml
│ ├── item_search_header.xml
│ ├── item_seekbar_dialog.xml
│ ├── item_stream.xml
│ ├── item_subtitle_text.xml
│ ├── item_title.xml
│ ├── item_title_chipgroup.xml
│ ├── item_title_recycler.xml
│ ├── item_title_text.xml
│ ├── item_title_trailer.xml
│ ├── item_url.xml
│ ├── splash_screen.xml
│ ├── tv_activity_main.xml
│ ├── tv_anime_card.xml
│ ├── tv_anime_detail.xml
│ ├── tv_anime_detail_info.xml
│ ├── tv_anime_detail_info_overview.xml
│ ├── tv_button_row_header.xml
│ ├── tv_detail_action.xml
│ ├── tv_detail_activity.xml
│ ├── tv_detail_info_title.xml
│ ├── tv_episode_card.xml
│ ├── tv_genre_card.xml
│ ├── tv_item_character.xml
│ ├── tv_item_chip.xml
│ ├── tv_item_source.xml
│ ├── tv_item_url.xml
│ ├── tv_network_login_fragment.xml
│ ├── tv_progress_row_header.xml
│ ├── tv_row_header.xml
│ └── tv_search_fragment.xml
│ ├── menu
│ ├── anime_menu_detail.xml
│ ├── bottom_navbar_menu.xml
│ ├── list_sort_menu.xml
│ ├── manga_menu_detail.xml
│ └── menu_media.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── 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-es
│ └── strings.xml
│ ├── values-night
│ ├── colors.xml
│ └── themes.xml
│ ├── values
│ ├── attr.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── font_certs.xml
│ ├── preloaded_fonts.xml
│ ├── strings.xml
│ ├── style.xml
│ └── themes.xml
│ └── xml
│ └── provider_paths.xml
├── beta.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
├── stable.md
└── stable.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | build
5 | local.properties
6 | .DS_Store
7 | /captures
8 | .externalNativeBuild
9 | .cxx
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/kotlinScripting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /debug
3 | /debug/output-metadata.json
4 | /release
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "71148296899",
4 | "project_id": "saikoutv-f0a81",
5 | "storage_bucket": "saikoutv-f0a81.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:71148296899:android:10c3ede77ee5e43aafa076",
11 | "android_client_info": {
12 | "package_name": "ani.saikou"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "71148296899-e8kl3j2ieb8b3f2fl95e44bl2ae3srdl.apps.googleusercontent.com",
18 | "client_type": 1,
19 | "android_info": {
20 | "package_name": "ani.saikou",
21 | "certificate_hash": "cd5db26708a5ee22d4e740010f77a70dcb0a8519"
22 | }
23 | },
24 | {
25 | "client_id": "71148296899-fk1jokciegdamvjbv97e5tbj5fpe8fal.apps.googleusercontent.com",
26 | "client_type": 1,
27 | "android_info": {
28 | "package_name": "ani.saikou",
29 | "certificate_hash": "71bc71da9b878fb691da817070c610784b863409"
30 | }
31 | },
32 | {
33 | "client_id": "71148296899-3tk4josah9mb0h32u6i276lka6hsbjio.apps.googleusercontent.com",
34 | "client_type": 3
35 | }
36 | ],
37 | "api_key": [
38 | {
39 | "current_key": "AIzaSyBN7D-3RArG5bQ-HEzrEP_JOcHqFs0sk9g"
40 | }
41 | ],
42 | "services": {
43 | "appinvite_service": {
44 | "other_platform_oauth_client": [
45 | {
46 | "client_id": "71148296899-3tk4josah9mb0h32u6i276lka6hsbjio.apps.googleusercontent.com",
47 | "client_type": 3
48 | }
49 | ]
50 | }
51 | }
52 | }
53 | ],
54 | "configuration_version": "1"
55 | }
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Saikou β
4 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nanoc6/SaikouTV/6a96beb7a4443353ab5cc0b1b4ac5c0de5e74b9e/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/LoginFragment.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import ani.saikou.anilist.Anilist
9 | import ani.saikou.databinding.FragmentLoginBinding
10 |
11 | class LoginFragment : Fragment() {
12 |
13 | private var _binding: FragmentLoginBinding? = null
14 | private val binding get() = _binding!!
15 |
16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
17 | _binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
18 | return binding.root
19 | }
20 |
21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
22 | binding.loginButton.setOnClickListener { Anilist.loginIntent(requireActivity()) }
23 | binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
24 | binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
25 | binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/NoInternet.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou
2 |
3 | import android.os.Bundle
4 | import android.view.ViewGroup
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.view.updateLayoutParams
7 | import ani.saikou.databinding.ActivityNoInternetBinding
8 |
9 | class NoInternet : AppCompatActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | val binding = ActivityNoInternetBinding.inflate(layoutInflater)
14 | setContentView(binding.root)
15 |
16 | binding.refreshContainer.updateLayoutParams {
17 | topMargin = statusBarHeight
18 | bottomMargin = navBarHeight
19 | }
20 | binding.refreshButton.setOnClickListener {
21 | if (isOnline(this)) {
22 | startMainActivity(this)
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/BannerImage.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist
2 |
3 | import java.io.Serializable
4 |
5 | data class BannerImage(
6 | val url: String?,
7 | var time: Long,
8 | ) : Serializable {
9 | fun checkTime(): Boolean {
10 | return (System.currentTimeMillis() - time) >= (1000 * 60 * 60 * 6)
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/Genre.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist
2 |
3 | import java.io.Serializable
4 |
5 | data class Genre(
6 | val name: String,
7 | var id: Int,
8 | var thumbnail: String,
9 | var time: Long,
10 | ) : Serializable
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/Login.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import androidx.appcompat.app.AppCompatActivity
7 | import ani.saikou.logger
8 | import ani.saikou.logError
9 | import ani.saikou.startMainActivity
10 |
11 | class Login : AppCompatActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | val data: Uri? = intent?.data
15 | logger(data.toString())
16 | try {
17 | Anilist.token = Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
18 | val filename = "anilistToken"
19 | this.openFileOutput(filename, Context.MODE_PRIVATE).use {
20 | it.write(Anilist.token!!.toByteArray())
21 | }
22 | } catch (e: Exception) {
23 | logError(e)
24 | }
25 | startMainActivity(this)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/UrlMedia.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist
2 |
3 | import android.net.Uri
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import ani.saikou.loadIsMAL
7 | import ani.saikou.loadMedia
8 | import ani.saikou.logError
9 | import ani.saikou.startMainActivity
10 |
11 | class UrlMedia : AppCompatActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | val data: Uri? = intent?.data
15 | if (data?.host != "anilist.co") loadIsMAL = true
16 | try {
17 | if (data?.pathSegments?.get(1) != null) loadMedia = data.pathSegments?.get(1)?.toIntOrNull()
18 | } catch (e: Exception) {
19 | logError(e)
20 | }
21 | startMainActivity(this)
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/api/FuzzyDate.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist.api
2 |
3 | import kotlinx.serialization.SerialName
4 | import java.io.Serializable
5 | import java.text.DateFormatSymbols
6 | import java.util.*
7 |
8 | @kotlinx.serialization.Serializable
9 | data class FuzzyDate(
10 | @SerialName("year") val year: Int? = null,
11 | @SerialName("month") val month: Int? = null,
12 | @SerialName("day") val day: Int? = null,
13 | ) : Serializable {
14 | override fun toString(): String {
15 | if(day==null && year==null && month==null)
16 | return "??"
17 | val a = if (month != null) DateFormatSymbols().months[month - 1] else ""
18 | return (if (day != null) "$day " else "") + a + (if (year != null) ", $year" else "")
19 | }
20 |
21 | fun getToday(): FuzzyDate {
22 | val cal = Calendar.getInstance()
23 | return FuzzyDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH))
24 | }
25 |
26 | fun toVariableString(): String {
27 | return ("{"
28 | + (if (year != null) "year:$year" else "")
29 | + (if (month != null) ",month:$month" else "")
30 | + (if (day != null) ",day:$day" else "")
31 | + "}")
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/api/Recommendations.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist.api
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | @Serializable
6 | data class Recommendation(
7 | // The id of the recommendation
8 | @SerialName("id") var id: Int?,
9 |
10 | // Users rating of the recommendation
11 | @SerialName("rating") var rating: Int?,
12 |
13 | // The rating of the recommendation by currently authenticated user
14 | // @SerialName("userRating") var userRating: RecommendationRating?,
15 |
16 | // The media the recommendation is from
17 | @SerialName("media") var media: Media?,
18 |
19 | // The recommended media
20 | @SerialName("mediaRecommendation") var mediaRecommendation: Media?,
21 |
22 | // The user that first created the recommendation
23 | @SerialName("user") var user: User?,
24 | )
25 | @Serializable
26 | data class RecommendationConnection(
27 | //@SerialName("edges") var edges: List?,
28 |
29 | @SerialName("nodes") var nodes: List?,
30 |
31 | // The pagination information
32 | //@SerialName("pageInfo") var pageInfo: PageInfo?,
33 |
34 | )
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anilist/api/Studio.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anilist.api
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Studio(
8 | // The id of the studio
9 | @SerialName("id") var id: Int,
10 |
11 | // The name of the studio
12 | // Originally non-nullable, needs to be nullable due to it not being always queried
13 | @SerialName("name") var name: String?,
14 |
15 | // If the studio is an animation studio or a different kind of company
16 | @SerialName("isAnimationStudio") var isAnimationStudio: Boolean?,
17 |
18 | // The media the studio has worked on
19 | @SerialName("media") var media: MediaConnection?,
20 |
21 | // The url for the studio page on the AniList website
22 | @SerialName("siteUrl") var siteUrl: String?,
23 |
24 | // If the studio is marked as favourite by the currently authenticated user
25 | @SerialName("isFavourite") var isFavourite: Boolean?,
26 |
27 | // The amount of user's who have favourited the studio
28 | @SerialName("favourites") var favourites: Int?,
29 | )
30 |
31 | @Serializable
32 | data class StudioConnection(
33 | //@SerialName("edges") var edges: List?,
34 |
35 | @SerialName("nodes") var nodes: List?,
36 |
37 | // The pagination information
38 | //@SerialName("pageInfo") var pageInfo: PageInfo?,
39 | )
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anime/Anime.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anime
2 |
3 | import ani.saikou.media.Studio
4 | import java.io.Serializable
5 |
6 | data class Anime(
7 | var totalEpisodes: Int? = null,
8 |
9 | var episodeDuration: Int? = null,
10 | var season: String? = null,
11 | var seasonYear: Int? = null,
12 |
13 | var op: ArrayList = arrayListOf(),
14 | var ed: ArrayList = arrayListOf(),
15 |
16 | var mainStudio: Studio? = null,
17 |
18 | var youtube: String? = null,
19 | var nextAiringEpisode: Int? = null,
20 | var nextAiringEpisodeTime: Long? = null,
21 |
22 | var selectedEpisode: String? = null,
23 | var episodes: MutableMap? = null,
24 | var slug: String? = null,
25 | var kitsuEpisodes: Map? = null,
26 | var fillerEpisodes: Map? = null,
27 | ) : Serializable
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anime/AnimeSourceAdapter.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anime
2 |
3 | import ani.saikou.media.MediaDetailsViewModel
4 | import ani.saikou.media.SourceAdapter
5 | import ani.saikou.media.SourceSearchDialogFragment
6 | import ani.saikou.parsers.ShowResponse
7 | import kotlinx.coroutines.CoroutineScope
8 |
9 | class AnimeSourceAdapter(
10 | sources: List,
11 | val model: MediaDetailsViewModel,
12 | val i: Int,
13 | val id: Int,
14 | fragment: SourceSearchDialogFragment,
15 | scope: CoroutineScope
16 | ) : SourceAdapter(sources, fragment, scope) {
17 |
18 | override suspend fun onItemClick(source: ShowResponse) {
19 | model.overrideEpisodes(i, source, id)
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anime/Episode.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anime
2 |
3 | import ani.saikou.FileUrl
4 | import ani.saikou.parsers.VideoExtractor
5 | import java.io.Serializable
6 |
7 | data class Episode(
8 | val number: String,
9 | var link: String? = null,
10 | var title: String? = null,
11 | var desc: String? = null,
12 | var thumb: FileUrl? = null,
13 | var filler: Boolean = false,
14 | var selectedExtractor: String? = null,
15 | var selectedVideo: Int = 0,
16 | var selectedSubtitle: Int? = -1,
17 | var extractors: MutableList?=null,
18 | @Transient var extractorCallback: ((VideoExtractor) -> Unit)?=null,
19 | var allStreams: Boolean = false,
20 | var watched: Long? = null,
21 | var maxLength: Long? = null,
22 | val extra: Any?=null,
23 |
24 | ) : Serializable
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/anime/VideoCache.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.anime
2 |
3 | import android.content.Context
4 | import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
5 | import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
6 | import com.google.android.exoplayer2.upstream.cache.SimpleCache
7 | import java.io.File
8 |
9 | object VideoCache {
10 | private var simpleCache: SimpleCache? = null
11 | fun getInstance(context: Context): SimpleCache {
12 | val databaseProvider = StandaloneDatabaseProvider(context)
13 | if (simpleCache == null)
14 | simpleCache = SimpleCache(
15 | File(context.cacheDir, "exoplayer").also { it.deleteOnExit() }, // Ensures always fresh file
16 | LeastRecentlyUsedCacheEvictor(300L * 1024L * 1024L),
17 | databaseProvider
18 | )
19 | return simpleCache as SimpleCache
20 | }
21 |
22 | fun release() {
23 | simpleCache?.release()
24 | simpleCache = null
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/manga/Manga.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.manga
2 |
3 | import java.io.Serializable
4 |
5 | data class Manga(
6 | var totalChapters: Int? = null,
7 | var selectedChapter: String? = null,
8 | var chapters: MutableMap? = null,
9 | var slug: String? = null,
10 | var author: String?=null,
11 | ) : Serializable
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/manga/MangaChapter.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.manga
2 |
3 | import ani.saikou.parsers.MangaChapter
4 | import ani.saikou.parsers.MangaImage
5 | import java.io.Serializable
6 | import kotlin.math.ceil
7 |
8 | data class MangaChapter(
9 | val number: String,
10 | var link: String,
11 | var title: String? = null,
12 | var description: String? = null,
13 | var images: List? = null
14 | ) : Serializable {
15 | constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description)
16 |
17 | private var dualPage: List>? = null
18 | fun dualPages(): List> {
19 | dualPage = dualPage ?: (0..ceil((images!!.size.toFloat() - 1f) / 2).toInt()).map {
20 | val i = it * 2
21 | (images?.getOrNull(i) to images?.getOrNull(i + 1))
22 | }
23 | return dualPage!!
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/manga/MangaSourceAdapter.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.manga
2 |
3 | import ani.saikou.media.MediaDetailsViewModel
4 | import ani.saikou.media.SourceAdapter
5 | import ani.saikou.media.SourceSearchDialogFragment
6 | import ani.saikou.parsers.ShowResponse
7 | import kotlinx.coroutines.CoroutineScope
8 |
9 | class MangaSourceAdapter(
10 | sources: List,
11 | val model: MediaDetailsViewModel,
12 | val i: Int,
13 | val id: Int,
14 | fragment: SourceSearchDialogFragment,
15 | scope: CoroutineScope
16 | ) : SourceAdapter(sources, fragment, scope) {
17 | override suspend fun onItemClick(source: ShowResponse) {
18 | model.overrideMangaChapters(i, source, id)
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/Character.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import ani.saikou.anilist.api.FuzzyDate
4 | import java.io.Serializable
5 |
6 | data class Character(
7 | val id: Int,
8 | val name: String?,
9 | val image: String?,
10 | val banner: String?,
11 | val role: String,
12 |
13 | var description: String? = null,
14 | var age: String? = null,
15 | var gender: String? = null,
16 | var dateOfBirth: FuzzyDate? = null,
17 | var roles: ArrayList? = null
18 | ) : Serializable
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/CharacterDetailsAdapter.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.view.LayoutInflater
6 | import android.view.ViewGroup
7 | import androidx.recyclerview.widget.RecyclerView
8 | import ani.saikou.databinding.ItemCharacterDetailsBinding
9 | import ani.saikou.others.SpoilerPlugin
10 | import io.noties.markwon.Markwon
11 | import io.noties.markwon.SoftBreakAddsNewLinePlugin
12 |
13 | class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
14 | RecyclerView.Adapter() {
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenreViewHolder {
16 | val binding = ItemCharacterDetailsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
17 | return GenreViewHolder(binding)
18 | }
19 |
20 | @SuppressLint("SetTextI18n")
21 | override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
22 | val binding = holder.binding
23 | val desc =
24 | (if (character.age != "null") "__Age:__ " + character.age else "") +
25 | (if (character.dateOfBirth.toString() != "") "\n__Birthday:__ " + character.dateOfBirth.toString() else "") +
26 | (if (character.gender != "null") "\n__Gender:__ " + character.gender else "") + "\n" + character.description
27 |
28 | binding.characterDesc.isTextSelectable
29 | val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create()).usePlugin(SpoilerPlugin()).build()
30 | markWon.setMarkdown(binding.characterDesc, desc)
31 |
32 | }
33 |
34 | override fun getItemCount(): Int = 1
35 | inner class GenreViewHolder(val binding: ItemCharacterDetailsBinding) : RecyclerView.ViewHolder(binding.root)
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/Selected.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import java.io.Serializable
4 |
5 | data class Selected(
6 | var window: Int = 0,
7 | var recyclerStyle: Int? = null,
8 | var recyclerReversed: Boolean = false,
9 | var chip: Int = 0,
10 | var source: Int = 0,
11 | var preferDub: Boolean = false,
12 | var server: String? = null,
13 | var video: Int = 0,
14 | ) : Serializable
15 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/Source.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import java.io.Serializable
4 |
5 | data class Source(
6 | val link: String,
7 | val name: String,
8 | val cover: String,
9 | val headers: MutableMap? = null
10 | ) : Serializable
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/Studio.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import java.io.Serializable
4 |
5 | data class Studio(
6 | val id: String,
7 | val name: String,
8 | var yearMedia: MutableMap>? = null
9 | ) : Serializable
10 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/media/TitleAdapter.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.media
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import ani.saikou.databinding.ItemTitleBinding
7 |
8 | class TitleAdapter(private val text: String) : RecyclerView.Adapter() {
9 | inner class TitleViewHolder(val binding: ItemTitleBinding) : RecyclerView.ViewHolder(binding.root)
10 |
11 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TitleViewHolder {
12 | val binding = ItemTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
13 | return TitleViewHolder(binding)
14 | }
15 |
16 | override fun onBindViewHolder(holder: TitleViewHolder, position: Int) {
17 | holder.binding.itemTitle.text = text
18 | }
19 |
20 | override fun getItemCount(): Int = 1
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/.gitignore:
--------------------------------------------------------------------------------
1 | DisabledReports.kt
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/AnimeFillerList.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.others
2 |
3 | import ani.saikou.anime.Episode
4 | import ani.saikou.client
5 | import ani.saikou.tryWithSuspend
6 | import kotlinx.serialization.SerialName
7 | import kotlinx.serialization.Serializable
8 |
9 | object AnimeFillerList {
10 | suspend fun getFillers(malId: Int): Map? {
11 | return tryWithSuspend {
12 | val json = client.get("https://raw.githubusercontent.com/saikou-app/mal-id-filler-list/main/fillers/$malId.json")
13 | return@tryWithSuspend if (json.text != "404: Not Found") json.parsed().episodes?.associate {
14 | val num = it.number.toString()
15 | num to Episode(
16 | num,
17 | it.title,
18 | filler = it.fillerBool == true
19 | )
20 | } else null
21 | }
22 | }
23 |
24 | @Serializable
25 | data class AnimeFillerListValue (
26 | @SerialName("MAL-id")
27 | val malID: Int? = null,
28 |
29 | @SerialName("Anilist-id")
30 | val anilistID: Int? = null,
31 |
32 | val episodes: List? = null
33 | )
34 |
35 | @Serializable
36 | data class AFLEpisode (
37 | val number: Int? = null,
38 | val title: String? = null,
39 | val desc: String? = null,
40 | val filler: String? = null,
41 | @SerialName("filler-bool") val fillerBool : Boolean?=null,
42 | val airDate: String? = null
43 | )
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/DisabledReports.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.others
2 |
3 | const val DisabledReports=true
4 | //Setting this to false, will allow sending crash reports to Saikou's Firebase Crashlytics
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/GlideApp.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.others
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import ani.saikou.okHttpClient
6 | import com.bumptech.glide.Glide
7 | import com.bumptech.glide.GlideBuilder
8 | import com.bumptech.glide.Registry
9 | import com.bumptech.glide.annotation.GlideModule
10 | import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
11 | import com.bumptech.glide.load.engine.DiskCacheStrategy
12 | import com.bumptech.glide.load.model.GlideUrl
13 | import com.bumptech.glide.module.AppGlideModule
14 | import com.bumptech.glide.request.RequestOptions
15 | import com.bumptech.glide.signature.ObjectKey
16 | import java.io.InputStream
17 |
18 | @GlideModule
19 | class SaikouGlideApp : AppGlideModule(){
20 | @SuppressLint("CheckResult")
21 | override fun applyOptions(context: Context, builder: GlideBuilder) {
22 | super.applyOptions(context, builder)
23 | builder.apply {
24 | RequestOptions()
25 | .diskCacheStrategy(DiskCacheStrategy.ALL)
26 | .signature(ObjectKey(System.currentTimeMillis().toShort()))
27 | }
28 | }
29 |
30 | // Needed for DOH
31 | // https://stackoverflow.com/a/61634041
32 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
33 | registry.replace(
34 | GlideUrl::class.java,
35 | InputStream::class.java,
36 | OkHttpUrlLoader.Factory(okHttpClient)
37 | )
38 | super.registerComponents(context, glide, registry)
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/Idiosyncrasy.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UNCHECKED_CAST", "DEPRECATION")
2 |
3 | package ani.saikou.others
4 |
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.Bundle
8 | import java.io.Serializable
9 |
10 | inline fun Bundle.getSerialized(key: String): T? {
11 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
12 | this.getSerializable(key, T::class.java)
13 | else
14 | this.getSerializable(key) as? T
15 | }
16 |
17 | inline fun Intent.getSerialized(key: String): T? {
18 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
19 | this.getSerializableExtra(key, T::class.java)
20 | else
21 | this.getSerializableExtra(key) as? T
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/MalSyncBackup.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.others
2 |
3 | import ani.saikou.client
4 | import ani.saikou.parsers.ShowResponse
5 | import ani.saikou.tryWithSuspend
6 | import kotlinx.serialization.SerialName
7 | import kotlinx.serialization.Serializable
8 |
9 | object MalSyncBackup {
10 | @Serializable
11 | data class MalBackUpSync(
12 | @SerialName("Pages") val pages: Map>? = null
13 | )
14 |
15 | @Serializable
16 | data class Page(
17 | val identifier: String,
18 | val title: String,
19 | val url: String? = null,
20 | val image: String? = null,
21 | val active: Boolean? = null,
22 | )
23 |
24 | suspend fun get(id: Int, name: String, dub: Boolean = false): ShowResponse? {
25 | return tryWithSuspend {
26 | val json =
27 | client.get("https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/anilist/anime/$id.json")
28 | if (json.text != "404: Not Found")
29 | json.parsed().pages?.get(name)?.forEach {
30 | val page = it.value
31 | val isDub = page.title.lowercase().replace(" ", "").endsWith("(dub)")
32 | val slug = if (dub == isDub) page.identifier else null
33 | if (slug != null && page.active == true && page.url != null) {
34 | val url = when(name){
35 | "Gogoanime" -> slug
36 | "Tenshi" -> slug
37 | else -> page.url
38 | }
39 | return@tryWithSuspend ShowResponse(page.title, url, page.image ?: "")
40 | }
41 | }
42 | return@tryWithSuspend null
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/others/ResettableTimer.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.others
2 |
3 | import java.util.*
4 | import java.util.concurrent.atomic.*
5 |
6 | class ResettableTimer {
7 | var resetLock = AtomicBoolean(false)
8 | var timer = Timer()
9 | fun reset(timerTask: TimerTask, delay: Long) {
10 | if (!resetLock.getAndSet(true)) {
11 | timer.cancel()
12 | timer.purge()
13 | timer = Timer()
14 | timer.schedule(object : TimerTask() {
15 | override fun run() {
16 | if (!resetLock.getAndSet(true)) {
17 | timerTask.run()
18 | timer.cancel()
19 | timer.purge()
20 | resetLock.set(false)
21 | }
22 | }
23 | }, delay)
24 | resetLock.set(false)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/parsers/AnimeSources.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.parsers
2 |
3 | import ani.saikou.Lazier
4 | import ani.saikou.lazyList
5 | import ani.saikou.parsers.anime.*
6 |
7 | object AnimeSources : WatchSources() {
8 | override val list: List> = lazyList(
9 | "AllAnime" to ::AllAnime,
10 | "Gogo" to ::Gogo,
11 | "Zoro" to ::Zoro,
12 | "Marin" to ::Marin,
13 | "AnimePahe" to ::AnimePahe,
14 | "Monoschinos" to ::Monoschinos,
15 | "Animefenix" to ::Animefenix,
16 | "Jkanime" to ::Jkanime,
17 | "AnimeLatinoHD" to ::Animelatinohd,
18 | "Animeflv" to ::TioAnime,
19 | "ConsumeBili" to ::ConsumeBili
20 | )
21 | }
22 |
23 | object HAnimeSources : WatchSources() {
24 | val aList: List> = lazyList(
25 | "HentaiMama" to ::HentaiMama,
26 | "Haho" to ::Haho,
27 | "HentaiStream" to ::HentaiStream,
28 | "HentaiFF" to ::HentaiFF,
29 | "Monoschinos" to ::Monoschinos,
30 | "Animefenix" to ::Animefenix,
31 | "Jkanime" to ::Jkanime,
32 | )
33 |
34 | override val list = listOf(aList,AnimeSources.list).flatten()
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/parsers/MangaSources.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.parsers
2 |
3 | import ani.saikou.Lazier
4 | import ani.saikou.lazyList
5 | import ani.saikou.parsers.manga.*
6 |
7 | object MangaSources : MangaReadSources() {
8 | override val list: List> = lazyList(
9 | "MangaKakalot" to ::MangaKakalot,
10 | "MangaBuddy" to ::MangaBuddy,
11 | "MangaPill" to ::MangaPill,
12 | "MangaDex" to ::MangaDex,
13 | "MangaReaderTo" to ::MangaReaderTo,
14 | "AllAnime" to ::AllAnime,
15 | "Toonily" to ::Toonily,
16 | // "MangaHub" to ::MangaHub,
17 | "MangaKatana" to ::MangaKatana,
18 | "Manga4Life" to ::Manga4Life,
19 | "MangaRead" to ::MangaRead,
20 | "ComickFun" to ::ComickFun,
21 | )
22 | }
23 |
24 | object HMangaSources : MangaReadSources() {
25 | val aList: List> = lazyList(
26 | "NineHentai" to ::NineHentai,
27 | "Manhwa18" to ::Manhwa18,
28 | "NHentai" to ::NHentai,
29 | )
30 | override val list = listOf(aList,MangaSources.list).flatten()
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/parsers/anime/HentaiStream.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.parsers.anime
2 |
3 | class HentaiStream: HentaiFF() {
4 | override val name: String = "Hentai Stream"
5 | override val saveName: String = "hentai_stream"
6 | override val hostUrl = "https://hentaistream.moe"
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/parsers/anime/extractors/FPlayer.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.parsers.anime.extractors
2 |
3 | import ani.saikou.asyncMap
4 | import ani.saikou.client
5 | import ani.saikou.getSize
6 | import ani.saikou.parsers.*
7 | import kotlinx.serialization.Serializable
8 |
9 | class FPlayer(override val server: VideoServer) : VideoExtractor() {
10 |
11 | override suspend fun extract(): VideoContainer {
12 | val url = server.embed.url
13 | val apiLink = url.replace("/v/", "/api/source/")
14 | try {
15 | val json = client.post(apiLink, referer = url).parsed()
16 | if (json.success) {
17 | return VideoContainer(json.data?.asyncMap {
18 | Video(
19 | it.label.replace("p", "").toIntOrNull(),
20 | VideoType.CONTAINER,
21 | it.file,
22 | getSize(it.file)
23 | )
24 | }?: listOf())
25 | }
26 |
27 | } catch (e: Exception) {}
28 | return VideoContainer(listOf())
29 | }
30 |
31 | @Serializable
32 | private data class Data(
33 | val file: String,
34 | val label: String
35 | )
36 |
37 | @Serializable
38 | private data class Json(
39 | val success: Boolean,
40 | val data: List?
41 | )
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/ani/saikou/parsers/anime/extractors/PStream.kt:
--------------------------------------------------------------------------------
1 | package ani.saikou.parsers.anime.extractors
2 |
3 | import android.util.Base64
4 | import ani.saikou.FileUrl
5 | import ani.saikou.Mapper
6 | import ani.saikou.client
7 | import ani.saikou.findBetween
8 | import ani.saikou.parsers.*
9 | import kotlinx.serialization.json.JsonObject
10 | import kotlinx.serialization.json.jsonPrimitive
11 |
12 | class PStream(override val server: VideoServer) : VideoExtractor() {
13 |
14 | val link = "https://www.pstream.net/u/player-script"
15 | val regex = Regex("""\)\)\}\("(.+)"\),n=.*file:.\.(.+),tracks:""")
16 |
17 | val headers = mapOf(
18 | "accept" to "*/*",
19 | "content-type" to "application/json",
20 | "accept-language" to "*/*"
21 | )
22 |
23 | override suspend fun extract(): VideoContainer {
24 | val res = client.get(server.embed.url, headers).text
25 | val jslink = res.findBetween("