Add - Added setting to use sonarr numbering instead of crunchyroll numbering
Chg - History "Refresh All" now shows in more detail what is being updated Chg - Calendar design changes - highlight "Premiere" episodes Fix - Crash caused by using sonarr Fix - Memory leak caused by progress bar Fix - Sometimes it downloaded Hardsub because it didn't know the language
This commit is contained in:
parent
9e975062dc
commit
7b021940c3
|
@ -4,16 +4,14 @@
|
||||||
xmlns:sty="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
|
xmlns:sty="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
|
||||||
x:Class="CRD.App"
|
x:Class="CRD.App"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Dark">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
<Application.DataTemplates>
|
<Application.DataTemplates>
|
||||||
<crd:ViewLocator/>
|
<crd:ViewLocator />
|
||||||
</Application.DataTemplates>
|
</Application.DataTemplates>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<sty:FluentAvaloniaTheme/>
|
<sty:FluentAvaloniaTheme PreferSystemTheme="True" PreferUserAccentColor="True"/>
|
||||||
<StyleInclude Source="avares://CRD/Styling/ControlsGalleryStyles.axaml" />
|
<StyleInclude Source="avares://CRD/Styling/ControlsGalleryStyles.axaml" />
|
||||||
<StyleInclude Source="avares://CRD/Assets/Icons.axaml"></StyleInclude>
|
<StyleInclude Source="avares://CRD/Assets/Icons.axaml"></StyleInclude>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class CrEpisode(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public CrunchySeriesList EpisodeData(CrunchyEpisodeList dlEpisodes){
|
public async Task<CrunchySeriesList> EpisodeData(CrunchyEpisodeList dlEpisodes){
|
||||||
bool serieshasversions = true;
|
bool serieshasversions = true;
|
||||||
|
|
||||||
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||||
|
@ -56,7 +56,7 @@ public class CrEpisode(){
|
||||||
foreach (var episode in dlEpisodes.Data){
|
foreach (var episode in dlEpisodes.Data){
|
||||||
|
|
||||||
if (crunInstance.CrunOptions.History){
|
if (crunInstance.CrunOptions.History){
|
||||||
crunInstance.CrHistory.UpdateWithEpisode(episode);
|
await crunInstance.CrHistory.UpdateWithEpisode(episode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the episode array
|
// Prepare the episode array
|
||||||
|
|
|
@ -164,7 +164,6 @@ public class Crunchyroll{
|
||||||
RefreshSonarr();
|
RefreshSonarr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
calendarLanguage = new(){
|
calendarLanguage = new(){
|
||||||
{ "en-us", "https://www.crunchyroll.com/simulcastcalendar" },
|
{ "en-us", "https://www.crunchyroll.com/simulcastcalendar" },
|
||||||
{ "es", "https://www.crunchyroll.com/es/simulcastcalendar" },
|
{ "es", "https://www.crunchyroll.com/es/simulcastcalendar" },
|
||||||
|
@ -181,8 +180,8 @@ public class Crunchyroll{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void RefreshSonarr(){
|
public async void RefreshSonarr(){
|
||||||
if (CrunOptions.SonarrProperties != null && !string.IsNullOrEmpty(CrunOptions.SonarrProperties.ApiKey)){
|
await SonarrClient.Instance.CheckSonarrSettings();
|
||||||
SonarrClient.Instance.SetApiUrl();
|
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
|
||||||
SonarrSeries = await SonarrClient.Instance.GetSeries();
|
SonarrSeries = await SonarrClient.Instance.GetSeries();
|
||||||
CrHistory.MatchHistorySeriesWithSonarr(true);
|
CrHistory.MatchHistorySeriesWithSonarr(true);
|
||||||
}
|
}
|
||||||
|
@ -221,8 +220,6 @@ public class Crunchyroll{
|
||||||
|
|
||||||
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
|
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
|
||||||
|
|
||||||
// Console.WriteLine($"Day: {dayName}, Date: {date}");
|
|
||||||
|
|
||||||
CalendarDay calDay = new CalendarDay();
|
CalendarDay calDay = new CalendarDay();
|
||||||
|
|
||||||
calDay.CalendarEpisodes = new List<CalendarEpisode>();
|
calDay.CalendarEpisodes = new List<CalendarEpisode>();
|
||||||
|
@ -242,14 +239,10 @@ public class Crunchyroll{
|
||||||
var episodeLink = episode.SelectSingleNode(".//a[contains(@class, 'available-episode-link')]")?.GetAttributeValue("href", "No link");
|
var episodeLink = episode.SelectSingleNode(".//a[contains(@class, 'available-episode-link')]")?.GetAttributeValue("href", "No link");
|
||||||
var thumbnailUrl = episode.SelectSingleNode(".//img[contains(@class, 'thumbnail')]")?.GetAttributeValue("src", "No image");
|
var thumbnailUrl = episode.SelectSingleNode(".//img[contains(@class, 'thumbnail')]")?.GetAttributeValue("src", "No image");
|
||||||
var isPremiumOnly = episode.SelectSingleNode(".//svg[contains(@class, 'premium-flag')]") != null;
|
var isPremiumOnly = episode.SelectSingleNode(".//svg[contains(@class, 'premium-flag')]") != null;
|
||||||
|
var isPremiere = episode.SelectSingleNode(".//div[contains(@class, 'premiere-flag')]") != null;
|
||||||
var seasonName = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim();
|
var seasonName = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim();
|
||||||
var episodeNumber = episode.SelectSingleNode(".//meta[contains(@itemprop, 'episodeNumber')]")?.GetAttributeValue("content", "?");
|
var episodeNumber = episode.SelectSingleNode(".//meta[contains(@itemprop, 'episodeNumber')]")?.GetAttributeValue("content", "?");
|
||||||
|
|
||||||
// Console.WriteLine($" Time: {episodeTime} (Has Passed: {hasPassed}), Episode: {episodeName}");
|
|
||||||
// Console.WriteLine($" Season Link: {seasonLink}");
|
|
||||||
// Console.WriteLine($" Episode Link: {episodeLink}");
|
|
||||||
// Console.WriteLine($" Thumbnail URL: {thumbnailUrl}");
|
|
||||||
|
|
||||||
CalendarEpisode calEpisode = new CalendarEpisode();
|
CalendarEpisode calEpisode = new CalendarEpisode();
|
||||||
|
|
||||||
calEpisode.DateTime = episodeTime;
|
calEpisode.DateTime = episodeTime;
|
||||||
|
@ -259,6 +252,7 @@ public class Crunchyroll{
|
||||||
calEpisode.EpisodeUrl = episodeLink;
|
calEpisode.EpisodeUrl = episodeLink;
|
||||||
calEpisode.ThumbnailUrl = thumbnailUrl;
|
calEpisode.ThumbnailUrl = thumbnailUrl;
|
||||||
calEpisode.IsPremiumOnly = isPremiumOnly;
|
calEpisode.IsPremiumOnly = isPremiumOnly;
|
||||||
|
calEpisode.IsPremiere = isPremiere;
|
||||||
calEpisode.SeasonName = seasonName;
|
calEpisode.SeasonName = seasonName;
|
||||||
calEpisode.EpisodeNumber = episodeNumber;
|
calEpisode.EpisodeNumber = episodeNumber;
|
||||||
|
|
||||||
|
@ -267,7 +261,6 @@ public class Crunchyroll{
|
||||||
}
|
}
|
||||||
|
|
||||||
week.CalendarDays.Add(calDay);
|
week.CalendarDays.Add(calDay);
|
||||||
// Console.WriteLine();
|
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
Console.WriteLine("No days found in the HTML document.");
|
Console.WriteLine("No days found in the HTML document.");
|
||||||
|
@ -291,10 +284,19 @@ public class Crunchyroll{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sList = CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
|
var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
|
||||||
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang);
|
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang);
|
||||||
var metas = selected.Values.ToList();
|
var metas = selected.Values.ToList();
|
||||||
|
|
||||||
foreach (var crunchyEpMeta in metas){
|
foreach (var crunchyEpMeta in metas){
|
||||||
|
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||||
|
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||||
|
if (historyEpisode != null){
|
||||||
|
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||||
|
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Queue.Add(crunchyEpMeta);
|
Queue.Add(crunchyEpMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,6 +312,14 @@ public class Crunchyroll{
|
||||||
|
|
||||||
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
||||||
if (crunchyEpMeta.Data?.First().Playback != null){
|
if (crunchyEpMeta.Data?.First().Playback != null){
|
||||||
|
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||||
|
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||||
|
if (historyEpisode != null){
|
||||||
|
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||||
|
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Queue.Add(crunchyEpMeta);
|
Queue.Add(crunchyEpMeta);
|
||||||
} else{
|
} else{
|
||||||
failed = true;
|
failed = true;
|
||||||
|
@ -559,10 +569,11 @@ public class Crunchyroll{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool dlFailed = false;
|
bool dlFailed = false;
|
||||||
bool dlVideoOnce = false;
|
bool dlVideoOnce = false;
|
||||||
|
|
||||||
if (data.Data != null)
|
if (data.Data != null){
|
||||||
foreach (CrunchyEpMetaData epMeta in data.Data){
|
foreach (CrunchyEpMetaData epMeta in data.Data){
|
||||||
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
||||||
|
|
||||||
|
@ -1065,6 +1076,7 @@ public class Crunchyroll{
|
||||||
if (File.Exists($"{tsFile}.video.m4s")){
|
if (File.Exists($"{tsFile}.video.m4s")){
|
||||||
File.Delete($"{tsFile}.video.m4s");
|
File.Delete($"{tsFile}.video.m4s");
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s");
|
File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s");
|
||||||
} catch (IOException ex){
|
} catch (IOException ex){
|
||||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||||
|
@ -1111,6 +1123,7 @@ public class Crunchyroll{
|
||||||
if (File.Exists($"{tsFile}.audio.m4s")){
|
if (File.Exists($"{tsFile}.audio.m4s")){
|
||||||
File.Delete($"{tsFile}.audio.m4s");
|
File.Delete($"{tsFile}.audio.m4s");
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s");
|
File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s");
|
||||||
} catch (IOException ex){
|
} catch (IOException ex){
|
||||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||||
|
@ -1226,7 +1239,7 @@ public class Crunchyroll{
|
||||||
|
|
||||||
await Task.Delay(options.Waittime);
|
await Task.Delay(options.Waittime);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// variables.Add(new Variable("height", quality == 0 ? plQuality.Last().RESOLUTION.Height : plQuality[quality - 1].RESOLUTION.Height, false));
|
// variables.Add(new Variable("height", quality == 0 ? plQuality.Last().RESOLUTION.Height : plQuality[quality - 1].RESOLUTION.Height, false));
|
||||||
// variables.Add(new Variable("width", quality == 0 ? plQuality.Last().RESOLUTION.Width : plQuality[quality - 1].RESOLUTION.Width, false));
|
// variables.Add(new Variable("width", quality == 0 ? plQuality.Last().RESOLUTION.Width : plQuality[quality - 1].RESOLUTION.Width, false));
|
||||||
|
@ -1476,7 +1489,7 @@ public class Crunchyroll{
|
||||||
var stream = hardsub.Value;
|
var stream = hardsub.Value;
|
||||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||||
Url = stream.Url,
|
Url = stream.Url,
|
||||||
HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
|
HardsubLocale = stream.Hlang
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1552,7 +1565,7 @@ public class Crunchyroll{
|
||||||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||||
|
|
||||||
if (playbackRequestResponse.IsOk){
|
if (playbackRequestResponse.IsOk){
|
||||||
// temppbData = Helpers.Deserialize<PlaybackData>(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ??
|
// var temppbData2 = Helpers.Deserialize<PlaybackData>(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ??
|
||||||
// new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
// new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||||
|
|
||||||
temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||||
|
@ -1569,7 +1582,7 @@ public class Crunchyroll{
|
||||||
var stream = hardsub.Value;
|
var stream = hardsub.Value;
|
||||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||||
Url = stream.Url,
|
Url = stream.Url,
|
||||||
HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
|
HardsubLocale = stream.Hlang
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1578,7 +1591,7 @@ public class Crunchyroll{
|
||||||
HardsubLocale = Locale.DefaulT
|
HardsubLocale = Locale.DefaulT
|
||||||
};
|
};
|
||||||
|
|
||||||
if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_dash"] = derivedPlayCrunchyStreams;
|
if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_switch_dash"] = derivedPlayCrunchyStreams;
|
||||||
|
|
||||||
temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List<string>{ playStream.Bifs }, MediaId = mediaId };
|
temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List<string>{ playStream.Bifs }, MediaId = mediaId };
|
||||||
temppbData.Meta.Subtitles = new Subtitles();
|
temppbData.Meta.Subtitles = new Subtitles();
|
||||||
|
|
|
@ -80,8 +80,27 @@ public class History(){
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
|
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HistoryEpisode? GetHistoryEpisode(string? seriesId, string? seasonId, string episodeId){
|
||||||
|
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||||
|
|
||||||
public async void UpdateWithEpisode(CrunchyEpisode episodeParam){
|
if (historySeries != null){
|
||||||
|
var historySeason = historySeries.Seasons.Find(s => s.SeasonId == seasonId);
|
||||||
|
|
||||||
|
if (historySeason != null){
|
||||||
|
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||||
|
|
||||||
|
if (historyEpisode != null){
|
||||||
|
|
||||||
|
return historyEpisode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){
|
||||||
var episode = episodeParam;
|
var episode = episodeParam;
|
||||||
|
|
||||||
if (episode.Versions != null){
|
if (episode.Versions != null){
|
||||||
|
@ -137,23 +156,23 @@ public class History(){
|
||||||
|
|
||||||
historySeries.UpdateNewEpisodes();
|
historySeries.UpdateNewEpisodes();
|
||||||
} else{
|
} else{
|
||||||
var newHistorySeries = new HistorySeries{
|
historySeries = new HistorySeries{
|
||||||
SeriesTitle = episode.SeriesTitle,
|
SeriesTitle = episode.SeriesTitle,
|
||||||
SeriesId = episode.SeriesId,
|
SeriesId = episode.SeriesId,
|
||||||
Seasons =[],
|
Seasons =[],
|
||||||
};
|
};
|
||||||
crunInstance.HistoryList.Add(newHistorySeries);
|
crunInstance.HistoryList.Add(historySeries);
|
||||||
var newSeason = NewHistorySeason(episode);
|
var newSeason = NewHistorySeason(episode);
|
||||||
|
|
||||||
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||||
if (series?.Data != null){
|
if (series?.Data != null){
|
||||||
newHistorySeries.SeriesDescription = series.Data.First().Description;
|
historySeries.SeriesDescription = series.Data.First().Description;
|
||||||
newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||||
newHistorySeries.SeriesTitle = series.Data.First().Title;
|
historySeries.SeriesTitle = series.Data.First().Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
newHistorySeries.Seasons.Add(newSeason);
|
historySeries.Seasons.Add(newSeason);
|
||||||
newHistorySeries.UpdateNewEpisodes();
|
historySeries.UpdateNewEpisodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
||||||
|
@ -163,10 +182,11 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
MatchHistorySeriesWithSonarr(false);
|
MatchHistorySeriesWithSonarr(false);
|
||||||
|
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||||
UpdateHistoryFile();
|
UpdateHistoryFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void UpdateWithSeasonData(CrunchyEpisodeList seasonData){
|
public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData){
|
||||||
if (seasonData.Data != null){
|
if (seasonData.Data != null){
|
||||||
var firstEpisode = seasonData.Data.First();
|
var firstEpisode = seasonData.Data.First();
|
||||||
var seriesId = firstEpisode.SeriesId;
|
var seriesId = firstEpisode.SeriesId;
|
||||||
|
@ -203,7 +223,6 @@ public class History(){
|
||||||
historyEpisode.EpisodeId = crunchyEpisode.Id;
|
historyEpisode.EpisodeId = crunchyEpisode.Id;
|
||||||
historyEpisode.Episode = crunchyEpisode.Episode;
|
historyEpisode.Episode = crunchyEpisode.Episode;
|
||||||
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
|
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +239,12 @@ public class History(){
|
||||||
|
|
||||||
historySeries.UpdateNewEpisodes();
|
historySeries.UpdateNewEpisodes();
|
||||||
} else{
|
} else{
|
||||||
var newHistorySeries = new HistorySeries{
|
historySeries = new HistorySeries{
|
||||||
SeriesTitle = firstEpisode.SeriesTitle,
|
SeriesTitle = firstEpisode.SeriesTitle,
|
||||||
SeriesId = firstEpisode.SeriesId,
|
SeriesId = firstEpisode.SeriesId,
|
||||||
Seasons =[],
|
Seasons =[],
|
||||||
};
|
};
|
||||||
crunInstance.HistoryList.Add(newHistorySeries);
|
crunInstance.HistoryList.Add(historySeries);
|
||||||
|
|
||||||
var newSeason = NewHistorySeason(seasonData, firstEpisode);
|
var newSeason = NewHistorySeason(seasonData, firstEpisode);
|
||||||
|
|
||||||
|
@ -233,26 +252,26 @@ public class History(){
|
||||||
|
|
||||||
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||||
if (series?.Data != null){
|
if (series?.Data != null){
|
||||||
newHistorySeries.SeriesDescription = series.Data.First().Description;
|
historySeries.SeriesDescription = series.Data.First().Description;
|
||||||
newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||||
newHistorySeries.SeriesTitle = series.Data.First().Title;
|
historySeries.SeriesTitle = series.Data.First().Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
newHistorySeries.Seasons.Add(newSeason);
|
historySeries.Seasons.Add(newSeason);
|
||||||
|
|
||||||
newHistorySeries.UpdateNewEpisodes();
|
historySeries.UpdateNewEpisodes();
|
||||||
|
}
|
||||||
|
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
||||||
|
crunInstance.HistoryList.Clear();
|
||||||
|
foreach (var item in sortedList){
|
||||||
|
crunInstance.HistoryList.Add(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
MatchHistorySeriesWithSonarr(false);
|
||||||
crunInstance.HistoryList.Clear();
|
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||||
foreach (var item in sortedList){
|
UpdateHistoryFile();
|
||||||
crunInstance.HistoryList.Add(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MatchHistorySeriesWithSonarr(false);
|
|
||||||
UpdateHistoryFile();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSeriesThumbnail(CrSeriesBase series){
|
private string GetSeriesThumbnail(CrSeriesBase series){
|
||||||
|
@ -322,6 +341,11 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MatchHistorySeriesWithSonarr(bool updateAll){
|
public void MatchHistorySeriesWithSonarr(bool updateAll){
|
||||||
|
|
||||||
|
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var historySeries in crunInstance.HistoryList){
|
foreach (var historySeries in crunInstance.HistoryList){
|
||||||
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||||
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle);
|
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle);
|
||||||
|
@ -334,7 +358,11 @@ public class History(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
|
public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
|
||||||
|
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||||
var episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
|
var episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
|
||||||
|
|
||||||
|
@ -372,7 +400,8 @@ public class History(){
|
||||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||||
episodes.Remove(episode);
|
episodes.Remove(episode);
|
||||||
} else{
|
} else{
|
||||||
var episode1 = episodes.Find(ele => !string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8);
|
var episode1 = episodes.Find(ele =>
|
||||||
|
!string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8);
|
||||||
|
|
||||||
if (episode1 != null){
|
if (episode1 != null){
|
||||||
historyEpisode.SonarrEpisodeId = episode1.Id + "";
|
historyEpisode.SonarrEpisodeId = episode1.Id + "";
|
||||||
|
@ -395,11 +424,7 @@ public class History(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +467,8 @@ public class History(){
|
||||||
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
|
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int LevenshteinDistance(string source, string target){
|
|
||||||
|
public int LevenshteinDistance(string source, string target){
|
||||||
if (string.IsNullOrEmpty(source)){
|
if (string.IsNullOrEmpty(source)){
|
||||||
return string.IsNullOrEmpty(target) ? 0 : target.Length;
|
return string.IsNullOrEmpty(target) ? 0 : target.Length;
|
||||||
}
|
}
|
||||||
|
@ -454,35 +480,32 @@ public class History(){
|
||||||
int n = source.Length;
|
int n = source.Length;
|
||||||
int m = target.Length;
|
int m = target.Length;
|
||||||
|
|
||||||
// Create two work arrays of integer distances.
|
// Use a single array for distances.
|
||||||
int[] previousDistances = new int[m + 1];
|
int[] distances = new int[m + 1];
|
||||||
int[] currentDistances = new int[m + 1];
|
|
||||||
|
|
||||||
// Initialize the previous distance array.
|
// Initialize the distance array.
|
||||||
for (int j = 0; j <= m; j++){
|
for (int j = 0; j <= m; j++){
|
||||||
previousDistances[j] = j;
|
distances[j] = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i <= n; i++){
|
for (int i = 1; i <= n; i++){
|
||||||
// Initialize the current distance array.
|
int previousDiagonal = distances[0];
|
||||||
currentDistances[0] = i;
|
distances[0] = i;
|
||||||
|
|
||||||
for (int j = 1; j <= m; j++){
|
for (int j = 1; j <= m; j++){
|
||||||
|
int previousDistance = distances[j];
|
||||||
int cost = (target[j - 1] == source[i - 1]) ? 0 : 1;
|
int cost = (target[j - 1] == source[i - 1]) ? 0 : 1;
|
||||||
|
|
||||||
currentDistances[j] = Math.Min(
|
distances[j] = Math.Min(
|
||||||
Math.Min(currentDistances[j - 1] + 1, previousDistances[j] + 1),
|
Math.Min(distances[j - 1] + 1, distances[j] + 1),
|
||||||
previousDistances[j - 1] + cost);
|
previousDiagonal + cost);
|
||||||
}
|
|
||||||
|
|
||||||
// Swap the arrays for the next iteration.
|
previousDiagonal = previousDistance;
|
||||||
var temp = previousDistances;
|
}
|
||||||
previousDistances = currentDistances;
|
|
||||||
currentDistances = temp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final distance is in the previous distance array.
|
// The final distance is in the last cell.
|
||||||
return previousDistances[m];
|
return distances[m];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,6 +553,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool FetchingData{ get; set; }
|
||||||
|
|
||||||
public async Task LoadImage(){
|
public async Task LoadImage(){
|
||||||
try{
|
try{
|
||||||
using (var client = new HttpClient()){
|
using (var client = new HttpClient()){
|
||||||
|
@ -574,6 +600,11 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetFetchingData(){
|
||||||
|
FetchingData = true;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
|
}
|
||||||
|
|
||||||
public async Task AddNewMissingToDownloads(){
|
public async Task AddNewMissingToDownloads(){
|
||||||
bool foundWatched = false;
|
bool foundWatched = false;
|
||||||
|
|
||||||
|
@ -600,7 +631,12 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchData(string? seasonId){
|
public async Task FetchData(string? seasonId){
|
||||||
|
FetchingData = true;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
await Crunchyroll.Instance.CrHistory.UpdateSeries(SeriesId, seasonId);
|
await Crunchyroll.Instance.CrHistory.UpdateSeries(SeriesId, seasonId);
|
||||||
|
FetchingData = false;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
|
Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false,this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,9 +149,6 @@ public class CfgManager{
|
||||||
|
|
||||||
public static void WriteJsonToFile(string pathToFile, object obj){
|
public static void WriteJsonToFile(string pathToFile, object obj){
|
||||||
try{
|
try{
|
||||||
// Serialize the object to a JSON string.
|
|
||||||
var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented);
|
|
||||||
|
|
||||||
// Check if the directory exists; if not, create it.
|
// Check if the directory exists; if not, create it.
|
||||||
string directoryPath = Path.GetDirectoryName(pathToFile);
|
string directoryPath = Path.GetDirectoryName(pathToFile);
|
||||||
if (!Directory.Exists(directoryPath)){
|
if (!Directory.Exists(directoryPath)){
|
||||||
|
@ -159,8 +156,13 @@ public class CfgManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (fileLock){
|
lock (fileLock){
|
||||||
// Write the JSON string to file. Creates the file if it does not exist.
|
// Write the JSON string to file using a streaming approach.
|
||||||
File.WriteAllText(pathToFile, jsonString);
|
using (var fileStream = new FileStream(pathToFile, FileMode.Create, FileAccess.Write))
|
||||||
|
using (var streamWriter = new StreamWriter(fileStream))
|
||||||
|
using (var jsonWriter = new JsonTextWriter(streamWriter){ Formatting = Formatting.Indented }){
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
serializer.Serialize(jsonWriter, obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex){
|
} catch (Exception ex){
|
||||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||||
|
|
|
@ -421,7 +421,7 @@ public class HlsDownloader{
|
||||||
try{
|
try{
|
||||||
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead);
|
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
return await response.Content.ReadAsByteArrayAsync();
|
return await ReadContentAsByteArrayAsync(response.Content);
|
||||||
} catch (HttpRequestException ex){
|
} catch (HttpRequestException ex){
|
||||||
// Log retry attempts
|
// Log retry attempts
|
||||||
string partType = isKey ? "Key" : "Part";
|
string partType = isKey ? "Key" : "Part";
|
||||||
|
@ -437,6 +437,14 @@ public class HlsDownloader{
|
||||||
return null; // Should not reach here
|
return null; // Should not reach here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]> ReadContentAsByteArrayAsync(HttpContent content){
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
using (var contentStream = await content.ReadAsStreamAsync()){
|
||||||
|
await contentStream.CopyToAsync(memoryStream, 81920);
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private HttpRequestMessage CloneHttpRequestMessage(HttpRequestMessage originalRequest){
|
private HttpRequestMessage CloneHttpRequestMessage(HttpRequestMessage originalRequest){
|
||||||
var clone = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri){
|
var clone = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri){
|
||||||
Content = originalRequest.Content?.Clone(),
|
Content = originalRequest.Content?.Clone(),
|
||||||
|
|
|
@ -38,7 +38,11 @@ public class Helpers{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Locale.DefaulT; // Return default if not found
|
if (string.IsNullOrEmpty(value)){
|
||||||
|
return Locale.DefaulT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Locale.Unknown; // Return default if not found
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GenerateSessionId(){
|
public static string GenerateSessionId(){
|
||||||
|
@ -88,22 +92,44 @@ public class Helpers{
|
||||||
return CosineSimilarity(vector1, vector2);
|
return CosineSimilarity(vector1, vector2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, double> ComputeWordFrequency(string text){
|
private static readonly char[] Delimiters ={ ' ', ',', '.', ';', ':', '-', '_', '\'' };
|
||||||
var wordFrequency = new Dictionary<string, double>();
|
|
||||||
var words = text.Split(new[]{ ' ', ',', '.', ';', ':', '-', '_', '\'' }, StringSplitOptions.RemoveEmptyEntries);
|
public static Dictionary<string, double> ComputeWordFrequency(string text){
|
||||||
|
var wordFrequency = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var words = SplitText(text);
|
||||||
|
|
||||||
foreach (var word in words){
|
foreach (var word in words){
|
||||||
var lowerWord = word.ToLower();
|
if (wordFrequency.TryGetValue(word, out double count)){
|
||||||
if (!wordFrequency.ContainsKey(lowerWord)){
|
wordFrequency[word] = count + 1;
|
||||||
wordFrequency[lowerWord] = 0;
|
} else{
|
||||||
|
wordFrequency[word] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
wordFrequency[lowerWord]++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wordFrequency;
|
return wordFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<string> SplitText(string text){
|
||||||
|
var words = new List<string>();
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i < text.Length; i++){
|
||||||
|
if (Array.IndexOf(Delimiters, text[i]) >= 0){
|
||||||
|
if (i > start){
|
||||||
|
words.Add(text.Substring(start, i - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < text.Length){
|
||||||
|
words.Add(text.Substring(start));
|
||||||
|
}
|
||||||
|
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static double CosineSimilarity(Dictionary<string, double> vector1, Dictionary<string, double> vector2){
|
private static double CosineSimilarity(Dictionary<string, double> vector1, Dictionary<string, double> vector2){
|
||||||
var intersection = vector1.Keys.Intersect(vector2.Keys);
|
var intersection = vector1.Keys.Intersect(vector2.Keys);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ using Newtonsoft.Json;
|
||||||
namespace CRD.Utils.Sonarr;
|
namespace CRD.Utils.Sonarr;
|
||||||
|
|
||||||
public class SonarrClient{
|
public class SonarrClient{
|
||||||
private string apiUrl;
|
private string? apiUrl;
|
||||||
|
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
@ -50,11 +50,39 @@ public class SonarrClient{
|
||||||
public void SetApiUrl(){
|
public void SetApiUrl(){
|
||||||
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) properties = Crunchyroll.Instance.CrunOptions.SonarrProperties;
|
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) properties = Crunchyroll.Instance.CrunOptions.SonarrProperties;
|
||||||
|
|
||||||
if (properties != null){
|
if (properties != null ){
|
||||||
apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{properties.Host}:{properties.Port}{(properties.UrlBase ?? "")}/api";
|
apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{(!string.IsNullOrEmpty(properties.Host) ? properties.Host : "localhost")}:{properties.Port}{(properties.UrlBase ?? "")}/api";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CheckSonarrSettings(){
|
||||||
|
|
||||||
|
SetApiUrl();
|
||||||
|
|
||||||
|
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null){
|
||||||
|
Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false;
|
||||||
|
} else{
|
||||||
|
Crunchyroll.Instance.CrunOptions.SonarrProperties = new SonarrProperties(){SonarrEnabled = false};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"[DEBUG] [SonarrClient.CheckSonarrSettings] Endpoint URL: '{apiUrl}'");
|
||||||
|
|
||||||
|
var request = CreateRequestMessage($"{apiUrl}", HttpMethod.Get);
|
||||||
|
HttpResponseMessage response;
|
||||||
|
|
||||||
|
try{
|
||||||
|
response = await httpClient.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = true;
|
||||||
|
} catch (Exception ex){
|
||||||
|
Debug.WriteLine($"[ERROR] [SonarrClient.GetJson] Endpoint URL: '{apiUrl}', {ex}");
|
||||||
|
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<SonarrSeries>> GetSeries(){
|
public async Task<List<SonarrSeries>> GetSeries(){
|
||||||
var json = await GetJson($"/v3/series{(true ? $"?includeSeasonImages={true}" : "")}");
|
var json = await GetJson($"/v3/series{(true ? $"?includeSeasonImages={true}" : "")}");
|
||||||
|
|
||||||
|
@ -151,4 +179,7 @@ public class SonarrProperties(){
|
||||||
public bool UseSsl{ get; set; }
|
public bool UseSsl{ get; set; }
|
||||||
|
|
||||||
public string? UrlBase{ get; set; }
|
public string? UrlBase{ get; set; }
|
||||||
|
|
||||||
|
public bool UseSonarrNumbering{ get; set; }
|
||||||
|
public bool SonarrEnabled{ get; set; }
|
||||||
}
|
}
|
|
@ -7,8 +7,6 @@ using System.Threading.Tasks;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Views;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs;
|
||||||
|
|
||||||
|
@ -36,6 +34,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
|
||||||
public string? EpisodeNumber{ get; set; }
|
public string? EpisodeNumber{ get; set; }
|
||||||
|
|
||||||
public bool IsPremiumOnly{ get; set; }
|
public bool IsPremiumOnly{ get; set; }
|
||||||
|
public bool IsPremiere{ get; set; }
|
||||||
|
|
||||||
public string? SeasonName{ get; set; }
|
public string? SeasonName{ get; set; }
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class Caption{
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HardSub{
|
public class HardSub{
|
||||||
public string? Hlang{ get; set; }
|
public Locale? Hlang{ get; set; }
|
||||||
public string? Url{ get; set; }
|
public string? Url{ get; set; }
|
||||||
public string? Quality{ get; set; }
|
public string? Quality{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,55 @@ public struct CrunchyEpisodeList{
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyEpisode{
|
public struct CrunchyEpisode{
|
||||||
[JsonProperty("next_episode_id")] public string NextEpisodeId{ get; set; }
|
[JsonProperty("next_episode_id")]
|
||||||
[JsonProperty("series_id")] public string SeriesId{ get; set; }
|
public string NextEpisodeId{ get; set; }
|
||||||
[JsonProperty("season_number")] public int SeasonNumber{ get; set; }
|
|
||||||
[JsonProperty("next_episode_title")] public string NextEpisodeTitle{ get; set; }
|
[JsonProperty("series_id")]
|
||||||
[JsonProperty("availability_notes")] public string AvailabilityNotes{ get; set; }
|
public string SeriesId{ get; set; }
|
||||||
[JsonProperty("duration_ms")] public int DurationMs{ get; set; }
|
|
||||||
[JsonProperty("series_slug_title")] public string SeriesSlugTitle{ get; set; }
|
[JsonProperty("season_number")]
|
||||||
[JsonProperty("series_title")] public string SeriesTitle{ get; set; }
|
public int SeasonNumber{ get; set; }
|
||||||
[JsonProperty("is_dubbed")] public bool IsDubbed{ get; set; }
|
|
||||||
public List<EpisodeVersion>? Versions{ get; set; } // Assume Version is defined elsewhere.
|
[JsonProperty("next_episode_title")]
|
||||||
|
public string NextEpisodeTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("availability_notes")]
|
||||||
|
public string AvailabilityNotes{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("duration_ms")]
|
||||||
|
public int DurationMs{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("series_slug_title")]
|
||||||
|
public string SeriesSlugTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("series_title")]
|
||||||
|
public string SeriesTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("is_dubbed")]
|
||||||
|
public bool IsDubbed{ get; set; }
|
||||||
|
|
||||||
|
public List<EpisodeVersion>? Versions{ get; set; }
|
||||||
public string Identifier{ get; set; }
|
public string Identifier{ get; set; }
|
||||||
[JsonProperty("sequence_number")] public float SequenceNumber{ get; set; }
|
|
||||||
[JsonProperty("eligible_region")] public string EligibleRegion{ get; set; }
|
[JsonProperty("sequence_number")]
|
||||||
[JsonProperty("availability_starts")] public DateTime? AvailabilityStarts{ get; set; }
|
public float SequenceNumber{ get; set; }
|
||||||
public Images? Images{ get; set; } // Assume Images is a struct or class you've defined elsewhere.
|
|
||||||
[JsonProperty("season_id")] public string SeasonId{ get; set; }
|
[JsonProperty("eligible_region")]
|
||||||
[JsonProperty("seo_title")] public string SeoTitle{ get; set; }
|
public string EligibleRegion{ get; set; }
|
||||||
[JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; }
|
|
||||||
|
[JsonProperty("availability_starts")]
|
||||||
|
public DateTime? AvailabilityStarts{ get; set; }
|
||||||
|
|
||||||
|
public Images? Images{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("season_id")]
|
||||||
|
public string SeasonId{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("seo_title")]
|
||||||
|
public string SeoTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("is_premium_only")]
|
||||||
|
public bool IsPremiumOnly{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("extended_maturity_rating")]
|
[JsonProperty("extended_maturity_rating")]
|
||||||
public Dictionary<string, object> ExtendedMaturityRating{ get; set; }
|
public Dictionary<string, object> ExtendedMaturityRating{ get; set; }
|
||||||
|
@ -41,104 +72,119 @@ public struct CrunchyEpisode{
|
||||||
[JsonProperty("premium_available_date")]
|
[JsonProperty("premium_available_date")]
|
||||||
public DateTime? PremiumAvailableDate{ get; set; }
|
public DateTime? PremiumAvailableDate{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("season_title")] public string SeasonTitle{ get; set; }
|
[JsonProperty("season_title")]
|
||||||
[JsonProperty("seo_description")] public string SeoDescription{ get; set; }
|
public string SeasonTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("seo_description")]
|
||||||
|
public string SeoDescription{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("audio_locale")]
|
||||||
|
public string AudioLocale{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("audio_locale")] public string AudioLocale{ get; set; }
|
|
||||||
public string Id{ get; set; }
|
public string Id{ get; set; }
|
||||||
[JsonProperty("media_type")] public MediaType? MediaType{ get; set; } // MediaType should be an enum you define based on possible values.
|
|
||||||
[JsonProperty("availability_ends")] public DateTime? AvailabilityEnds{ get; set; }
|
[JsonProperty("media_type")]
|
||||||
[JsonProperty("free_available_date")] public DateTime? FreeAvailableDate{ get; set; }
|
public MediaType? MediaType{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("availability_ends")]
|
||||||
|
public DateTime? AvailabilityEnds{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("free_available_date")]
|
||||||
|
public DateTime? FreeAvailableDate{ get; set; }
|
||||||
|
|
||||||
public string Playback{ get; set; }
|
public string Playback{ get; set; }
|
||||||
[JsonProperty("channel_id")] public ChannelId? ChannelId{ get; set; } // ChannelID should be an enum or struct.
|
|
||||||
|
[JsonProperty("channel_id")]
|
||||||
|
public ChannelId? ChannelId{ get; set; }
|
||||||
|
|
||||||
public string? Episode{ get; set; }
|
public string? Episode{ get; set; }
|
||||||
[JsonProperty("is_mature")] public bool IsMature{ get; set; }
|
|
||||||
[JsonProperty("listing_id")] public string ListingId{ get; set; }
|
[JsonProperty("is_mature")]
|
||||||
[JsonProperty("episode_air_date")] public DateTime? EpisodeAirDate{ get; set; }
|
public bool IsMature{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("listing_id")]
|
||||||
|
public string ListingId{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("episode_air_date")]
|
||||||
|
public DateTime? EpisodeAirDate{ get; set; }
|
||||||
|
|
||||||
public string Slug{ get; set; }
|
public string Slug{ get; set; }
|
||||||
[JsonProperty("available_date")] public DateTime? AvailableDate{ get; set; }
|
|
||||||
[JsonProperty("subtitle_locales")] public List<string> SubtitleLocales{ get; set; }
|
[JsonProperty("available_date")]
|
||||||
[JsonProperty("slug_title")] public string SlugTitle{ get; set; }
|
public DateTime? AvailableDate{ get; set; }
|
||||||
[JsonProperty("available_offline")] public bool AvailableOffline{ get; set; }
|
|
||||||
|
[JsonProperty("subtitle_locales")]
|
||||||
|
public List<string> SubtitleLocales{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slug_title")]
|
||||||
|
public string SlugTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("available_offline")]
|
||||||
|
public bool AvailableOffline{ get; set; }
|
||||||
|
|
||||||
public string Description{ get; set; }
|
public string Description{ get; set; }
|
||||||
[JsonProperty("is_subbed")] public bool IsSubbed{ get; set; }
|
|
||||||
[JsonProperty("premium_date")] public DateTime? PremiumDate{ get; set; }
|
[JsonProperty("is_subbed")]
|
||||||
[JsonProperty("upload_date")] public DateTime? UploadDate{ get; set; }
|
public bool IsSubbed{ get; set; }
|
||||||
[JsonProperty("season_slug_title")] public string SeasonSlugTitle{ get; set; }
|
|
||||||
|
[JsonProperty("premium_date")]
|
||||||
|
public DateTime? PremiumDate{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("upload_date")]
|
||||||
|
public DateTime? UploadDate{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("season_slug_title")]
|
||||||
|
public string SeasonSlugTitle{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("closed_captions_available")]
|
[JsonProperty("closed_captions_available")]
|
||||||
public bool ClosedCaptionsAvailable{ get; set; }
|
public bool ClosedCaptionsAvailable{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("episode_number")] public int? EpisodeNumber{ get; set; }
|
[JsonProperty("episode_number")]
|
||||||
[JsonProperty("season_tags")] public List<object> SeasonTags{ get; set; } // More specific type could be used if known.
|
public int? EpisodeNumber{ get; set; }
|
||||||
[JsonProperty("maturity_ratings")] public List<string> MaturityRatings{ get; set; } // MaturityRating should be defined based on possible values.
|
|
||||||
[JsonProperty("streams_link")] public string? StreamsLink{ get; set; }
|
[JsonProperty("season_tags")]
|
||||||
[JsonProperty("mature_blocked")] public bool? MatureBlocked{ get; set; }
|
public List<object> SeasonTags{ get; set; }
|
||||||
[JsonProperty("is_clip")] public bool IsClip{ get; set; }
|
|
||||||
[JsonProperty("hd_flag")] public bool HdFlag{ get; set; }
|
[JsonProperty("maturity_ratings")]
|
||||||
[JsonProperty("hide_season_title")] public bool? HideSeasonTitle{ get; set; }
|
public List<string> MaturityRatings{ get; set; }
|
||||||
[JsonProperty("hide_season_number")] public bool? HideSeasonNumber{ get; set; }
|
|
||||||
|
[JsonProperty("streams_link")]
|
||||||
|
public string? StreamsLink{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mature_blocked")]
|
||||||
|
public bool? MatureBlocked{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("is_clip")]
|
||||||
|
public bool IsClip{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hd_flag")]
|
||||||
|
public bool HdFlag{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hide_season_title")]
|
||||||
|
public bool? HideSeasonTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hide_season_number")]
|
||||||
|
public bool? HideSeasonNumber{ get; set; }
|
||||||
|
|
||||||
public bool? IsSelected{ get; set; }
|
public bool? IsSelected{ get; set; }
|
||||||
[JsonProperty("seq_id")] public string SeqId{ get; set; }
|
|
||||||
[JsonProperty("__links__")] public Links? Links{ get; set; }
|
[JsonProperty("seq_id")]
|
||||||
|
public string SeqId{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("__links__")]
|
||||||
|
public Links? Links{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// public struct CrunchyEpisode{
|
|
||||||
//
|
|
||||||
// public string channel_id{ get; set; }
|
|
||||||
// public bool is_mature{ get; set; }
|
|
||||||
// public string upload_date{ get; set; }
|
|
||||||
// public string free_available_date{ get; set; }
|
|
||||||
// public List<string> content_descriptors{ get; set; }
|
|
||||||
// public Dictionary<object, object> images{ get; set; } // Consider specifying actual key and value types if known
|
|
||||||
// public int season_sequence_number{ get; set; }
|
|
||||||
// public string audio_locale{ get; set; }
|
|
||||||
// public string title{ get; set; }
|
|
||||||
// public Dictionary<object, object>
|
|
||||||
// extended_maturity_rating{ get; set; } // Consider specifying actual key and value types if known
|
|
||||||
// public bool available_offline{ get; set; }
|
|
||||||
// public string identifier{ get; set; }
|
|
||||||
// public string listing_id{ get; set; }
|
|
||||||
// public List<string> season_tags{ get; set; }
|
|
||||||
// public string next_episode_id{ get; set; }
|
|
||||||
// public string next_episode_title{ get; set; }
|
|
||||||
// public bool is_subbed{ get; set; }
|
|
||||||
// public string slug{ get; set; }
|
|
||||||
// public List<Version> versions{ get; set; }
|
|
||||||
// public int season_number{ get; set; }
|
|
||||||
// public string availability_ends{ get; set; }
|
|
||||||
// public string eligible_region{ get; set; }
|
|
||||||
// public bool is_clip{ get; set; }
|
|
||||||
// public string description{ get; set; }
|
|
||||||
// public string seo_description{ get; set; }
|
|
||||||
// public bool is_premium_only{ get; set; }
|
|
||||||
// public string streams_link{ get; set; }
|
|
||||||
// public int episode_number{ get; set; }
|
|
||||||
// public bool closed_captions_available{ get; set; }
|
|
||||||
//
|
|
||||||
// public bool is_dubbed{ get; set; }
|
|
||||||
// public string seo_title{ get; set; }
|
|
||||||
// public long duration_ms{ get; set; }
|
|
||||||
// public string id{ get; set; }
|
|
||||||
// public string series_id{ get; set; }
|
|
||||||
// public string series_slug_title{ get; set; }
|
|
||||||
// public string episode_air_date{ get; set; }
|
|
||||||
// public bool hd_flag{ get; set; }
|
|
||||||
// public bool mature_blocked{ get; set; }
|
|
||||||
//
|
|
||||||
// public string availability_notes{ get; set; }
|
|
||||||
//
|
|
||||||
// public List<string> maturity_ratings{ get; set; }
|
|
||||||
// public string episode{ get; set; }
|
|
||||||
// public int sequence_number{ get; set; }
|
|
||||||
// public List<string> subtitle_locales{ get; set; }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
public struct Images{
|
public struct Images{
|
||||||
[JsonProperty("poster_tall")] public List<List<Image>>? PosterTall{ get; set; }
|
[JsonProperty("poster_tall")]
|
||||||
[JsonProperty("poster_wide")] public List<List<Image>>? PosterWide{ get; set; }
|
public List<List<Image>>? PosterTall{ get; set; }
|
||||||
[JsonProperty("promo_image")] public List<List<Image>>? PromoImage{ get; set; }
|
|
||||||
|
[JsonProperty("poster_wide")]
|
||||||
|
public List<List<Image>>? PosterWide{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("promo_image")]
|
||||||
|
public List<List<Image>>? PromoImage{ get; set; }
|
||||||
|
|
||||||
public List<List<Image>> Thumbnail{ get; set; }
|
public List<List<Image>> Thumbnail{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,12 +196,22 @@ public struct Image{
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EpisodeVersion{
|
public struct EpisodeVersion{
|
||||||
[JsonProperty("audio_locale")] public string AudioLocale{ get; set; }
|
[JsonProperty("audio_locale")]
|
||||||
|
public string AudioLocale{ get; set; }
|
||||||
|
|
||||||
public string Guid{ get; set; }
|
public string Guid{ get; set; }
|
||||||
[JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; }
|
|
||||||
[JsonProperty("media_guid")] public string? MediaGuid{ get; set; }
|
[JsonProperty("is_premium_only")]
|
||||||
|
public bool IsPremiumOnly{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("media_guid")]
|
||||||
|
public string? MediaGuid{ get; set; }
|
||||||
|
|
||||||
public bool Original{ get; set; }
|
public bool Original{ get; set; }
|
||||||
[JsonProperty("season_guid")] public string SeasonGuid{ get; set; }
|
|
||||||
|
[JsonProperty("season_guid")]
|
||||||
|
public string SeasonGuid{ get; set; }
|
||||||
|
|
||||||
public string Variant{ get; set; }
|
public string Variant{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +250,6 @@ public class CrunchyEpMeta{
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloadProgress{
|
public class DownloadProgress{
|
||||||
|
|
||||||
public bool IsDownloading = false;
|
public bool IsDownloading = false;
|
||||||
public bool Done = false;
|
public bool Done = false;
|
||||||
public bool Error = false;
|
public bool Error = false;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
|
||||||
|
namespace CRD.Utils.UI;
|
||||||
|
|
||||||
|
public class UiValueConverterCalendarBackground : IValueConverter{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){
|
||||||
|
if (value is bool boolValue){
|
||||||
|
return boolValue ? new SolidColorBrush(Color.Parse("#10f5d800")) : new SolidColorBrush(Color.Parse("#10FFFFFF"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SolidColorBrush(Color.Parse("#10FFFFFF"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture){
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
public ObservableCollection<HistorySeries> Items{ get; }
|
public ObservableCollection<HistorySeries> Items{ get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool? _showLoading = false;
|
private static bool _fetchingData;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public HistorySeries _selectedSeries;
|
public HistorySeries _selectedSeries;
|
||||||
|
@ -35,38 +35,52 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
partial void OnSelectedSeriesChanged(HistorySeries value){
|
partial void OnSelectedSeriesChanged(HistorySeries value){
|
||||||
Crunchyroll.Instance.SelectedSeries = value;
|
Crunchyroll.Instance.SelectedSeries = value;
|
||||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false));
|
NavToSeries();
|
||||||
_selectedSeries = null;
|
_selectedSeries = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void RemoveSeries(string? seriesId){
|
public void RemoveSeries(string? seriesId){
|
||||||
|
|
||||||
HistorySeries? objectToRemove = Crunchyroll.Instance.HistoryList.ToList().Find(se => se.SeriesId == seriesId) ?? null;
|
HistorySeries? objectToRemove = Crunchyroll.Instance.HistoryList.ToList().Find(se => se.SeriesId == seriesId) ?? null;
|
||||||
if (objectToRemove != null) {
|
if (objectToRemove != null){
|
||||||
Crunchyroll.Instance.HistoryList.Remove(objectToRemove);
|
Crunchyroll.Instance.HistoryList.Remove(objectToRemove);
|
||||||
Items.Remove(objectToRemove);
|
Items.Remove(objectToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void NavToSeries(){
|
public void NavToSeries(){
|
||||||
|
if (FetchingData){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false));
|
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void RefreshAll(){
|
public async void RefreshAll(){
|
||||||
|
FetchingData = true;
|
||||||
|
RaisePropertyChanged(nameof(FetchingData));
|
||||||
for (int i = 0; i < Items.Count; i++){
|
for (int i = 0; i < Items.Count; i++){
|
||||||
ShowLoading = true;
|
Items[i].SetFetchingData();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < Items.Count; i++){
|
||||||
|
FetchingData = true;
|
||||||
|
RaisePropertyChanged(nameof(FetchingData));
|
||||||
await Items[i].FetchData("");
|
await Items[i].FetchData("");
|
||||||
Items[i].UpdateNewEpisodes();
|
Items[i].UpdateNewEpisodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowLoading = false;
|
FetchingData = false;
|
||||||
|
RaisePropertyChanged(nameof(FetchingData));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
@ -74,7 +88,5 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
for (int i = 0; i < Items.Count; i++){
|
for (int i = 0; i < Items.Count; i++){
|
||||||
await Items[i].AddNewMissingToDownloads();
|
await Items[i].AddNewMissingToDownloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowLoading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
|
@ -100,6 +101,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _sonarrUseSsl = false;
|
private bool _sonarrUseSsl = false;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _sonarrUseSonarrNumbering = false;
|
||||||
|
|
||||||
public ObservableCollection<Color> PredefinedColors{ get; } = new(){
|
public ObservableCollection<Color> PredefinedColors{ get; } = new(){
|
||||||
Color.FromRgb(255, 185, 0),
|
Color.FromRgb(255, 185, 0),
|
||||||
Color.FromRgb(255, 140, 0),
|
Color.FromRgb(255, 140, 0),
|
||||||
|
@ -230,6 +234,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
if (props != null){
|
if (props != null){
|
||||||
SonarrUseSsl = props.UseSsl;
|
SonarrUseSsl = props.UseSsl;
|
||||||
|
SonarrUseSonarrNumbering = props.UseSonarrNumbering;
|
||||||
SonarrHost = props.Host + "";
|
SonarrHost = props.Host + "";
|
||||||
SonarrPort = props.Port + "";
|
SonarrPort = props.Port + "";
|
||||||
SonarrApiKey = props.ApiKey + "";
|
SonarrApiKey = props.ApiKey + "";
|
||||||
|
@ -316,13 +321,21 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
var props = new SonarrProperties();
|
var props = new SonarrProperties();
|
||||||
|
|
||||||
props.UseSsl = SonarrUseSsl;
|
props.UseSsl = SonarrUseSsl;
|
||||||
|
props.UseSonarrNumbering = SonarrUseSonarrNumbering;
|
||||||
props.Host = SonarrHost;
|
props.Host = SonarrHost;
|
||||||
props.Port = Convert.ToInt32(SonarrPort);
|
|
||||||
|
if (int.TryParse(SonarrPort, out var portNumber)){
|
||||||
|
props.Port = portNumber;
|
||||||
|
} else{
|
||||||
|
props.Port = 8989;
|
||||||
|
}
|
||||||
|
|
||||||
props.ApiKey = SonarrApiKey;
|
props.ApiKey = SonarrApiKey;
|
||||||
|
|
||||||
|
|
||||||
Crunchyroll.Instance.CrunOptions.SonarrProperties = props;
|
Crunchyroll.Instance.CrunOptions.SonarrProperties = props;
|
||||||
|
|
||||||
Crunchyroll.Instance.RefreshSonarr();
|
|
||||||
|
|
||||||
//TODO - Mux Options
|
//TODO - Mux Options
|
||||||
|
|
||||||
|
@ -469,4 +482,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
partial void OnSonarrUseSslChanged(bool value){
|
partial void OnSonarrUseSslChanged(bool value){
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnSonarrUseSonarrNumberingChanged(bool value){
|
||||||
|
UpdateSettings();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,11 +4,16 @@
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||||
|
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||||
x:DataType="vm:CalendarPageViewModel"
|
x:DataType="vm:CalendarPageViewModel"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="CRD.Views.CalendarPageView">
|
x:Class="CRD.Views.CalendarPageView">
|
||||||
|
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ui:UiValueConverterCalendarBackground x:Key="UiValueConverterCalendarBackground" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" /> <!-- For the button -->
|
<RowDefinition Height="Auto" /> <!-- For the button -->
|
||||||
|
@ -51,12 +56,12 @@
|
||||||
|
|
||||||
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
|
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
|
||||||
<!-- Spinner Style ProgressBar -->
|
<!-- Spinner Style ProgressBar -->
|
||||||
<ProgressBar IsIndeterminate="True"
|
<!-- <ProgressBar IsIndeterminate="True" -->
|
||||||
Value="50"
|
<!-- MaxWidth="100" -->
|
||||||
Maximum="100"
|
<!-- IsVisible="{Binding ShowLoading}"> -->
|
||||||
MaxWidth="100"
|
<!-- </ProgressBar> -->
|
||||||
IsVisible="{Binding ShowLoading}">
|
|
||||||
</ProgressBar>
|
<controls:ProgressRing IsVisible="{Binding ShowLoading}" Width="100" Height="100"></controls:ProgressRing>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ItemsControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding !ShowLoading}"
|
<ItemsControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding !ShowLoading}"
|
||||||
|
@ -84,60 +89,59 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- ListBox for episodes -->
|
<ScrollViewer Grid.Row="1" >
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding CalendarEpisodes}"> <!-- Adjust MaxHeight as needed -->
|
<ItemsControl ItemsSource="{Binding CalendarEpisodes}">
|
||||||
<ListBox.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Border Padding="10" Margin="5">
|
<Border Padding="10" Margin="5 5 15 5" CornerRadius="5" Background="{Binding IsPremiere, Converter={StaticResource UiValueConverterCalendarBackground}}">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<TextBlock HorizontalAlignment="Center"
|
<TextBlock HorizontalAlignment="Center"
|
||||||
Text="{Binding DateTime, StringFormat='hh:mm tt'}"
|
Text="{Binding DateTime, StringFormat='hh:mm tt'}"
|
||||||
Margin="0,0,0,0" />
|
Margin="0,0,0,0" />
|
||||||
<Grid HorizontalAlignment="Center">
|
<Grid HorizontalAlignment="Center">
|
||||||
<Image HorizontalAlignment="Center" Source="{Binding ImageBitmap}" />
|
<Image HorizontalAlignment="Center" Source="{Binding ImageBitmap}" />
|
||||||
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
|
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
|
||||||
<TextBlock VerticalAlignment="Center" TextAlignment="Center"
|
<TextBlock VerticalAlignment="Center" TextAlignment="Center"
|
||||||
Margin="0 0 5 0" Width="30" Height="30"
|
Margin="0 0 5 0" Width="30" Height="30"
|
||||||
Background="Black" Opacity="0.8"
|
Background="Black" Opacity="0.8"
|
||||||
Text="{Binding EpisodeNumber}"
|
Text="{Binding EpisodeNumber}"
|
||||||
Padding="0,5,0,0" />
|
Padding="0,5,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||||
IsVisible="{Binding IsPremiumOnly}" Margin="0,0,5,5">
|
IsVisible="{Binding IsPremiumOnly}" Margin="0,0,5,5">
|
||||||
<Canvas Width="28" Height="28">
|
<Canvas Width="28" Height="28">
|
||||||
<Ellipse Fill="#40FFFFFF" Width="28" Height="28" />
|
<Ellipse Fill="#40FFFFFF" Width="28" Height="28" />
|
||||||
<Viewbox Width="24" Height="24" Stretch="Uniform"
|
<Viewbox Width="24" Height="24" Stretch="Uniform"
|
||||||
Canvas.Left="2" Canvas.Top="2">
|
Canvas.Left="2" Canvas.Top="2">
|
||||||
<Canvas Width="50" Height="50"> <!-- Ensure inner canvas is large enough to hold the path data -->
|
<Canvas Width="50" Height="50"> <!-- Ensure inner canvas is large enough to hold the path data -->
|
||||||
<Path Fill="#f78c25"
|
<Path Fill="#f78c25"
|
||||||
Stroke="#f78c25"
|
Stroke="#f78c25"
|
||||||
StrokeThickness="1"
|
StrokeThickness="1"
|
||||||
Data="M35.7,36.2H12.3c-0.7,0-1.4-0.5-1.6-1.2L6.1,18.6c-0.2-0.6,0-1.3,0.5-1.7c0.5-0.4,1.2-0.5,1.8-0.2l8.1,4.1 l6.2-8.3c0.3-0.4,0.8-0.7,1.3-0.7h0c0.5,0,1,0.2,1.3,0.7l6.2,8.3l8.2-4.1c0.6-0.3,1.3-0.2,1.8,0.2c0.5,0.4,0.7,1.1,0.5,1.7 L37.3,35C37.1,35.7,36.4,36.2,35.7,36.2z" />
|
Data="M35.7,36.2H12.3c-0.7,0-1.4-0.5-1.6-1.2L6.1,18.6c-0.2-0.6,0-1.3,0.5-1.7c0.5-0.4,1.2-0.5,1.8-0.2l8.1,4.1 l6.2-8.3c0.3-0.4,0.8-0.7,1.3-0.7h0c0.5,0,1,0.2,1.3,0.7l6.2,8.3l8.2-4.1c0.6-0.3,1.3-0.2,1.8,0.2c0.5,0.4,0.7,1.1,0.5,1.7 L37.3,35C37.1,35.7,36.4,36.2,35.7,36.2z" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock HorizontalAlignment="Center" Text="{Binding SeasonName}"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
Margin="0,0,0,0">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="{Binding SeasonName}" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
</TextBlock>
|
||||||
|
<Button HorizontalAlignment="Center" Content="Download"
|
||||||
|
IsEnabled="{Binding HasPassed}"
|
||||||
|
Command="{Binding AddEpisodeToQue}"
|
||||||
|
CommandParameter="{Binding EpisodeUrl}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SeasonName}"
|
|
||||||
TextWrapping="NoWrap"
|
|
||||||
Margin="0,0,0,0">
|
|
||||||
|
|
||||||
<ToolTip.Tip>
|
|
||||||
<TextBlock Text="{Binding SeasonName}" FontSize="15" />
|
|
||||||
</ToolTip.Tip>
|
|
||||||
|
|
||||||
</TextBlock>
|
|
||||||
<Button HorizontalAlignment="Center" Content="Download"
|
|
||||||
IsEnabled="{Binding HasPassed}"
|
|
||||||
Command="{Binding AddEpisodeToQue}"
|
|
||||||
CommandParameter="{Binding EpisodeUrl}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
|
||||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
@ -22,22 +21,12 @@
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="20 0 0 0 ">
|
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="20 0 0 0 ">
|
||||||
<Button Command="{Binding RefreshAll}" Margin="10">Refresh All</Button>
|
<Button Command="{Binding RefreshAll}" Margin="10" IsEnabled="{Binding !FetchingData}">Refresh All</Button>
|
||||||
<Button Command="{Binding AddMissingToQueue}" Margin="10">Add To Queue</Button>
|
<Button Command="{Binding AddMissingToQueue}" Margin="10" IsEnabled="{Binding !FetchingData}">Add To Queue</Button>
|
||||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="10">Edit</ToggleButton>
|
<ToggleButton IsChecked="{Binding EditMode}" Margin="10" IsEnabled="{Binding !FetchingData}">Edit</ToggleButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Grid.Row="1" Grid.Column="0">
|
<ListBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedSeries}" Margin="5">
|
||||||
<!-- Spinner Style ProgressBar -->
|
|
||||||
<ProgressBar IsIndeterminate="True"
|
|
||||||
Value="50"
|
|
||||||
Maximum="100"
|
|
||||||
MaxWidth="100"
|
|
||||||
IsVisible="{Binding ShowLoading}">
|
|
||||||
</ProgressBar>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<ListBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Items}" IsVisible="{Binding !ShowLoading}" SelectedItem="{Binding SelectedSeries}" Margin="5">
|
|
||||||
|
|
||||||
<ListBox.ItemsPanel>
|
<ListBox.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
|
@ -59,8 +48,8 @@
|
||||||
Padding="0,5,0,0"/>
|
Padding="0,5,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SeriesTitle}" TextWrapping="NoWrap"
|
<TextBlock HorizontalAlignment="Center" Text="{Binding SeriesTitle}" TextWrapping="NoWrap" MaxWidth="240"
|
||||||
Margin="4,0,0,0">
|
Margin="4,0,4,0">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
@ -70,6 +59,7 @@
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||||
CommandParameter="{Binding SeriesId}"
|
CommandParameter="{Binding SeriesId}"
|
||||||
|
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).FetchingData}"
|
||||||
>
|
>
|
||||||
<ToolTip.Tip>
|
<ToolTip.Tip>
|
||||||
<TextBlock Text="Remove Series" FontSize="15" />
|
<TextBlock Text="Remove Series" FontSize="15" />
|
||||||
|
@ -80,6 +70,14 @@
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="250" Width="250"
|
||||||
|
MaxHeight="400" Height="400" Background="#90000000" IsVisible="{Binding FetchingData}">
|
||||||
|
<!-- <ProgressBar IsIndeterminate="{Binding FetchingData}" -->
|
||||||
|
<!-- MaxWidth="100"> -->
|
||||||
|
<!-- </ProgressBar> -->
|
||||||
|
<controls:ProgressRing Width="100" Height="100"></controls:ProgressRing>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Avalonia;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace CRD.Views;
|
namespace CRD.Views;
|
||||||
|
|
||||||
public partial class HistoryPageView : UserControl{
|
public partial class HistoryPageView : UserControl{
|
||||||
|
|
||||||
public HistoryPageView(){
|
public HistoryPageView(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,7 +6,8 @@
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:DataType="vm:SettingsPageViewModel"
|
x:DataType="vm:SettingsPageViewModel"
|
||||||
x:Class="CRD.Views.SettingsPageView">
|
x:Class="CRD.Views.SettingsPageView"
|
||||||
|
Unloaded="OnUnloaded">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:SettingsPageViewModel />
|
<vm:SettingsPageViewModel />
|
||||||
|
@ -240,6 +241,7 @@
|
||||||
<controls:SettingsExpander Header="Sonarr Settings"
|
<controls:SettingsExpander Header="Sonarr Settings"
|
||||||
IconSource="Globe"
|
IconSource="Globe"
|
||||||
Description="Adjust sonarr settings"
|
Description="Adjust sonarr settings"
|
||||||
|
IsEnabled="{Binding History}"
|
||||||
IsExpanded="False">
|
IsExpanded="False">
|
||||||
|
|
||||||
<controls:SettingsExpanderItem Content="Host">
|
<controls:SettingsExpanderItem Content="Host">
|
||||||
|
@ -269,6 +271,12 @@
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="Use Sonarr Numbering" Description="Potentially wrong if it couldn't be matched">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<CheckBox IsChecked="{Binding SonarrUseSonarrNumbering}"> </CheckBox>
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<controls:SettingsExpander Header="App Theme"
|
<controls:SettingsExpander Header="App Theme"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using CRD.Downloader;
|
||||||
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.ViewModels;
|
using CRD.ViewModels;
|
||||||
|
|
||||||
namespace CRD.Views;
|
namespace CRD.Views;
|
||||||
|
@ -9,4 +12,10 @@ public partial class SettingsPageView : UserControl{
|
||||||
public SettingsPageView(){
|
public SettingsPageView(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object? sender, RoutedEventArgs e){
|
||||||
|
if (DataContext is SettingsPageViewModel viewModel){
|
||||||
|
Crunchyroll.Instance.RefreshSonarr();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue