├── .gitattributes ├── CRD ├── Assets │ ├── app_icon.ico │ ├── coming_soon_ep.jpg │ ├── sonarr.png │ ├── sonarr_inactive.png │ ├── crunchy_icon_round.png │ └── Icons.axaml ├── Utils │ ├── Parser │ │ ├── Utils │ │ │ ├── UrlResolver.cs │ │ │ ├── ManifestInfo.cs │ │ │ ├── DivisionValueParser.cs │ │ │ ├── XMLUtils.cs │ │ │ ├── UrlUtils.cs │ │ │ ├── DurationParser.cs │ │ │ └── ObjectUtilities.cs │ │ ├── Playlists │ │ │ ├── Errors.cs │ │ │ ├── ToPlaylistsClass.cs │ │ │ └── PlaylistMerge.cs │ │ ├── Segments │ │ │ ├── UrlType.cs │ │ │ ├── TimelineTimeParser.cs │ │ │ ├── SegmentList.cs │ │ │ ├── DurationTimeParser.cs │ │ │ ├── SegmentBase.cs │ │ │ └── SegmentTemplate.cs │ │ └── DashParser.cs │ ├── Sonarr │ │ └── Models │ │ │ ├── SonarrQualityProfile.cs │ │ │ ├── SonarrImage.cs │ │ │ ├── SonarrSeason.cs │ │ │ ├── SonarrStatistics.cs │ │ │ └── SonarrEpisode.cs │ ├── CustomList │ │ └── RefreshableObservableCollection.cs │ ├── Structs │ │ ├── Crunchyroll │ │ │ ├── Series │ │ │ │ ├── CrSearchSeries.cs │ │ │ │ ├── CrSeriesBase.cs │ │ │ │ ├── CrSeriesSearch.cs │ │ │ │ └── CrBrowseSeries.cs │ │ │ ├── CrToken.cs │ │ │ ├── Music │ │ │ │ └── CrArtist.cs │ │ │ ├── CrunchyStreamData.cs │ │ │ ├── StreamLimits.cs │ │ │ ├── Playback.cs │ │ │ ├── CrMovie.cs │ │ │ └── CrProfile.cs │ │ ├── Variable.cs │ │ ├── History │ │ │ ├── SeriesDataCache.cs │ │ │ └── IHistorySource.cs │ │ ├── AnilistResponse.cs │ │ ├── Chapters.cs │ │ ├── CalendarStructs.cs │ │ └── AnilistUpcoming.cs │ ├── UI │ │ ├── UiEmptyToDefaultConverter.cs │ │ ├── UiSeasonValueConverter.cs │ │ ├── UiIntToVisibilityConverter.cs │ │ ├── UiValueConverter.cs │ │ ├── UiListToStringConverter.cs │ │ ├── UiSeriesSeasonConverter.cs │ │ ├── UiSonarrIdToVisibilityConverter.cs │ │ ├── UiEnumToBoolConverter.cs │ │ ├── UiListHasElementsConverter.cs │ │ ├── UiValueConverterCalendarBackground.cs │ │ └── CustomContentDialog.cs │ ├── AudioPlayer.cs │ ├── DRM │ │ ├── CryptoUtils.cs │ │ ├── ContentKey.cs │ │ ├── PSSHbox.cs │ │ └── Protocol.cs │ ├── JsonConv │ │ ├── UtcToLocalTimeConverter.cs │ │ └── LocaleConverter.cs │ ├── Http │ │ └── ChallengeDetector.cs │ ├── HLS │ │ └── ThrottledStream.cs │ └── Ffmpeg Encoding │ │ └── FfmpegEncoding.cs ├── Views │ ├── UpdateView.axaml.cs │ ├── AccountPageView.axaml.cs │ ├── CalendarPageView.axaml.cs │ ├── SeriesPageView.axaml.cs │ ├── Utils │ │ ├── GeneralSettingsView.axaml.cs │ │ ├── ContentDialogInputLoginView.axaml.cs │ │ ├── ContentDialogSonarrMatchView.axaml.cs │ │ ├── ContentDialogFeaturedMusicView.axaml.cs │ │ ├── ContentDialogDropdownSelectView.axaml.cs │ │ ├── ContentDialogEncodingPresetView.axaml.cs │ │ ├── ContentDialogSonarrMatchEpisodeView.axaml.cs │ │ ├── ContentDialogInputLoginView.axaml │ │ ├── ContentDialogSeriesDetailsView.axaml.cs │ │ └── ContentDialogDropdownSelectView.axaml │ ├── DownloadsPageView.axaml.cs │ ├── SettingsPageView.axaml.cs │ ├── HistoryPageView.axaml.cs │ ├── AddDownloadPageView.axaml.cs │ ├── UpcomingSeasonsPageView.axaml.cs │ ├── ToastNotification.axaml │ ├── AccountPageView.axaml │ ├── ToastNotification.axaml.cs │ ├── SettingsPageView.axaml │ ├── UpdateView.axaml │ └── MainWindow.axaml ├── ViewModels │ ├── MainWindowViewModel.cs │ ├── ViewModelBase.cs │ ├── Utils │ │ ├── ContentDialogDropdownSelectViewModel.cs │ │ ├── ContentDialogInputLoginViewModel.cs │ │ ├── ContentDialogSonarrMatchViewModel.cs │ │ ├── ContentDialogFeaturedMusicViewModel.cs │ │ ├── ContentDialogSonarrMatchEpisodeViewModel.cs │ │ └── ContentDialogSeriesDetailsViewModel.cs │ └── SettingsPageViewModel.cs ├── ViewLocator.cs ├── Downloader │ └── Crunchyroll │ │ ├── Views │ │ └── CrunchyrollSettingsView.axaml.cs │ │ └── CrMovies.cs ├── App.axaml.cs ├── app.manifest ├── Program.cs ├── App.axaml └── Styling │ └── ControlsGalleryStyles.axaml ├── images ├── Calendar.png ├── Settings.png ├── Add_Downloads.png ├── Settings_CR.png ├── Settings_Debug.png ├── Settings_Proxy.png ├── Calendar_Custom.png ├── Download_Queue.png ├── History_Overview.png ├── Settings_Appearance.png ├── Settings_Download.png ├── Settings_Filename.png ├── Settings_History.png ├── Settings_Muxing.png ├── Settings_Search_CR.png ├── Settings_Softsubs.png ├── Settings_Sonarr.png ├── Calendar_Custom_Settings.png ├── History_Overview_Table.png ├── History_Series_Overview.png ├── Settings_Download_CR.png ├── Add_Downloads_Search_Enabled.png ├── Add_Downloads_Search_Results.png ├── Calendar_Simulcast_Settings.png └── Settings_Muxing_New_Encoding_Preset.png ├── LICENSE ├── .github └── ISSUE_TEMPLATE │ ├── -bug-.md │ ├── -feature-.md │ └── other.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /CRD/Assets/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crunchy-DL/Crunchy-Downloader/HEAD/CRD/Assets/app_icon.ico -------------------------------------------------------------------------------- /CRD/Assets/coming_soon_ep.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crunchy-DL/Crunchy-Downloader/HEAD/CRD/Assets/coming_soon_ep.jpg -------------------------------------------------------------------------------- /CRD/Utils/Parser/Utils/UrlResolver.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.Parser.Utils; 2 | 3 | public class UrlResolver{ 4 | 5 | } -------------------------------------------------------------------------------- /CRD/Assets/sonarr.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c74a16f9cf6d20af4301293f5c4103ab5c322ee53c1b4e3027c457380fd8cee9 3 | size 13245 4 | -------------------------------------------------------------------------------- /images/Calendar.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fa26297bca30c191ff2942dd85765f06c7b507a0323114a9616e28a1a4212436 3 | size 2512751 4 | -------------------------------------------------------------------------------- /images/Settings.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7fb2b48e9158c7561806c332feeb35969ec4d7e87d8c8f8497829fdcf2e728bd 3 | size 1330840 4 | -------------------------------------------------------------------------------- /images/Add_Downloads.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:be461a8a8f83d1c983f263ae555d813c97238a394481775c5437c3fd5ee8b70e 3 | size 1734367 4 | -------------------------------------------------------------------------------- /images/Settings_CR.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:735c0443f30df52255870e918c151ad09c9f1c4b126ba209d9412af923f33eee 3 | size 1318928 4 | -------------------------------------------------------------------------------- /images/Settings_Debug.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b7f36600058649c73fb1b150aeeedc7c0df477e10420726dbe3ceef1a12fbc85 3 | size 204111 4 | -------------------------------------------------------------------------------- /images/Settings_Proxy.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c59625be49ec90ea847fa6bc19ccc837fa022736557958f59049bf46cee3c5da 3 | size 497458 4 | -------------------------------------------------------------------------------- /CRD/Assets/sonarr_inactive.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1f8990ede98a4ec31d0018b4e1d6d628d38e2c36add503ab9afbff64f194e937 3 | size 11338 4 | -------------------------------------------------------------------------------- /images/Calendar_Custom.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:221b925000c4af65c7c6e4dd1ad41e6fa83f338d49aa9f068517819c8659c553 3 | size 2445666 4 | -------------------------------------------------------------------------------- /images/Download_Queue.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c36ed9fc50f0dc5cc07d2dd4fe065aba07ff581161b0444dd16db86a22e217ad 3 | size 1651333 4 | -------------------------------------------------------------------------------- /images/History_Overview.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:023106d3d3cea64b33444ea10eb53c1826bd4348150738064a613c95334a77f4 3 | size 4005470 4 | -------------------------------------------------------------------------------- /images/Settings_Appearance.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a997c2e0e4feedeaa27ed64adc31a95a822771091a4fd544e778e8a828736463 3 | size 520046 4 | -------------------------------------------------------------------------------- /images/Settings_Download.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1060a231b9d751ec7113f3629fc5d6bd8a9140e90b07991dd53bcb17594aedea 3 | size 748762 4 | -------------------------------------------------------------------------------- /images/Settings_Filename.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b8af7c95d1d1524b80e89decf25fff24d49088f9387454e04e6e900e03921994 3 | size 257691 4 | -------------------------------------------------------------------------------- /images/Settings_History.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5de4562fb9240336894c46eb638af39679d0cc4a8ce954abafbbb05ec0b8bd5e 3 | size 614123 4 | -------------------------------------------------------------------------------- /images/Settings_Muxing.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c7bc446361ee6089c8398c29355b0d94cc8faf0781eef2b87a5abaf019895400 3 | size 1256371 4 | -------------------------------------------------------------------------------- /images/Settings_Search_CR.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cf49c4b9a7fbdb7aadbb54454634f2ff8d26d355f1487fb6e6ca29177e25f5f2 3 | size 189462 4 | -------------------------------------------------------------------------------- /images/Settings_Softsubs.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0ef0a29fe0d5a3a157986ac6aaa3ff7fd8b44680146ccea31873d845c7d9f3eb 3 | size 496226 4 | -------------------------------------------------------------------------------- /images/Settings_Sonarr.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cdf83fb5879eb7019db5725a015defb53fea087fb637c9608bc0574654733120 3 | size 489541 4 | -------------------------------------------------------------------------------- /CRD/Assets/crunchy_icon_round.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:eb8049bb754d9e938ac7adf0b7884274c68d265b7ce7b0c4fa2085f84677296b 3 | size 5780 4 | -------------------------------------------------------------------------------- /images/Calendar_Custom_Settings.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9c9e5bb6abb881b09cc873a0b1abaa00fa689d59360d29887c4ab10e7681d6b3 3 | size 16028 4 | -------------------------------------------------------------------------------- /images/History_Overview_Table.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:44fdc5ece01d4d482acda4d1d67c6bc482fd6c96ec33a944c7766e7aeaedee9c 3 | size 1506259 4 | -------------------------------------------------------------------------------- /images/History_Series_Overview.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:edf6049ef4a28a65a5cd9656dd81fccfa344100879811ceb653144e756301e15 3 | size 1621039 4 | -------------------------------------------------------------------------------- /images/Settings_Download_CR.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5b68fdc1908506b36243bcccbc510d2565215e11e024b7e3bd1ae6b2956b9976 3 | size 840816 4 | -------------------------------------------------------------------------------- /images/Add_Downloads_Search_Enabled.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c96fe42754971a666095ebbe5d833b31cf235ae5b631878b3f3759c7afe878ef 3 | size 197916 4 | -------------------------------------------------------------------------------- /images/Add_Downloads_Search_Results.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5a61c273c82a97f5f52b78161389b14917b4fcca8135c13fb58d07b3208fec39 3 | size 603377 4 | -------------------------------------------------------------------------------- /images/Calendar_Simulcast_Settings.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:295749287df14d780e070d8462f12984163c00ccf0a16d56edcffedf37a819ee 3 | size 12787 4 | -------------------------------------------------------------------------------- /images/Settings_Muxing_New_Encoding_Preset.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e68ec790ab6b064ccd0224dfe75a401389b2ad85e41d1a5ec9007e4ed258fd01 3 | size 21890 4 | -------------------------------------------------------------------------------- /CRD/Views/UpdateView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views; 4 | 5 | public partial class UpdateView : UserControl{ 6 | public UpdateView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/AccountPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views; 4 | 5 | public partial class AccountPageView : UserControl{ 6 | public AccountPageView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/CalendarPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views; 4 | 5 | public partial class CalendarPageView : UserControl{ 6 | public CalendarPageView(){ 7 | InitializeComponent(); 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /CRD/Views/SeriesPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views; 4 | 5 | public partial class SeriesPageView : UserControl{ 6 | public SeriesPageView(){ 7 | InitializeComponent(); 8 | } 9 | 10 | 11 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/GeneralSettingsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class GeneralSettingsView : UserControl{ 6 | public GeneralSettingsView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/DownloadsPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views; 4 | 5 | public partial class DownloadsPageView : UserControl{ 6 | public DownloadsPageView(){ 7 | InitializeComponent(); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogInputLoginView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogInputLoginView : UserControl{ 6 | public ContentDialogInputLoginView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogSonarrMatchView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogSonarrMatchView : UserControl{ 6 | public ContentDialogSonarrMatchView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogFeaturedMusicView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogFeaturedMusicView : UserControl{ 6 | public ContentDialogFeaturedMusicView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogDropdownSelectView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogDropdownSelectView : UserControl{ 6 | public ContentDialogDropdownSelectView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogEncodingPresetView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogEncodingPresetView : UserControl{ 6 | public ContentDialogEncodingPresetView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogSonarrMatchEpisodeView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace CRD.Views.Utils; 4 | 5 | public partial class ContentDialogSonarrMatchEpisodeView : UserControl{ 6 | public ContentDialogSonarrMatchEpisodeView(){ 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Utils/ManifestInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.Parser.Utils; 2 | 3 | public class ManifestInfo{ 4 | public dynamic locations{ get; set; } 5 | public dynamic contentSteeringInfo{ get; set; } 6 | public dynamic representationInfo{ get; set; } 7 | public dynamic eventStream{ get; set; } 8 | } -------------------------------------------------------------------------------- /CRD/Utils/Sonarr/Models/SonarrQualityProfile.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using YamlDotNet.Core.Tokens; 3 | 4 | namespace CRD.Utils.Sonarr.Models; 5 | 6 | public class SonarrQualityProfile{ 7 | 8 | [JsonProperty("value")] 9 | public Value Value{ get; set; } 10 | 11 | 12 | [JsonProperty("isLoaded")] 13 | public bool IsLoaded{ get; set; } 14 | } -------------------------------------------------------------------------------- /CRD/Utils/CustomList/RefreshableObservableCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Collections.Specialized; 3 | 4 | namespace CRD.Utils.CustomList; 5 | 6 | public class RefreshableObservableCollection : ObservableCollection{ 7 | public void Refresh(){ 8 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 9 | } 10 | } -------------------------------------------------------------------------------- /CRD/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using CRD.Downloader; 3 | 4 | namespace CRD.ViewModels; 5 | 6 | public partial class MainWindowViewModel : ViewModelBase{ 7 | 8 | [ObservableProperty] 9 | public ProgramManager _programManager; 10 | 11 | public MainWindowViewModel(ProgramManager manager){ 12 | ProgramManager = manager; 13 | } 14 | } -------------------------------------------------------------------------------- /CRD/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using CommunityToolkit.Mvvm.ComponentModel; 3 | 4 | namespace CRD.ViewModels; 5 | 6 | public class ViewModelBase : ObservableObject{ 7 | public event PropertyChangedEventHandler PropertyChanged; 8 | 9 | protected void RaisePropertyChanged(string propName){ 10 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); 11 | } 12 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Utils/DivisionValueParser.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.Parser.Utils; 2 | 3 | public class DivisionValueParser{ 4 | public static double ParseDivisionValue(string value){ 5 | string[] parts = value.Split('/'); 6 | double result = double.Parse(parts[0]); 7 | for (int i = 1; i < parts.Length; i++){ 8 | result /= double.Parse(parts[i]); 9 | } 10 | 11 | return result; 12 | } 13 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/Crunchyroll/Series/CrSearchSeries.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CRD.Utils.Structs; 4 | 5 | public class CrSearchSeries{ 6 | public int count{ get; set; } 7 | public List? Items{ get; set; } 8 | public string? type{ get; set; } 9 | } 10 | 11 | public class CrSearchSeriesBase{ 12 | public int Total{ get; set; } 13 | public List? Data{ get; set; } 14 | public Meta Meta{ get; set; } 15 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/Variable.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.Structs; 2 | 3 | public class Variable{ 4 | public string Name{ get; set; } 5 | public object ReplaceWith{ get; set; } 6 | public string Type{ get; set; } 7 | public bool Sanitize{ get; set; } 8 | 9 | public Variable(string name, object replaceWith, bool sanitize){ 10 | Name = name; 11 | ReplaceWith = replaceWith; 12 | Type = replaceWith.GetType().Name.ToLower(); 13 | Sanitize = sanitize; 14 | } 15 | 16 | public Variable(){ 17 | } 18 | } -------------------------------------------------------------------------------- /CRD/Views/SettingsPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Interactivity; 3 | using CRD.Utils.Sonarr; 4 | using CRD.ViewModels; 5 | 6 | namespace CRD.Views; 7 | 8 | public partial class SettingsPageView : UserControl{ 9 | public SettingsPageView(){ 10 | InitializeComponent(); 11 | } 12 | 13 | private void OnUnloaded(object? sender, RoutedEventArgs e){ 14 | if (DataContext is SettingsPageViewModel viewModel){ 15 | SonarrClient.Instance.RefreshSonarr(); 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/History/SeriesDataCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CRD.Utils.Structs.History; 4 | 5 | public class SeriesDataCache{ 6 | 7 | public string SeriesId{ get; set; } = ""; 8 | 9 | public string SeriesTitle{ get; set; } = ""; 10 | 11 | public string SeriesDescription{ get; set; } = ""; 12 | public string ThumbnailImageUrl{ get; set; } = ""; 13 | 14 | public List HistorySeriesAvailableDubLang{ get; set; } =[]; 15 | 16 | public List HistorySeriesAvailableSoftSubs{ get; set; } =[]; 17 | } -------------------------------------------------------------------------------- /CRD/Utils/Sonarr/Models/SonarrImage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace CRD.Utils.Sonarr.Models; 4 | 5 | public class SonarrImage{ 6 | /// 7 | /// Gets or sets the type of the cover. 8 | /// 9 | /// 10 | /// The type of the cover. 11 | /// 12 | [JsonProperty("coverType")] public SonarrCoverType CoverType { get; set; } 13 | 14 | /// 15 | /// Gets or sets the URL. 16 | /// 17 | /// 18 | /// The URL. 19 | /// 20 | [JsonProperty("url")] public string Url { get; set; } 21 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/Crunchyroll/CrToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CRD.Utils.Structs.Crunchyroll; 4 | 5 | public class CrToken{ 6 | public string? access_token { get; set; } 7 | public string? refresh_token { get; set; } 8 | public int? expires_in { get; set; } 9 | public string? token_type { get; set; } 10 | public string? scope { get; set; } 11 | public string? country { get; set; } 12 | public string? account_id { get; set; } 13 | public string? profile_id { get; set; } 14 | public string? device_id { get; set; } 15 | public DateTime expires { get; set; } 16 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiEmptyToDefaultConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Avalonia.Data.Converters; 3 | 4 | namespace CRD.Utils.UI; 5 | 6 | public class UiEmptyToDefaultConverter: IValueConverter{ 7 | public object? Convert(object? value, System.Type targetType, object? parameter, CultureInfo culture){ 8 | var s = value as string; 9 | var fallback = parameter as string ?? string.Empty; 10 | return string.IsNullOrEmpty(s) ? fallback : s!; 11 | } 12 | 13 | public object? ConvertBack(object? value, System.Type targetType, object? parameter, CultureInfo culture) => value!; 14 | } -------------------------------------------------------------------------------- /CRD/Utils/AudioPlayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NetCoreAudio; 3 | 4 | namespace CRD.Utils; 5 | 6 | public class AudioPlayer{ 7 | private readonly Player _player; 8 | private bool _isPlaying = false; 9 | 10 | public AudioPlayer(){ 11 | _player = new Player(); 12 | } 13 | 14 | public async void Play(string path){ 15 | if (_isPlaying){ 16 | Console.WriteLine("Audio is already playing, ignoring duplicate request."); 17 | return; 18 | } 19 | 20 | _isPlaying = true; 21 | await _player.Play(path); 22 | _isPlaying = false; 23 | } 24 | 25 | public async void Stop(){ 26 | await _player.Stop(); 27 | _isPlaying = false; 28 | } 29 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Playlists/Errors.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.Parser; 2 | 3 | public class Errors{ 4 | public static string INVALID_NUMBER_OF_PERIOD = "INVALID_NUMBER_OF_PERIOD"; 5 | public static string INVALID_NUMBER_OF_CONTENT_STEERING = "INVALID_NUMBER_OF_CONTENT_STEERING"; 6 | public static string DASH_EMPTY_MANIFEST = "DASH_EMPTY_MANIFEST"; 7 | public static string DASH_INVALID_XML = "DASH_INVALID_XML"; 8 | public static string NO_BASE_URL = "NO_BASE_URL"; 9 | public static string MISSING_SEGMENT_INFORMATION = "MISSING_SEGMENT_INFORMATION"; 10 | public static string SEGMENT_TIME_UNSPECIFIED = "SEGMENT_TIME_UNSPECIFIED"; 11 | public static string UNSUPPORTED_UTC_TIMING_SCHEME = "UNSUPPORTED_UTC_TIMING_SCHEME"; 12 | 13 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiSeasonValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | 5 | namespace CRD.Utils.UI; 6 | 7 | public class UiSeasonValueConverter : IValueConverter{ 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ 9 | 10 | if (value is string stringValue){ 11 | var parsed = int.TryParse(stringValue, out int seasonNum); 12 | if (parsed) 13 | return $"Season {seasonNum}"; 14 | } 15 | 16 | return "Specials"; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){ 20 | throw new NotImplementedException(); 21 | } 22 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiIntToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | 5 | namespace CRD.Utils.UI; 6 | 7 | public class UiIntToVisibilityConverter : IValueConverter{ 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ 9 | if (value is int intValue){ 10 | // Return Visible if intValue is greater than or equal to 1, otherwise Collapsed 11 | return intValue >= 1 ? true : false; 12 | } 13 | 14 | return false; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){ 18 | throw new NotImplementedException("This converter only works for one-way binding"); 19 | } 20 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | using FluentAvalonia.UI.Controls; 5 | 6 | namespace CRD.Utils.UI; 7 | 8 | public class UiValueConverter : IValueConverter{ 9 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){ 10 | if (value is bool boolValue){ 11 | return boolValue ? Symbol.Pause : Symbol.Play; 12 | } 13 | 14 | return null; // Or return a default value 15 | } 16 | 17 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture){ 18 | if (value is Symbol sym) 19 | { 20 | return sym == Symbol.Pause; 21 | } 22 | return false; 23 | } 24 | } -------------------------------------------------------------------------------- /CRD/ViewLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using CRD.ViewModels; 5 | 6 | namespace CRD; 7 | 8 | public class ViewLocator : IDataTemplate{ 9 | public Control? Build(object? data){ 10 | if (data is null) 11 | return null; 12 | 13 | var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); 14 | var type = Type.GetType(name); 15 | 16 | if (type != null){ 17 | var control = (Control)Activator.CreateInstance(type)!; 18 | control.DataContext = data; 19 | return control; 20 | } 21 | 22 | return new TextBlock{ Text = "Not Found: " + name }; 23 | } 24 | 25 | public bool Match(object? data){ 26 | return data is ViewModelBase; 27 | } 28 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiListToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using Avalonia.Data.Converters; 6 | 7 | namespace CRD.Utils.UI; 8 | 9 | public class UiListToStringConverter : IValueConverter{ 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ 11 | if (value is List list){ 12 | return string.Join(", ", list); 13 | } 14 | 15 | return ""; 16 | } 17 | 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){ 20 | if (value is string str){ 21 | return str.Split(new[]{ ", " }, StringSplitOptions.None).ToList(); 22 | } 23 | 24 | return new List(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CRD/Utils/UI/UiSeriesSeasonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using Avalonia.Data.Converters; 5 | using CRD.Utils.Structs; 6 | using CRD.Utils.Structs.History; 7 | 8 | namespace CRD.Utils.UI; 9 | 10 | public class UiSeriesSeasonConverter : IMultiValueConverter{ 11 | public object Convert(IList values, Type targetType, object parameter, CultureInfo culture){ 12 | var series = values.Count > 0 && values[0] is HistorySeries hs ? hs : null; 13 | var season = values.Count > 1 && values[1] is HistorySeason hsn ? hsn : null; 14 | return new SeasonDialogArgs(series, season); 15 | } 16 | 17 | public IList ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 18 | => throw new NotImplementedException(); 19 | } -------------------------------------------------------------------------------- /CRD/Views/HistoryPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Interactivity; 4 | using CRD.ViewModels; 5 | 6 | namespace CRD.Views; 7 | 8 | public partial class HistoryPageView : UserControl{ 9 | public HistoryPageView(){ 10 | InitializeComponent(); 11 | } 12 | 13 | private void OnUnloaded(object? sender, RoutedEventArgs e){ 14 | 15 | if (DataContext is HistoryPageViewModel viewModel){ 16 | viewModel.LastScrollOffset = SeriesListBox.Scroll?.Offset ?? Vector.Zero; 17 | } 18 | 19 | } 20 | 21 | private void Control_OnLoaded(object? sender, RoutedEventArgs e){ 22 | if (DataContext is HistoryPageViewModel viewModel){ 23 | if (SeriesListBox.Scroll != null) SeriesListBox.Scroll.Offset = viewModel.LastScrollOffset; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiSonarrIdToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | using CRD.Downloader.Crunchyroll; 5 | 6 | namespace CRD.Utils.UI; 7 | 8 | public class UiSonarrIdToVisibilityConverter : IValueConverter{ 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ 10 | if (value is string stringValue){ 11 | return CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null && (stringValue.Length > 0 && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled); 12 | } 13 | 14 | return false; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){ 18 | throw new NotImplementedException("This converter only works for one-way binding"); 19 | } 20 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/History/IHistorySource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CRD.Utils.Structs.History; 5 | 6 | public interface IHistorySource{ 7 | string GetSeriesId(); 8 | string GetSeriesTitle(); 9 | string GetSeasonTitle(); 10 | string GetSeasonNum(); 11 | string GetSeasonId(); 12 | 13 | string GetImageUrl(); 14 | 15 | string GetEpisodeId(); 16 | string GetEpisodeNumber(); 17 | string GetEpisodeTitle(); 18 | string GetEpisodeDescription(); 19 | 20 | bool IsSpecialSeason(); 21 | bool IsSpecialEpisode(); 22 | 23 | List GetAnimeIds(); 24 | 25 | List GetEpisodeAvailableDubLang(); 26 | List GetEpisodeAvailableSoftSubs(); 27 | 28 | DateTime GetAvailableDate(); 29 | 30 | SeriesType GetSeriesType(); 31 | EpisodeType GetEpisodeType(); 32 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Utils/XMLUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Xml; 4 | 5 | namespace CRD.Utils.Parser.Utils; 6 | 7 | public class XMLUtils{ 8 | public static List FindChildren(XmlElement element, string name){ 9 | return From(element.ChildNodes).OfType().Where(child => child.Name == name).ToList(); 10 | } 11 | 12 | public static string GetContent(XmlElement element){ 13 | return element.InnerText.Trim(); 14 | } 15 | 16 | private static List From(XmlNodeList list){ 17 | if (list.Count == 0){ 18 | return new List(); 19 | } 20 | 21 | List result = new List(list.Count); 22 | 23 | for (int i = 0; i < list.Count; i++){ 24 | result.Add(list[i]); 25 | } 26 | 27 | return result; 28 | } 29 | } -------------------------------------------------------------------------------- /CRD/Utils/DRM/CryptoUtils.cs: -------------------------------------------------------------------------------- 1 | namespace CRD.Utils.DRM; 2 | 3 | using Org.BouncyCastle.Crypto; 4 | using Org.BouncyCastle.Crypto.Engines; 5 | using Org.BouncyCastle.Crypto.Macs; 6 | using Org.BouncyCastle.Crypto.Parameters; 7 | using System.Security.Cryptography; 8 | 9 | public class CryptoUtils{ 10 | public static byte[] GetHMACSHA256Digest(byte[] data, byte[] key){ 11 | return new HMACSHA256(key).ComputeHash(data); 12 | } 13 | 14 | public static byte[] GetCMACDigest(byte[] data, byte[] key){ 15 | IBlockCipher cipher = new AesEngine(); 16 | IMac mac = new CMac(cipher, 128); 17 | 18 | KeyParameter keyParam = new KeyParameter(key); 19 | 20 | mac.Init(keyParam); 21 | 22 | mac.BlockUpdate(data, 0, data.Length); 23 | 24 | byte[] outBytes = new byte[16]; 25 | 26 | mac.DoFinal(outBytes, 0); 27 | return outBytes; 28 | } 29 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiEnumToBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | 5 | namespace CRD.Utils.UI; 6 | 7 | public class UiEnumToBoolConverter : IValueConverter{ 8 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture){ 9 | if (value == null || parameter == null) 10 | return false; 11 | 12 | string enumString = parameter.ToString(); 13 | if (enumString == null) 14 | return false; 15 | 16 | return value.ToString() == enumString; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture){ 20 | if ((bool)value && parameter != null){ 21 | return Enum.Parse(targetType, parameter.ToString() ?? string.Empty); 22 | } 23 | 24 | return Avalonia.Data.BindingOperations.DoNothing; 25 | } 26 | } -------------------------------------------------------------------------------- /CRD/Views/AddDownloadPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime; 3 | using Avalonia.Controls; 4 | using Avalonia.Interactivity; 5 | using CRD.ViewModels; 6 | 7 | namespace CRD.Views; 8 | 9 | public partial class AddDownloadPageView : UserControl{ 10 | public AddDownloadPageView(){ 11 | InitializeComponent(); 12 | } 13 | 14 | private void OnUnloaded(object? sender, RoutedEventArgs e){ 15 | if (DataContext is AddDownloadPageViewModel viewModel){ 16 | viewModel.Dispose(); 17 | DataContext = null; 18 | GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; 19 | GC.Collect(); 20 | } 21 | } 22 | 23 | private void Popup_Closed(object? sender, EventArgs e){ 24 | if (DataContext is AddDownloadPageViewModel viewModel){ 25 | viewModel.SearchPopupVisible = false; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/Crunchyroll/Music/CrArtist.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace CRD.Utils.Structs.Crunchyroll.Music; 6 | 7 | 8 | public class CrunchyArtistList{ 9 | public int Total{ get; set; } 10 | public List Data{ get; set; } =[]; 11 | public Meta? Meta{ get; set; } 12 | } 13 | 14 | public class CrArtist{ 15 | [JsonProperty("description")] 16 | public string? Description{ get; set; } 17 | 18 | [JsonProperty("name")] 19 | public string? Name{ get; set; } 20 | 21 | [JsonProperty("slug")] 22 | public string? Slug{ get; set; } 23 | 24 | [JsonProperty("type")] 25 | public string? Type{ get; set; } 26 | 27 | [JsonProperty("id")] 28 | public string? Id{ get; set; } 29 | 30 | [JsonProperty("publishDate")] 31 | public DateTime? PublishDate{ get; set; } 32 | 33 | public MusicImages Images{ get; set; } = new(); 34 | 35 | } -------------------------------------------------------------------------------- /CRD/Utils/UI/UiListHasElementsConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Globalization; 4 | using Avalonia.Data.Converters; 5 | 6 | namespace CRD.Utils.UI; 7 | 8 | public class UiListHasElementsConverter : IValueConverter{ 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ 10 | if (value is IEnumerable enumerable){ 11 | // Check if the collection has any elements 12 | foreach (var _ in enumerable){ 13 | return true; // At least one element exists 14 | } 15 | 16 | return false; // No elements 17 | } 18 | 19 | // Return false if the input is not a collection or is null 20 | return false; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){ 24 | throw new NotSupportedException("ListToBooleanConverter does not support ConvertBack."); 25 | } 26 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Utils/UrlUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CRD.Utils.Parser.Utils; 4 | 5 | public class UrlUtils{ 6 | public static string ResolveUrl(string baseUrl, string relativeUrl){ 7 | // Return early if the relative URL is actually an absolute URL 8 | if (Uri.IsWellFormedUriString(relativeUrl, UriKind.Absolute)) 9 | return relativeUrl; 10 | 11 | // Handle the case where baseUrl is not specified or invalid 12 | Uri baseUri; 13 | if (string.IsNullOrEmpty(baseUrl) || !Uri.TryCreate(baseUrl, UriKind.Absolute, out baseUri)){ 14 | // Assuming you want to use a default base if none is provided 15 | // For example, you could default to "http://example.com" 16 | // This part is up to how you want to handle such cases 17 | baseUri = new Uri("http://example.com"); 18 | } 19 | 20 | Uri resolvedUri = new Uri(baseUri, relativeUrl); 21 | return resolvedUri.ToString(); 22 | } 23 | } -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogInputLoginView.axaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CRD/Utils/DRM/ContentKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace CRD.Utils.DRM; 8 | 9 | [Serializable] 10 | public class ContentKey{ 11 | [JsonPropertyName("key_id")] public byte[] KeyID{ get; set; } 12 | 13 | [JsonPropertyName("type")] public string Type{ get; set; } 14 | 15 | [JsonPropertyName("bytes")] public byte[] Bytes{ get; set; } // key 16 | 17 | [NotMapped] 18 | [JsonPropertyName("permissions")] 19 | public List Permissions{ 20 | get{ return PermissionsString.Split(",").ToList(); } 21 | set{ PermissionsString = string.Join(",", value); } 22 | } 23 | 24 | [JsonIgnore] public string PermissionsString{ get; set; } 25 | 26 | public override string ToString(){ 27 | return $"{BitConverter.ToString(KeyID).Replace("-", "").ToLower()}:{BitConverter.ToString(Bytes).Replace("-", "").ToLower()}"; 28 | } 29 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/AnilistResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CRD.Utils.Structs.History; 4 | 5 | public class AniListResponse{ 6 | public Data? Data{ get; set; } 7 | } 8 | 9 | public class Data{ 10 | public Page? Page{ get; set; } 11 | } 12 | 13 | public class Page{ 14 | public PageInfo? PageInfo{ get; set; } 15 | public List? Media{ get; set; } 16 | } 17 | 18 | public class PageInfo{ 19 | public bool HasNextPage{ get; set; } 20 | public int Total{ get; set; } 21 | } 22 | 23 | public class AniListResponseCalendar{ 24 | public Data2? Data{ get; set; } 25 | } 26 | 27 | public class Data2{ 28 | public Page2? Page{ get; set; } 29 | } 30 | 31 | public class Page2{ 32 | public PageInfo? PageInfo{ get; set; } 33 | public List? AiringSchedules{ get; set; } 34 | } 35 | 36 | public class AiringSchedule{ 37 | public int Id{ get; set; } 38 | public int Episode{ get; set; } 39 | public int AiringAt{ get; set; } 40 | public AnilistSeries? Media{ get; set; } 41 | } -------------------------------------------------------------------------------- /CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Avalonia.Controls; 3 | using Avalonia.VisualTree; 4 | 5 | namespace CRD.Downloader.Crunchyroll.Views; 6 | 7 | public partial class CrunchyrollSettingsView : UserControl{ 8 | public CrunchyrollSettingsView(){ 9 | InitializeComponent(); 10 | } 11 | 12 | private void ListBox_PointerWheelChanged(object sender, Avalonia.Input.PointerWheelEventArgs e){ 13 | var listBox = sender as ListBox; 14 | var scrollViewer = listBox?.GetVisualDescendants().OfType().FirstOrDefault(); 15 | 16 | if (scrollViewer != null){ 17 | // Determine if the ListBox is at its bounds (top or bottom) 18 | bool atTop = scrollViewer.Offset.Y <= 0 && e.Delta.Y > 0; 19 | bool atBottom = scrollViewer.Offset.Y >= scrollViewer.Extent.Height - scrollViewer.Viewport.Height && e.Delta.Y < 0; 20 | 21 | if (atTop || atBottom){ 22 | e.Handled = true; // Stop the event from propagating to the parent 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CRD/Utils/Structs/Chapters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace CRD.Utils.Structs; 6 | 7 | public class CrunchyChapters{ 8 | public List Chapters { get; set; } 9 | public DateTime lastUpdate { get; set; } 10 | public string? mediaId { get; set; } 11 | } 12 | 13 | public class CrunchyChapter{ 14 | public string approverId { get; set; } 15 | public string distributionNumber { get; set; } 16 | public double? end { get; set; } 17 | public double? start { get; set; } 18 | public string title { get; set; } 19 | public string seriesId { get; set; } 20 | [JsonProperty("new")] 21 | public string? New { get; set; } 22 | public string type { get; set; } 23 | } 24 | 25 | public class CrunchyOldChapter{ 26 | public string media_id { get; set; } 27 | public double startTime { get; set; } 28 | public double endTime { get; set; } 29 | public double duration { get; set; } 30 | public string comparedWith { get; set; } 31 | public string ordering { get; set; } 32 | public DateTime last_updated { get; set; } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Crunchy DL 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 | -------------------------------------------------------------------------------- /CRD/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls.ApplicationLifetimes; 4 | using Avalonia.Markup.Xaml; 5 | using CRD.ViewModels; 6 | using MainWindow = CRD.Views.MainWindow; 7 | using System.Linq; 8 | using CRD.Downloader; 9 | 10 | namespace CRD; 11 | 12 | public partial class App : Application{ 13 | public override void Initialize(){ 14 | AvaloniaXamlLoader.Load(this); 15 | } 16 | 17 | public override void OnFrameworkInitializationCompleted(){ 18 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop){ 19 | var isHeadless = Environment.GetCommandLineArgs().Contains("--headless"); 20 | 21 | var manager = ProgramManager.Instance; 22 | 23 | if (!isHeadless){ 24 | desktop.MainWindow = new MainWindow{ 25 | DataContext = new MainWindowViewModel(manager), 26 | }; 27 | 28 | desktop.MainWindow.Opened += (_, _) => { manager.SetBackgroundImage(); }; 29 | } 30 | 31 | 32 | 33 | } 34 | 35 | base.OnFrameworkInitializationCompleted(); 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /CRD/Utils/Sonarr/Models/SonarrSeason.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace CRD.Utils.Sonarr.Models; 5 | 6 | public class SonarrSeason{ 7 | /// 8 | /// Gets or sets the season number. 9 | /// 10 | /// 11 | /// The season number. 12 | /// 13 | [JsonProperty("seasonNumber")] public int SeasonNumber { get; set; } 14 | 15 | /// 16 | /// Gets or sets a value indicating whether this is monitored. 17 | /// 18 | /// 19 | /// true if monitored; otherwise, false. 20 | /// 21 | [JsonProperty("monitored")] public bool Monitored { get; set; } 22 | 23 | /// 24 | /// Gets or sets the statistics. 25 | /// 26 | /// 27 | /// The statistics. 28 | /// 29 | [JsonProperty("statistics")] public SonarrStatistics Statistics { get; set; } 30 | 31 | /// 32 | /// Gets or sets the images. 33 | /// 34 | /// 35 | /// The images. 36 | /// 37 | [JsonProperty("images")] public List Images { get; set; } 38 | } -------------------------------------------------------------------------------- /CRD/Views/UpcomingSeasonsPageView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using CRD.Utils.Structs; 3 | using CRD.ViewModels; 4 | 5 | namespace CRD.Views; 6 | 7 | public partial class UpcomingPageView : UserControl{ 8 | public UpcomingPageView(){ 9 | InitializeComponent(); 10 | } 11 | 12 | private void SelectionChanged(object? sender, SelectionChangedEventArgs e){ 13 | if (DataContext is UpcomingPageViewModel viewModel && sender is ListBox listBox){ 14 | viewModel.SelectionChangedOfSeries((AnilistSeries?)listBox.SelectedItem); 15 | } 16 | } 17 | 18 | private void ScrollViewer_PointerWheelChanged(object sender, Avalonia.Input.PointerWheelEventArgs e){ 19 | if (sender is ScrollViewer scrollViewer){ 20 | // Determine if the ListBox is at its bounds (top or bottom) 21 | bool atTop = scrollViewer.Offset.Y <= 0 && e.Delta.Y > 0; 22 | bool atBottom = scrollViewer.Offset.Y >= scrollViewer.Extent.Height - scrollViewer.Viewport.Height && e.Delta.Y < 0; 23 | 24 | if (atTop || atBottom){ 25 | e.Handled = true; // Stop the event from propagating to the parent 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /CRD/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CRD/Utils/UI/UiValueConverterCalendarBackground.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia; 4 | using Avalonia.Data.Converters; 5 | using Avalonia.Media; 6 | using Avalonia.Styling; 7 | 8 | namespace CRD.Utils.UI; 9 | 10 | public class UiValueConverterCalendarBackground : IValueConverter{ 11 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){ 12 | if (value is bool boolValue){ 13 | var currentThemeVariant = Application.Current?.RequestedThemeVariant; 14 | 15 | return boolValue ? currentThemeVariant == ThemeVariant.Dark ? new SolidColorBrush(Color.Parse("#583819")) : new SolidColorBrush(Color.Parse("#ffd8a1")) : 16 | currentThemeVariant == ThemeVariant.Dark ? new SolidColorBrush(Color.Parse("#353535")) : new SolidColorBrush(Color.Parse("#d7d7d7")); 17 | // return boolValue ? new SolidColorBrush(Color.Parse("#10f5d800")) : new SolidColorBrush(Color.Parse("#10FFFFFF")); 18 | } 19 | 20 | return new SolidColorBrush(Color.Parse("#10FFFFFF")); 21 | } 22 | 23 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture){ 24 | throw new NotImplementedException(); 25 | } 26 | } -------------------------------------------------------------------------------- /CRD/Utils/Parser/Segments/UrlType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CRD.Utils.Parser.Utils; 3 | 4 | namespace CRD.Utils.Parser.Segments; 5 | 6 | public class UrlType{ 7 | public static dynamic UrlTypeToSegment(dynamic input){ 8 | dynamic segment = new { 9 | uri = ObjectUtilities.GetMemberValue(input,"source"), 10 | resolvedUri = new Uri(new Uri(input.baseUrl, UriKind.Absolute), input.source).ToString() 11 | }; 12 | 13 | string rangeStr = !string.IsNullOrEmpty(input.range) ? ObjectUtilities.GetMemberValue(input,"range") : ObjectUtilities.GetMemberValue(input,"indexRange"); 14 | if (!string.IsNullOrEmpty(rangeStr)){ 15 | var ranges = rangeStr.Split('-'); 16 | long startRange = long.Parse(ranges[0]); 17 | long endRange = long.Parse(ranges[1]); 18 | long length = endRange - startRange + 1; 19 | 20 | segment.ByteRange = new { 21 | length = length, 22 | offset = startRange 23 | }; 24 | } 25 | 26 | return segment; 27 | } 28 | 29 | 30 | public static string ByteRangeToString(dynamic byteRange){ 31 | long endRange = byteRange.offset + byteRange.length - 1; 32 | return $"{byteRange.offset}-{endRange}"; 33 | } 34 | } -------------------------------------------------------------------------------- /CRD/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using System.Linq; 4 | using ReactiveUI.Avalonia; 5 | 6 | namespace CRD; 7 | 8 | sealed class Program{ 9 | // Initialization code. Don't use any Avalonia, third-party APIs or any 10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 11 | // yet and stuff might break. 12 | [STAThread] 13 | public static void Main(string[] args){ 14 | var isHeadless = args.Contains("--headless"); 15 | 16 | BuildAvaloniaApp(isHeadless).StartWithClassicDesktopLifetime(args); 17 | } 18 | 19 | // Avalonia configuration, don't remove; also used by visual designer. 20 | // public static AppBuilder BuildAvaloniaApp() 21 | // => AppBuilder.Configure() 22 | // .UsePlatformDetect() 23 | // .WithInterFont() 24 | // .LogToTrace(); 25 | 26 | public static AppBuilder BuildAvaloniaApp(bool isHeadless){ 27 | var builder = AppBuilder.Configure() 28 | .UsePlatformDetect() 29 | .WithInterFont() 30 | .LogToTrace() 31 | .UseReactiveUI() ; 32 | 33 | if (isHeadless){ 34 | Console.WriteLine("Running in headless mode..."); 35 | } 36 | 37 | return builder; 38 | } 39 | } -------------------------------------------------------------------------------- /CRD/App.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 500 15 | 1500 16 | 150 17 | 700 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CRD/Views/Utils/ContentDialogSeriesDetailsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Avalonia.Controls; 3 | using Avalonia.VisualTree; 4 | using CRD.ViewModels.Utils; 5 | using Image = CRD.Utils.Structs.Image; 6 | 7 | namespace CRD.Views.Utils; 8 | 9 | public partial class ContentDialogSeriesDetailsView : UserControl{ 10 | public ContentDialogSeriesDetailsView(){ 11 | InitializeComponent(); 12 | } 13 | 14 | private void ImageSelectionChanged(object? sender, SelectionChangedEventArgs e){ 15 | if (DataContext is ContentDialogSeriesDetailsViewModel viewModel && sender is ListBox listBox){ 16 | _ = viewModel.DownloadImage((Image)listBox.SelectedItem ); 17 | } 18 | } 19 | 20 | private void ListBox_PointerWheelChanged(object sender, Avalonia.Input.PointerWheelEventArgs e){ 21 | var listBox = sender as ListBox; 22 | var scrollViewer = listBox?.GetVisualDescendants().OfType().FirstOrDefault(); 23 | 24 | if (scrollViewer != null){ 25 | // Determine if the ListBox is at its bounds (top or bottom) 26 | bool atTop = scrollViewer.Offset.Y <= 0 && e.Delta.Y > 0; 27 | bool atBottom = scrollViewer.Offset.Y >= scrollViewer.Extent.Height - scrollViewer.Viewport.Height && e.Delta.Y < 0; 28 | 29 | if (atTop || atBottom){ 30 | e.Handled = true; // Stop the event from propagating to the parent 31 | } 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-bug-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[Bug]" 3 | about: Create a bug report 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before you open a new issue, please ensure you have done the following:** 11 | 12 | 1. **Review Closed Issues** 13 | - Have you reviewed the closed issues to verify if your concern has already been resolved? Many common problems and their solutions are documented there. 14 | - [Search Closed Issues](https://github.com/Crunchy-DL/Crunchy-Downloader/issues?q=is%3Aissue+is%3Aclosed) 15 | 16 | 2. **Consult the Wiki and Discussions** 17 | - The [Wiki](https://github.com/Crunchy-DL/Crunchy-Downloader/wiki) is a valuable resource for guides and troubleshooting steps. 18 | - The [Discussions](https://github.com/Crunchy-DL/Crunchy-Downloader/discussions) section is also a great place to see if others have encountered and resolved similar issues. 19 | 20 | 3. **Enable Log Mode and Review for Errors** 21 | - If the program displays an error or behaves unexpectedly, please enable log mode and review the logs for any error messages. Include these logs in your issue submission. 22 | 23 | 4. **Check the Windows Event Log (if the program crashes)** 24 | - In the event of a program crash, please check the Windows Event Log for any related error messages (.NET runtime) and include that information in your report. 25 | 26 | **Please Note:** Feature requests or issues that have already been addressed, as well as requests for features that already exist, will be closed without further comment. 27 | -------------------------------------------------------------------------------- /CRD/Utils/JsonConv/UtcToLocalTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace CRD.Utils.JsonConv; 5 | 6 | public class UtcToLocalTimeConverter : JsonConverter{ 7 | public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer){ 8 | try{ 9 | return reader.Value switch{ 10 | null => DateTime.MinValue, 11 | DateTime dateTime when dateTime.Kind == DateTimeKind.Utc => dateTime.ToLocalTime(), 12 | DateTime dateTime => dateTime, 13 | string dateString => TryParseDateTime(dateString), 14 | _ => throw new JsonSerializationException($"Unexpected token parsing date. Expected DateTime or string, got {reader.Value?.GetType()}.") 15 | }; 16 | } catch (Exception ex){ 17 | Console.Error.WriteLine("Error deserializing DateTime", ex); 18 | } 19 | return DateTime.UnixEpoch; 20 | } 21 | 22 | private DateTime TryParseDateTime(string dateString){ 23 | if (DateTime.TryParse(dateString, out DateTime parsedDate)){ 24 | return parsedDate.Kind == DateTimeKind.Utc ? parsedDate.ToLocalTime() : parsedDate; 25 | } 26 | 27 | throw new JsonSerializationException($"Invalid date string: {dateString}"); 28 | } 29 | 30 | public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer){ 31 | writer.WriteValue(value); 32 | } 33 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-feature-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[Feature]" 3 | about: Feature request 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before you open a new issue, please ensure you have done the following:** 11 | 12 | 1. **Review Closed Issues** 13 | - Have you reviewed the closed issues to verify if your concern has already been resolved? Many common problems and their solutions are documented there. 14 | - [Search Closed Issues](https://github.com/Crunchy-DL/Crunchy-Downloader/issues?q=is%3Aissue+is%3Aclosed) 15 | 16 | 2. **Consult the Wiki and Discussions** 17 | - The [Wiki](https://github.com/Crunchy-DL/Crunchy-Downloader/wiki) is a valuable resource for guides and troubleshooting steps. 18 | - The [Discussions](https://github.com/Crunchy-DL/Crunchy-Downloader/discussions) section is also a great place to see if others have encountered and resolved similar issues. 19 | 20 | 3. **Enable Log Mode and Review for Errors** 21 | - If the program displays an error or behaves unexpectedly, please enable log mode and review the logs for any error messages. Include these logs in your issue submission. 22 | 23 | 4. **Check the Windows Event Log (if the program crashes)** 24 | - In the event of a program crash, please check the Windows Event Log for any related error messages (.NET runtime) and include that information in your report. 25 | 26 | **Please Note:** Feature requests or issues that have already been addressed, as well as requests for features that already exist, will be closed without further comment. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Not a bug report or feature request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before you open a new issue, please ensure you have done the following:** 11 | 12 | 1. **Review Closed Issues** 13 | - Have you reviewed the closed issues to verify if your concern has already been resolved? Many common problems and their solutions are documented there. 14 | - [Search Closed Issues](https://github.com/Crunchy-DL/Crunchy-Downloader/issues?q=is%3Aissue+is%3Aclosed) 15 | 16 | 2. **Consult the Wiki and Discussions** 17 | - The [Wiki](https://github.com/Crunchy-DL/Crunchy-Downloader/wiki) is a valuable resource for guides and troubleshooting steps. 18 | - The [Discussions](https://github.com/Crunchy-DL/Crunchy-Downloader/discussions) section is also a great place to see if others have encountered and resolved similar issues. 19 | 20 | 3. **Enable Log Mode and Review for Errors** 21 | - If the program displays an error or behaves unexpectedly, please enable log mode and review the logs for any error messages. Include these logs in your issue submission. 22 | 23 | 4. **Check the Windows Event Log (if the program crashes)** 24 | - In the event of a program crash, please check the Windows Event Log for any related error messages (.NET runtime) and include that information in your report. 25 | 26 | **Please Note:** Feature requests or issues that have already been addressed, as well as requests for features that already exist, will be closed without further comment. 27 | -------------------------------------------------------------------------------- /CRD/Views/ToastNotification.axaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 15 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /CRD/ViewModels/Utils/ContentDialogDropdownSelectViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using CommunityToolkit.Mvvm.ComponentModel; 5 | using CRD.Utils.Structs; 6 | using FluentAvalonia.UI.Controls; 7 | 8 | namespace CRD.ViewModels.Utils; 9 | 10 | public partial class ContentDialogDropdownSelectViewModel : ViewModelBase{ 11 | private readonly ContentDialog dialog; 12 | 13 | public ObservableCollection DropDownItemList{ get; } = new(){ }; 14 | 15 | [ObservableProperty] 16 | private StringItem _selectedDropdownItem = new StringItem(); 17 | 18 | [ObservableProperty] 19 | private string _episodeInfo; 20 | 21 | public ContentDialogDropdownSelectViewModel(ContentDialog dialog, string episodeInfo, List dropdownItems){ 22 | if (dialog is null){ 23 | throw new ArgumentNullException(nameof(dialog)); 24 | } 25 | 26 | this.dialog = dialog; 27 | dialog.Closed += DialogOnClosed; 28 | dialog.PrimaryButtonClick += SaveButton; 29 | EpisodeInfo = episodeInfo; 30 | foreach (var dropdownItem in dropdownItems){ 31 | DropDownItemList.Add(new StringItem(){ stringValue = dropdownItem }); 32 | } 33 | } 34 | 35 | private async void SaveButton(ContentDialog sender, ContentDialogButtonClickEventArgs args){ 36 | dialog.PrimaryButtonClick -= SaveButton; 37 | } 38 | 39 | private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){ 40 | dialog.Closed -= DialogOnClosed; 41 | } 42 | } -------------------------------------------------------------------------------- /CRD/Utils/JsonConv/LocaleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.Serialization; 4 | using Newtonsoft.Json; 5 | 6 | namespace CRD.Utils.JsonConv; 7 | 8 | public class LocaleConverter : JsonConverter{ 9 | public override bool CanConvert(Type objectType){ 10 | return objectType == typeof(Locale); 11 | } 12 | 13 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer){ 14 | if (reader.TokenType == JsonToken.Null) 15 | return Locale.Unknown; 16 | 17 | var value = reader.Value?.ToString(); 18 | 19 | foreach (Locale locale in Enum.GetValues(typeof(Locale))){ 20 | FieldInfo fi = typeof(Locale).GetField(locale.ToString()); 21 | EnumMemberAttribute[] attributes = (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false); 22 | if (attributes.Length > 0 && attributes[0].Value == value) 23 | return locale; 24 | } 25 | 26 | return Locale.Unknown; 27 | } 28 | 29 | public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer){ 30 | FieldInfo? fi = value?.GetType().GetField(value.ToString() ?? string.Empty); 31 | EnumMemberAttribute[] attributes = (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false); 32 | 33 | if (attributes.Length > 0 && !string.IsNullOrEmpty(attributes[0].Value)) 34 | writer.WriteValue(attributes[0].Value); 35 | else 36 | writer.WriteValue(value?.ToString()); 37 | } 38 | } -------------------------------------------------------------------------------- /CRD/Views/AccountPageView.axaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |