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
|
@ -10,10 +10,8 @@
|
|||
<crd:ViewLocator />
|
||||
</Application.DataTemplates>
|
||||
|
||||
|
||||
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme/>
|
||||
<sty:FluentAvaloniaTheme PreferSystemTheme="True" PreferUserAccentColor="True"/>
|
||||
<StyleInclude Source="avares://CRD/Styling/ControlsGalleryStyles.axaml" />
|
||||
<StyleInclude Source="avares://CRD/Assets/Icons.axaml"></StyleInclude>
|
||||
</Application.Styles>
|
||||
|
|
|
@ -47,7 +47,7 @@ public class CrEpisode(){
|
|||
}
|
||||
|
||||
|
||||
public CrunchySeriesList EpisodeData(CrunchyEpisodeList dlEpisodes){
|
||||
public async Task<CrunchySeriesList> EpisodeData(CrunchyEpisodeList dlEpisodes){
|
||||
bool serieshasversions = true;
|
||||
|
||||
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||
|
@ -56,7 +56,7 @@ public class CrEpisode(){
|
|||
foreach (var episode in dlEpisodes.Data){
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
crunInstance.CrHistory.UpdateWithEpisode(episode);
|
||||
await crunInstance.CrHistory.UpdateWithEpisode(episode);
|
||||
}
|
||||
|
||||
// Prepare the episode array
|
||||
|
|
|
@ -164,7 +164,6 @@ public class Crunchyroll{
|
|||
RefreshSonarr();
|
||||
}
|
||||
|
||||
|
||||
calendarLanguage = new(){
|
||||
{ "en-us", "https://www.crunchyroll.com/simulcastcalendar" },
|
||||
{ "es", "https://www.crunchyroll.com/es/simulcastcalendar" },
|
||||
|
@ -181,8 +180,8 @@ public class Crunchyroll{
|
|||
}
|
||||
|
||||
public async void RefreshSonarr(){
|
||||
if (CrunOptions.SonarrProperties != null && !string.IsNullOrEmpty(CrunOptions.SonarrProperties.ApiKey)){
|
||||
SonarrClient.Instance.SetApiUrl();
|
||||
await SonarrClient.Instance.CheckSonarrSettings();
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
|
||||
SonarrSeries = await SonarrClient.Instance.GetSeries();
|
||||
CrHistory.MatchHistorySeriesWithSonarr(true);
|
||||
}
|
||||
|
@ -221,8 +220,6 @@ public class Crunchyroll{
|
|||
|
||||
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
|
||||
|
||||
// Console.WriteLine($"Day: {dayName}, Date: {date}");
|
||||
|
||||
CalendarDay calDay = new CalendarDay();
|
||||
|
||||
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 thumbnailUrl = episode.SelectSingleNode(".//img[contains(@class, 'thumbnail')]")?.GetAttributeValue("src", "No image");
|
||||
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 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();
|
||||
|
||||
calEpisode.DateTime = episodeTime;
|
||||
|
@ -259,6 +252,7 @@ public class Crunchyroll{
|
|||
calEpisode.EpisodeUrl = episodeLink;
|
||||
calEpisode.ThumbnailUrl = thumbnailUrl;
|
||||
calEpisode.IsPremiumOnly = isPremiumOnly;
|
||||
calEpisode.IsPremiere = isPremiere;
|
||||
calEpisode.SeasonName = seasonName;
|
||||
calEpisode.EpisodeNumber = episodeNumber;
|
||||
|
||||
|
@ -267,7 +261,6 @@ public class Crunchyroll{
|
|||
}
|
||||
|
||||
week.CalendarDays.Add(calDay);
|
||||
// Console.WriteLine();
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("No days found in the HTML document.");
|
||||
|
@ -291,10 +284,19 @@ public class Crunchyroll{
|
|||
return;
|
||||
}
|
||||
|
||||
var sList = CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
|
||||
var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
|
||||
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang);
|
||||
var metas = selected.Values.ToList();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -310,6 +312,14 @@ public class Crunchyroll{
|
|||
|
||||
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
||||
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);
|
||||
} else{
|
||||
failed = true;
|
||||
|
@ -559,10 +569,11 @@ public class Crunchyroll{
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
bool dlFailed = false;
|
||||
bool dlVideoOnce = false;
|
||||
|
||||
if (data.Data != null)
|
||||
if (data.Data != null){
|
||||
foreach (CrunchyEpMetaData epMeta in data.Data){
|
||||
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
||||
|
||||
|
@ -1065,6 +1076,7 @@ public class Crunchyroll{
|
|||
if (File.Exists($"{tsFile}.video.m4s")){
|
||||
File.Delete($"{tsFile}.video.m4s");
|
||||
}
|
||||
|
||||
File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s");
|
||||
} catch (IOException ex){
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
|
@ -1111,6 +1123,7 @@ public class Crunchyroll{
|
|||
if (File.Exists($"{tsFile}.audio.m4s")){
|
||||
File.Delete($"{tsFile}.audio.m4s");
|
||||
}
|
||||
|
||||
File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s");
|
||||
} catch (IOException ex){
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
|
@ -1226,7 +1239,7 @@ public class Crunchyroll{
|
|||
|
||||
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("width", quality == 0 ? plQuality.Last().RESOLUTION.Width : plQuality[quality - 1].RESOLUTION.Width, false));
|
||||
|
@ -1476,7 +1489,7 @@ public class Crunchyroll{
|
|||
var stream = hardsub.Value;
|
||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||
Url = stream.Url,
|
||||
HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
|
||||
HardsubLocale = stream.Hlang
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1552,7 +1565,7 @@ public class Crunchyroll{
|
|||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||
|
||||
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>>>() };
|
||||
|
||||
temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
|
@ -1569,7 +1582,7 @@ public class Crunchyroll{
|
|||
var stream = hardsub.Value;
|
||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||
Url = stream.Url,
|
||||
HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
|
||||
HardsubLocale = stream.Hlang
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1578,7 +1591,7 @@ public class Crunchyroll{
|
|||
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.Subtitles = new Subtitles();
|
||||
|
|
|
@ -80,8 +80,27 @@ public class History(){
|
|||
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;
|
||||
|
||||
if (episode.Versions != null){
|
||||
|
@ -137,23 +156,23 @@ public class History(){
|
|||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
} else{
|
||||
var newHistorySeries = new HistorySeries{
|
||||
historySeries = new HistorySeries{
|
||||
SeriesTitle = episode.SeriesTitle,
|
||||
SeriesId = episode.SeriesId,
|
||||
Seasons =[],
|
||||
};
|
||||
crunInstance.HistoryList.Add(newHistorySeries);
|
||||
crunInstance.HistoryList.Add(historySeries);
|
||||
var newSeason = NewHistorySeason(episode);
|
||||
|
||||
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||
if (series?.Data != null){
|
||||
newHistorySeries.SeriesDescription = series.Data.First().Description;
|
||||
newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||
newHistorySeries.SeriesTitle = series.Data.First().Title;
|
||||
historySeries.SeriesDescription = series.Data.First().Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||
historySeries.SeriesTitle = series.Data.First().Title;
|
||||
}
|
||||
|
||||
newHistorySeries.Seasons.Add(newSeason);
|
||||
newHistorySeries.UpdateNewEpisodes();
|
||||
historySeries.Seasons.Add(newSeason);
|
||||
historySeries.UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
|
||||
|
@ -163,10 +182,11 @@ public class History(){
|
|||
}
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||
UpdateHistoryFile();
|
||||
}
|
||||
|
||||
public async void UpdateWithSeasonData(CrunchyEpisodeList seasonData){
|
||||
public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData){
|
||||
if (seasonData.Data != null){
|
||||
var firstEpisode = seasonData.Data.First();
|
||||
var seriesId = firstEpisode.SeriesId;
|
||||
|
@ -203,7 +223,6 @@ public class History(){
|
|||
historyEpisode.EpisodeId = crunchyEpisode.Id;
|
||||
historyEpisode.Episode = crunchyEpisode.Episode;
|
||||
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,12 +239,12 @@ public class History(){
|
|||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
} else{
|
||||
var newHistorySeries = new HistorySeries{
|
||||
historySeries = new HistorySeries{
|
||||
SeriesTitle = firstEpisode.SeriesTitle,
|
||||
SeriesId = firstEpisode.SeriesId,
|
||||
Seasons =[],
|
||||
};
|
||||
crunInstance.HistoryList.Add(newHistorySeries);
|
||||
crunInstance.HistoryList.Add(historySeries);
|
||||
|
||||
var newSeason = NewHistorySeason(seasonData, firstEpisode);
|
||||
|
||||
|
@ -233,18 +252,16 @@ public class History(){
|
|||
|
||||
var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||
if (series?.Data != null){
|
||||
newHistorySeries.SeriesDescription = series.Data.First().Description;
|
||||
newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||
newHistorySeries.SeriesTitle = series.Data.First().Title;
|
||||
historySeries.SeriesDescription = series.Data.First().Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
|
||||
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){
|
||||
|
@ -252,8 +269,10 @@ public class History(){
|
|||
}
|
||||
|
||||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false,historySeries);
|
||||
UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSeriesThumbnail(CrSeriesBase series){
|
||||
// var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||
|
@ -322,6 +341,11 @@ public class History(){
|
|||
}
|
||||
|
||||
public void MatchHistorySeriesWithSonarr(bool updateAll){
|
||||
|
||||
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var historySeries in crunInstance.HistoryList){
|
||||
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||
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)){
|
||||
var episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
|
||||
|
||||
|
@ -372,7 +400,8 @@ public class History(){
|
|||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||
episodes.Remove(episode);
|
||||
} 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){
|
||||
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);
|
||||
}
|
||||
|
||||
private int LevenshteinDistance(string source, string target){
|
||||
|
||||
public int LevenshteinDistance(string source, string target){
|
||||
if (string.IsNullOrEmpty(source)){
|
||||
return string.IsNullOrEmpty(target) ? 0 : target.Length;
|
||||
}
|
||||
|
@ -454,35 +480,32 @@ public class History(){
|
|||
int n = source.Length;
|
||||
int m = target.Length;
|
||||
|
||||
// Create two work arrays of integer distances.
|
||||
int[] previousDistances = new int[m + 1];
|
||||
int[] currentDistances = new int[m + 1];
|
||||
// Use a single array for distances.
|
||||
int[] distances = new int[m + 1];
|
||||
|
||||
// Initialize the previous distance array.
|
||||
// Initialize the distance array.
|
||||
for (int j = 0; j <= m; j++){
|
||||
previousDistances[j] = j;
|
||||
distances[j] = j;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= n; i++){
|
||||
// Initialize the current distance array.
|
||||
currentDistances[0] = i;
|
||||
int previousDiagonal = distances[0];
|
||||
distances[0] = i;
|
||||
|
||||
for (int j = 1; j <= m; j++){
|
||||
int previousDistance = distances[j];
|
||||
int cost = (target[j - 1] == source[i - 1]) ? 0 : 1;
|
||||
|
||||
currentDistances[j] = Math.Min(
|
||||
Math.Min(currentDistances[j - 1] + 1, previousDistances[j] + 1),
|
||||
previousDistances[j - 1] + cost);
|
||||
distances[j] = Math.Min(
|
||||
Math.Min(distances[j - 1] + 1, distances[j] + 1),
|
||||
previousDiagonal + cost);
|
||||
|
||||
previousDiagonal = previousDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap the arrays for the next iteration.
|
||||
var temp = previousDistances;
|
||||
previousDistances = currentDistances;
|
||||
currentDistances = temp;
|
||||
}
|
||||
|
||||
// The final distance is in the previous distance array.
|
||||
return previousDistances[m];
|
||||
// The final distance is in the last cell.
|
||||
return distances[m];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,6 +553,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool FetchingData{ get; set; }
|
||||
|
||||
public async Task LoadImage(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
|
@ -574,6 +600,11 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
||||
}
|
||||
|
||||
public void SetFetchingData(){
|
||||
FetchingData = true;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
}
|
||||
|
||||
public async Task AddNewMissingToDownloads(){
|
||||
bool foundWatched = false;
|
||||
|
||||
|
@ -600,7 +631,12 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
public async Task FetchData(string? seasonId){
|
||||
FetchingData = true;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
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){
|
||||
try{
|
||||
// Serialize the object to a JSON string.
|
||||
var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented);
|
||||
|
||||
// Check if the directory exists; if not, create it.
|
||||
string directoryPath = Path.GetDirectoryName(pathToFile);
|
||||
if (!Directory.Exists(directoryPath)){
|
||||
|
@ -159,8 +156,13 @@ public class CfgManager{
|
|||
}
|
||||
|
||||
lock (fileLock){
|
||||
// Write the JSON string to file. Creates the file if it does not exist.
|
||||
File.WriteAllText(pathToFile, jsonString);
|
||||
// Write the JSON string to file using a streaming approach.
|
||||
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){
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
|
|
|
@ -421,7 +421,7 @@ public class HlsDownloader{
|
|||
try{
|
||||
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsByteArrayAsync();
|
||||
return await ReadContentAsByteArrayAsync(response.Content);
|
||||
} catch (HttpRequestException ex){
|
||||
// Log retry attempts
|
||||
string partType = isKey ? "Key" : "Part";
|
||||
|
@ -437,6 +437,14 @@ public class HlsDownloader{
|
|||
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){
|
||||
var clone = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri){
|
||||
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(){
|
||||
|
@ -88,22 +92,44 @@ public class Helpers{
|
|||
return CosineSimilarity(vector1, vector2);
|
||||
}
|
||||
|
||||
private static Dictionary<string, double> ComputeWordFrequency(string text){
|
||||
var wordFrequency = new Dictionary<string, double>();
|
||||
var words = text.Split(new[]{ ' ', ',', '.', ';', ':', '-', '_', '\'' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
private static readonly char[] Delimiters ={ ' ', ',', '.', ';', ':', '-', '_', '\'' };
|
||||
|
||||
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){
|
||||
var lowerWord = word.ToLower();
|
||||
if (!wordFrequency.ContainsKey(lowerWord)){
|
||||
wordFrequency[lowerWord] = 0;
|
||||
if (wordFrequency.TryGetValue(word, out double count)){
|
||||
wordFrequency[word] = count + 1;
|
||||
} else{
|
||||
wordFrequency[word] = 1;
|
||||
}
|
||||
|
||||
wordFrequency[lowerWord]++;
|
||||
}
|
||||
|
||||
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){
|
||||
var intersection = vector1.Keys.Intersect(vector2.Keys);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Utils.Sonarr;
|
||||
|
||||
public class SonarrClient{
|
||||
private string apiUrl;
|
||||
private string? apiUrl;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
@ -51,10 +51,38 @@ public class SonarrClient{
|
|||
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) properties = Crunchyroll.Instance.CrunOptions.SonarrProperties;
|
||||
|
||||
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(){
|
||||
var json = await GetJson($"/v3/series{(true ? $"?includeSeasonImages={true}" : "")}");
|
||||
|
||||
|
@ -151,4 +179,7 @@ public class SonarrProperties(){
|
|||
public bool UseSsl{ 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 CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Views;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
|
@ -36,6 +34,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
|
|||
public string? EpisodeNumber{ get; set; }
|
||||
|
||||
public bool IsPremiumOnly{ get; set; }
|
||||
public bool IsPremiere{ get; set; }
|
||||
|
||||
public string? SeasonName{ get; set; }
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public class Caption{
|
|||
}
|
||||
|
||||
public class HardSub{
|
||||
public string? Hlang{ get; set; }
|
||||
public Locale? Hlang{ get; set; }
|
||||
public string? Url{ get; set; }
|
||||
public string? Quality{ get; set; }
|
||||
}
|
||||
|
|
|
@ -11,24 +11,55 @@ public struct CrunchyEpisodeList{
|
|||
}
|
||||
|
||||
public struct CrunchyEpisode{
|
||||
[JsonProperty("next_episode_id")] public string NextEpisodeId{ get; set; }
|
||||
[JsonProperty("series_id")] public string SeriesId{ get; set; }
|
||||
[JsonProperty("season_number")] public int SeasonNumber{ get; set; }
|
||||
[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; } // Assume Version is defined elsewhere.
|
||||
[JsonProperty("next_episode_id")]
|
||||
public string NextEpisodeId{ get; set; }
|
||||
|
||||
[JsonProperty("series_id")]
|
||||
public string SeriesId{ get; set; }
|
||||
|
||||
[JsonProperty("season_number")]
|
||||
public int SeasonNumber{ get; set; }
|
||||
|
||||
[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; }
|
||||
[JsonProperty("sequence_number")] public float SequenceNumber{ get; set; }
|
||||
[JsonProperty("eligible_region")] public string EligibleRegion{ get; set; }
|
||||
[JsonProperty("availability_starts")] public DateTime? AvailabilityStarts{ 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("seo_title")] public string SeoTitle{ get; set; }
|
||||
[JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; }
|
||||
|
||||
[JsonProperty("sequence_number")]
|
||||
public float SequenceNumber{ get; set; }
|
||||
|
||||
[JsonProperty("eligible_region")]
|
||||
public string EligibleRegion{ 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")]
|
||||
public Dictionary<string, object> ExtendedMaturityRating{ get; set; }
|
||||
|
@ -41,104 +72,119 @@ public struct CrunchyEpisode{
|
|||
[JsonProperty("premium_available_date")]
|
||||
public DateTime? PremiumAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("season_title")] public string SeasonTitle{ get; set; }
|
||||
[JsonProperty("seo_description")] public string SeoDescription{ get; set; }
|
||||
[JsonProperty("season_title")]
|
||||
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; }
|
||||
[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("free_available_date")] public DateTime? FreeAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("media_type")]
|
||||
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; }
|
||||
[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; }
|
||||
[JsonProperty("is_mature")] public bool IsMature{ get; set; }
|
||||
[JsonProperty("listing_id")] public string ListingId{ get; set; }
|
||||
[JsonProperty("episode_air_date")] public DateTime? EpisodeAirDate{ get; set; }
|
||||
|
||||
[JsonProperty("is_mature")]
|
||||
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; }
|
||||
[JsonProperty("available_date")] public DateTime? AvailableDate{ 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; }
|
||||
|
||||
[JsonProperty("available_date")]
|
||||
public DateTime? AvailableDate{ 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; }
|
||||
[JsonProperty("is_subbed")] public bool IsSubbed{ 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("is_subbed")]
|
||||
public bool IsSubbed{ 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")]
|
||||
public bool ClosedCaptionsAvailable{ get; set; }
|
||||
|
||||
[JsonProperty("episode_number")] public int? EpisodeNumber{ get; set; }
|
||||
[JsonProperty("season_tags")] public List<object> SeasonTags{ get; set; } // More specific type could be used if known.
|
||||
[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("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; }
|
||||
[JsonProperty("episode_number")]
|
||||
public int? EpisodeNumber{ get; set; }
|
||||
|
||||
[JsonProperty("season_tags")]
|
||||
public List<object> SeasonTags{ get; set; }
|
||||
|
||||
[JsonProperty("maturity_ratings")]
|
||||
public List<string> MaturityRatings{ 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; }
|
||||
[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{
|
||||
[JsonProperty("poster_tall")] public List<List<Image>>? PosterTall{ get; set; }
|
||||
[JsonProperty("poster_wide")] public List<List<Image>>? PosterWide{ get; set; }
|
||||
[JsonProperty("promo_image")] public List<List<Image>>? PromoImage{ get; set; }
|
||||
[JsonProperty("poster_tall")]
|
||||
public List<List<Image>>? PosterTall{ 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; }
|
||||
}
|
||||
|
||||
|
@ -150,12 +196,22 @@ public struct Image{
|
|||
}
|
||||
|
||||
public struct EpisodeVersion{
|
||||
[JsonProperty("audio_locale")] public string AudioLocale{ get; set; }
|
||||
[JsonProperty("audio_locale")]
|
||||
public string AudioLocale{ 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; }
|
||||
[JsonProperty("season_guid")] public string SeasonGuid{ get; set; }
|
||||
|
||||
[JsonProperty("season_guid")]
|
||||
public string SeasonGuid{ get; set; }
|
||||
|
||||
public string Variant{ get; set; }
|
||||
}
|
||||
|
||||
|
@ -194,7 +250,6 @@ public class CrunchyEpMeta{
|
|||
}
|
||||
|
||||
public class DownloadProgress{
|
||||
|
||||
public bool IsDownloading = false;
|
||||
public bool Done = 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; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? _showLoading = false;
|
||||
private static bool _fetchingData;
|
||||
|
||||
[ObservableProperty]
|
||||
public HistorySeries _selectedSeries;
|
||||
|
@ -35,38 +35,52 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
partial void OnSelectedSeriesChanged(HistorySeries value){
|
||||
Crunchyroll.Instance.SelectedSeries = value;
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false));
|
||||
NavToSeries();
|
||||
_selectedSeries = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveSeries(string? seriesId){
|
||||
|
||||
HistorySeries? objectToRemove = Crunchyroll.Instance.HistoryList.ToList().Find(se => se.SeriesId == seriesId) ?? null;
|
||||
if (objectToRemove != null){
|
||||
Crunchyroll.Instance.HistoryList.Remove(objectToRemove);
|
||||
Items.Remove(objectToRemove);
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void NavToSeries(){
|
||||
if (FetchingData){
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void RefreshAll(){
|
||||
FetchingData = true;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
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("");
|
||||
Items[i].UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
ShowLoading = false;
|
||||
FetchingData = false;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
@ -74,7 +88,5 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
for (int i = 0; i < Items.Count; i++){
|
||||
await Items[i].AddNewMissingToDownloads();
|
||||
}
|
||||
|
||||
ShowLoading = false;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ using Avalonia.Controls;
|
|||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Sonarr;
|
||||
|
@ -100,6 +101,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private bool _sonarrUseSsl = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSonarrNumbering = false;
|
||||
|
||||
public ObservableCollection<Color> PredefinedColors{ get; } = new(){
|
||||
Color.FromRgb(255, 185, 0),
|
||||
Color.FromRgb(255, 140, 0),
|
||||
|
@ -230,6 +234,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
if (props != null){
|
||||
SonarrUseSsl = props.UseSsl;
|
||||
SonarrUseSonarrNumbering = props.UseSonarrNumbering;
|
||||
SonarrHost = props.Host + "";
|
||||
SonarrPort = props.Port + "";
|
||||
SonarrApiKey = props.ApiKey + "";
|
||||
|
@ -316,13 +321,21 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
var props = new SonarrProperties();
|
||||
|
||||
props.UseSsl = SonarrUseSsl;
|
||||
props.UseSonarrNumbering = SonarrUseSonarrNumbering;
|
||||
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;
|
||||
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.SonarrProperties = props;
|
||||
|
||||
Crunchyroll.Instance.RefreshSonarr();
|
||||
|
||||
|
||||
//TODO - Mux Options
|
||||
|
||||
|
@ -469,4 +482,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
partial void OnSonarrUseSslChanged(bool value){
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnSonarrUseSonarrNumberingChanged(bool value){
|
||||
UpdateSettings();
|
||||
}
|
||||
}
|
|
@ -4,11 +4,16 @@
|
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
x:DataType="vm:CalendarPageViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.CalendarPageView">
|
||||
|
||||
|
||||
<UserControl.Resources>
|
||||
<ui:UiValueConverterCalendarBackground x:Key="UiValueConverterCalendarBackground" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- For the button -->
|
||||
|
@ -51,12 +56,12 @@
|
|||
|
||||
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<!-- Spinner Style ProgressBar -->
|
||||
<ProgressBar IsIndeterminate="True"
|
||||
Value="50"
|
||||
Maximum="100"
|
||||
MaxWidth="100"
|
||||
IsVisible="{Binding ShowLoading}">
|
||||
</ProgressBar>
|
||||
<!-- <ProgressBar IsIndeterminate="True" -->
|
||||
<!-- MaxWidth="100" -->
|
||||
<!-- IsVisible="{Binding ShowLoading}"> -->
|
||||
<!-- </ProgressBar> -->
|
||||
|
||||
<controls:ProgressRing IsVisible="{Binding ShowLoading}" Width="100" Height="100"></controls:ProgressRing>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding !ShowLoading}"
|
||||
|
@ -84,11 +89,11 @@
|
|||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ListBox for episodes -->
|
||||
<ListBox Grid.Row="1" ItemsSource="{Binding CalendarEpisodes}"> <!-- Adjust MaxHeight as needed -->
|
||||
<ListBox.ItemTemplate>
|
||||
<ScrollViewer Grid.Row="1" >
|
||||
<ItemsControl ItemsSource="{Binding CalendarEpisodes}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<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">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{Binding DateTime, StringFormat='hh:mm tt'}"
|
||||
|
@ -119,15 +124,12 @@
|
|||
</StackPanel>
|
||||
</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}"
|
||||
|
@ -136,8 +138,10 @@
|
|||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
|
@ -22,22 +21,12 @@
|
|||
</Grid.RowDefinitions>
|
||||
|
||||
<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 AddMissingToQueue}" Margin="10">Add To Queue</Button>
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="10">Edit</ToggleButton>
|
||||
<Button Command="{Binding RefreshAll}" Margin="10" IsEnabled="{Binding !FetchingData}">Refresh All</Button>
|
||||
<Button Command="{Binding AddMissingToQueue}" Margin="10" IsEnabled="{Binding !FetchingData}">Add To Queue</Button>
|
||||
<ToggleButton IsChecked="{Binding EditMode}" Margin="10" IsEnabled="{Binding !FetchingData}">Edit</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<!-- 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 Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedSeries}" Margin="5">
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
|
@ -59,8 +48,8 @@
|
|||
Padding="0,5,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SeriesTitle}" TextWrapping="NoWrap"
|
||||
Margin="4,0,0,0">
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SeriesTitle}" TextWrapping="NoWrap" MaxWidth="240"
|
||||
Margin="4,0,4,0">
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
|
@ -70,6 +59,7 @@
|
|||
VerticalAlignment="Center"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||
CommandParameter="{Binding SeriesId}"
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).FetchingData}"
|
||||
>
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Series" FontSize="15" />
|
||||
|
@ -80,6 +70,14 @@
|
|||
</Button>
|
||||
</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>
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
public partial class HistoryPageView : UserControl{
|
||||
|
||||
public HistoryPageView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:SettingsPageViewModel"
|
||||
x:Class="CRD.Views.SettingsPageView">
|
||||
x:Class="CRD.Views.SettingsPageView"
|
||||
Unloaded="OnUnloaded">
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:SettingsPageViewModel />
|
||||
|
@ -240,6 +241,7 @@
|
|||
<controls:SettingsExpander Header="Sonarr Settings"
|
||||
IconSource="Globe"
|
||||
Description="Adjust sonarr settings"
|
||||
IsEnabled="{Binding History}"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Host">
|
||||
|
@ -269,6 +271,12 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</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 Header="App Theme"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.ViewModels;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
@ -9,4 +12,10 @@ public partial class SettingsPageView : UserControl{
|
|||
public SettingsPageView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object? sender, RoutedEventArgs e){
|
||||
if (DataContext is SettingsPageViewModel viewModel){
|
||||
Crunchyroll.Instance.RefreshSonarr();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue