Crunchy-Downloader/CRD/ViewModels/UpcomingSeasonsPageViewModel.cs
Elwador 95cd06a523 Add - Added path reset buttons to Temp Folder Path, Download Folder, and Background Image settings
Add - Added background image option to the Appearance settings
Add - Background Image Settings - Added new options to control opacity and blur radius
Add - Added "Couldn't sync dubs" status if the syncing failed
Add - Added functionality to combine multiple episodes from the same season into a single entry in the calendar
Add - Added video resolution display next to dubs/subs in the downloads tab
Add - Added Cloudflare check to image loading
Add - Added hardsub selection if the current is not available
Add - Added part size setting to configure the size of parts downloaded at the same time
Add - Added quality override to history series
Add - Added history marker to search results to indicate if a series is already in the user's history
Add - Added seasons tab for seasonal releases (Spring, Summer, Fall, Winter)
Add - Added potential releases and release times for the current day and the next week to the custom calendar
Chg - Changed Calendar cards background color for improved visibility
Chg - Combined Appearance settings into a single section in the settings tab
Chg - Consolidated Debug settings into one settings expander for better organization
Chg - Changed time sync to now check both the start and end of the video
Chg - Changed encoding progress to be displayed by the progress bar
Chg - Updated the functionality for hiding dubs in the custom calendar
Chg - Adjusted Dub sync to improve accuracy, resolving issues where it failed for more episodes than expected
Chg - Subtitles and dubs are now sorted according to the order selected in the MKV file
Chg - Changed logout behavior to correctly log out if login fails when starting the downloader
Chg - Changed that all downloaded files are removed if an in-progress download is removed from the queue
Chg - Changed default profile image
Chg - Updated used packages to the newest version
Chg - Separated settings to separate tabs
Fix - Fixed some series didn't get added to the history
Fix - Fixed an issue with file path length that prevented some files from being accessed properly
Fix - Fixed an issue where file names exceeded the maximum allowable length, causing errors
Fix - Fixed an issue where refreshing a series could get stuck
Fix - Fixed a crash that could happen with the syncing
Fix - Fixed an issue where the download status showed "Done" while moving files from the temp folder
Fix - Fixed an issue where cookies were not being utilized correctly
Fix - Resolved issues with displaying dates in UTC format
Fix - Fixed an issue with incorrect calendar grouping
Fix - Fixed an issue with the previous week navigation in the calendar
Fix - Fixed an issue where the calendar would not display correctly when not logged in
Fix - Fixed incorrect FFmpeg check for other OS (Linux/macOS)
Fix - Fixed an issue where image loading used a different HTTP client
2024-12-19 19:01:50 +01:00

394 lines
No EOL
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CRD.Downloader;
using CRD.Downloader.Crunchyroll;
using CRD.Utils;
using CRD.Utils.Structs;
using CRD.Utils.Structs.History;
using CRD.Views;
using Newtonsoft.Json;
using ReactiveUI;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
namespace CRD.ViewModels;
public partial class UpcomingPageViewModel : ViewModelBase{
#region Query
private string query = @"query (
$season: MediaSeason,
$year: Int,
$format: MediaFormat,
$excludeFormat: MediaFormat,
$status: MediaStatus,
$minEpisodes: Int,
$page: Int,
){
Page(page: $page) {
pageInfo {
hasNextPage
total
}
media(
season: $season
seasonYear: $year
format: $format,
format_not: $excludeFormat,
status: $status,
episodes_greater: $minEpisodes,
isAdult: false,
type: ANIME,
sort: TITLE_ENGLISH,
) {
id
idMal
title {
romaji
native
english
}
startDate {
year
month
day
}
endDate {
year
month
day
}
status
season
format
genres
synonyms
duration
popularity
episodes
source(version: 2)
countryOfOrigin
hashtag
averageScore
siteUrl
description
bannerImage
isAdult
coverImage {
extraLarge
color
}
trailer {
id
site
thumbnail
}
externalLinks {
site
icon
color
url
}
rankings {
rank
type
season
allTime
}
studios(isMain: true) {
nodes {
id
name
siteUrl
}
}
relations {
edges {
relationType(version: 2)
node {
id
title {
romaji
native
english
}
siteUrl
}
}
}
airingSchedule(
notYetAired: true
perPage: 2
) {
nodes {
episode
airingAt
}
}
}
}
}";
#endregion
[ObservableProperty]
private AnilistSeries _selectedSeries;
[ObservableProperty]
private int _selectedIndex;
[ObservableProperty]
private bool _isLoading;
public ObservableCollection<SeasonViewModel> Seasons{ get; set; } =[];
public ObservableCollection<AnilistSeries> SelectedSeason{ get; set; } =[];
private SeasonViewModel currentSelection;
public UpcomingPageViewModel(){
LoadSeasons();
}
private async void LoadSeasons(){
Seasons = GetTargetSeasonsAndYears();
currentSelection = Seasons.Last();
currentSelection.IsSelected = true;
var list = await GetSeriesForSeason(currentSelection.Season, currentSelection.Year, false);
SelectedSeason.Clear();
foreach (var anilistSeries in list){
SelectedSeason.Add(anilistSeries);
}
}
[RelayCommand]
public async Task SelectSeasonCommand(SeasonViewModel selectedSeason){
currentSelection.IsSelected = false;
currentSelection = selectedSeason;
currentSelection.IsSelected = true;
var list = await GetSeriesForSeason(currentSelection.Season, currentSelection.Year, false);
SelectedSeason.Clear();
foreach (var anilistSeries in list){
SelectedSeason.Add(anilistSeries);
}
}
[RelayCommand]
public void OpenTrailer(AnilistSeries series){
if (series.Trailer.Site.Equals("youtube")){
var url = "https://www.youtube.com/watch?v=" + series.Trailer.Id; // Replace with your video URL
Process.Start(new ProcessStartInfo{
FileName = url,
UseShellExecute = true
});
}
}
[RelayCommand]
public async void AddToHistory(AnilistSeries series){
if (!string.IsNullOrEmpty(series.CrunchyrollID)){
if (CrunchyrollManager.Instance.CrunOptions.History){
series.IsInHistory = true;
RaisePropertyChanged(nameof(series.IsInHistory));
var sucess = await CrunchyrollManager.Instance.History.CRUpdateSeries(series.CrunchyrollID, "");
series.IsInHistory = sucess;
RaisePropertyChanged(nameof(series.IsInHistory));
if (sucess){
MessageBus.Current.SendMessage(new ToastMessage($"Series added to History", ToastType.Information, 3));
} else{
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History\n(maybe not available in your region)", ToastType.Error, 3));
}
} else{
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History", ToastType.Error, 3));
}
} else{
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History", ToastType.Error, 3));
}
}
private async Task<List<AnilistSeries>> GetSeriesForSeason(string season, int year, bool forceRefresh){
if (ProgramManager.Instance.AnilistSeasons.ContainsKey(season + year) && !forceRefresh){
return ProgramManager.Instance.AnilistSeasons[season + year];
}
IsLoading = true;
var variables = new{
season,
year,
format = "TV",
page = 1
};
var payload = new{
query,
variables
};
string jsonPayload = JsonConvert.SerializeObject(payload, Formatting.Indented);
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Anilist);
request.Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await HttpClientReq.Instance.SendHttpRequest(request);
if (!response.IsOk){
Console.Error.WriteLine($"Anilist Request Failed for {season} {year}");
return[];
}
AniListResponse aniListResponse = Helpers.Deserialize<AniListResponse>(response.ResponseContent, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? new AniListResponse();
var list = aniListResponse.Data?.Page?.Media ??[];
list = list.Where(ele => ele.ExternalLinks != null && ele.ExternalLinks.Any(external =>
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase))).ToList();
foreach (var anilistEle in list){
anilistEle.ThumbnailImage = await Helpers.LoadImage(anilistEle.CoverImage.ExtraLarge, 185, 265);
anilistEle.Description = anilistEle.Description
.Replace("<i>", "")
.Replace("</i>", "")
.Replace("<BR>", "")
.Replace("<br>", "");
if (anilistEle.ExternalLinks != null){
var url = anilistEle.ExternalLinks.First(external =>
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
string pattern = @"series\/([^\/]+)";
Match match = Regex.Match(url, pattern);
if (match.Success){
anilistEle.CrunchyrollID = match.Groups[1].Value;
anilistEle.HasCrID = true;
if (CrunchyrollManager.Instance.CrunOptions.History){
var historyIDs = new HashSet<string>(CrunchyrollManager.Instance.HistoryList.Select(item => item.SeriesId ?? ""));
if (historyIDs.Contains(anilistEle.CrunchyrollID)){
anilistEle.IsInHistory = true;
}
}
} else{
Uri uri = new Uri(url);
if (uri.Host == "www.crunchyroll.com"
&& uri.AbsolutePath != "/"
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)){
HttpRequestMessage getUrlRequest = new HttpRequestMessage(HttpMethod.Head, url);
string? finalUrl = "";
try{
HttpResponseMessage getUrlResponse = await HttpClientReq.Instance.GetHttpClient().SendAsync(getUrlRequest);
finalUrl = getUrlResponse.RequestMessage?.RequestUri?.ToString();
} catch (Exception ex){
Console.WriteLine($"Error: {ex.Message}");
}
Match match2 = Regex.Match(finalUrl ?? string.Empty, pattern);
if (match2.Success){
anilistEle.CrunchyrollID = match2.Groups[1].Value;
anilistEle.HasCrID = true;
if (CrunchyrollManager.Instance.CrunOptions.History){
var historyIDs = new HashSet<string>(CrunchyrollManager.Instance.HistoryList.Select(item => item.SeriesId ?? ""));
if (historyIDs.Contains(anilistEle.CrunchyrollID)){
anilistEle.IsInHistory = true;
}
}
} else{
anilistEle.CrunchyrollID = "";
anilistEle.HasCrID = false;
}
} else{
anilistEle.CrunchyrollID = "";
anilistEle.HasCrID = false;
}
}
}
}
ProgramManager.Instance.AnilistSeasons[season + year] = list;
IsLoading = false;
return list;
}
private ObservableCollection<SeasonViewModel> GetTargetSeasonsAndYears(){
DateTime now = DateTime.Now;
int currentMonth = now.Month;
int currentYear = now.Year;
string currentSeason;
if (currentMonth >= 1 && currentMonth <= 3)
currentSeason = "WINTER";
else if (currentMonth >= 4 && currentMonth <= 6)
currentSeason = "SPRING";
else if (currentMonth >= 7 && currentMonth <= 9)
currentSeason = "SUMMER";
else
currentSeason = "FALL";
var seasons = new List<string>{ "WINTER", "SPRING", "SUMMER", "FALL" };
int currentSeasonIndex = seasons.IndexOf(currentSeason);
var targetSeasons = new ObservableCollection<SeasonViewModel>();
// Includes: -2 (two seasons ago), -1 (previous), 0 (current), 1 (next)
for (int i = -2; i <= 1; i++){
int targetIndex = (currentSeasonIndex + i + 4) % 4;
string targetSeason = seasons[targetIndex];
int targetYear = currentYear;
if (i < 0 && targetIndex == 3){
targetYear--;
} else if (i > 0 && targetIndex == 0){
targetYear++;
}
targetSeasons.Add(new SeasonViewModel(){ Season = targetSeason, Year = targetYear });
}
return targetSeasons;
}
public void SelectionChangedOfSeries(AnilistSeries? value){
SelectedSeries = null;
SelectedIndex = -1;
}
partial void OnSelectedSeriesChanged(AnilistSeries? value){
SelectionChangedOfSeries(value);
}
}