Skip to content

Commit

Permalink
Added a Console App
Browse files Browse the repository at this point in the history
  • Loading branch information
yairp03 authored Aug 29, 2022
2 parents 61c02c9 + 15bedf6 commit 5ba6d15
Show file tree
Hide file tree
Showing 67 changed files with 353 additions and 1,890 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -621,3 +621,5 @@ $RECYCLE.BIN/
# Additional files built by Visual Studio

# End of https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,csharp,windows

HoradotTV/
27 changes: 27 additions & 0 deletions HoradotTV.Console/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace HoradotTV.Console;

internal static class Constants
{
public static int QUERY_MIN_LENGTH = 2;

public static string DEFAULT_DOWNLOAD_LOCATION = KnownFolders.Downloads.Path;
}

internal static class Menus
{
public static string MODES_MENU = "-- Download Modes --\n" +
"[0] Back to start\n" +
"[1] Download Episode\n" +
"[2] Download Episodes\n" +
"[3] Download Season\n" +
"[4] Download Series";
}

internal enum Modes
{
None,
Episode,
Episodes,
Season,
Series
}
6 changes: 6 additions & 0 deletions HoradotTV.Console/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
global using OpenQA.Selenium;
global using SdarotAPI;
global using SdarotAPI.Models;
global using Syroot.Windows.IO;
global using System.Runtime.InteropServices;
global using System.Text;
18 changes: 18 additions & 0 deletions HoradotTV.Console/HoradotTV.Console.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.3.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SdarotAPI\SdarotAPI.csproj" />
</ItemGroup>

</Project>
55 changes: 55 additions & 0 deletions HoradotTV.Console/IOHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace HoradotTV.Console;

internal static class IOHelpers
{
public static void Print(string s) => System.Console.WriteLine(s);
public static string Input(string s = "")
{
System.Console.Write(s);
return System.Console.ReadLine()!;
}
public static int InputInt(string s = "")
{
while (true)
{
try
{
return int.Parse(Input(s));
}
catch (FormatException)
{
Print("Please enter a number.");
continue;
}
}
}

public static string ChooseOption(IEnumerable<string> options, string type = "option", string title = "Choose an option")
{
string option;
do
{
Print($"\nAvailable {type}s: " + string.Join(", ", options));
option = Input($"{title}: ");
if (options.Contains(option))
return option;
Print($"Please enter an valid {type}.");
} while (true);
}
public static int ChooseOptionRange(int max, string title = "Choose a number") => ChooseOptionRange(0, max, title);
public static int ChooseOptionRange(int min, int max, string title = "Choose a number")
{
int option;
do
{
option = InputInt($"{title} ({min}-{max}): ");

if (option > max || option < min)
{
Print("Please choose a number within the range.");
}
} while (option > max || option < min);

return option;
}
}
223 changes: 223 additions & 0 deletions HoradotTV.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using System.Linq;

namespace HoradotTV.Console;

internal class Program
{
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);

private delegate bool EventHandler(CtrlType sig);
static EventHandler? _handler;

enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
}

private static bool Handler(CtrlType sig)
{
switch (sig)
{
case CtrlType.CTRL_C_EVENT:
case CtrlType.CTRL_LOGOFF_EVENT:
case CtrlType.CTRL_SHUTDOWN_EVENT:
case CtrlType.CTRL_CLOSE_EVENT:
default:
driver?.Shutdown();
break;
}

return true;
}

static SdarotDriver? driver;

static async Task Main()
{
IOHelpers.Print("Welcome to HoradotTV!");
IOHelpers.Print("Initializing...");

_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
System.Console.InputEncoding = Encoding.Unicode;
System.Console.OutputEncoding = Encoding.Unicode;
driver = new SdarotDriver();
await driver.Initialize();

while (true)
{
var series = await SearchSeries(driver);
if (series == null)
continue;

var downloadLocation = GetDownloadLocation();

var mode = GetMode();
if (mode == Modes.None)
continue;
if (mode == Modes.Series)
{
await DownloadSeries(driver, series, downloadLocation);
continue;
}

var season = await GetSeason(driver, series);
if (mode == Modes.Season)
{
await DownloadSeason(driver, season, downloadLocation);
continue;
}

var episode = await GetEpisode(driver, season);
if (mode == Modes.Episode)
{
await DownloadEpisode(driver, episode, downloadLocation);
continue;
}

var episodesAmount = GetEpisodesAmount();
await DownloadEpisodes(driver, episode, episodesAmount, downloadLocation);
}
}

static async Task<SeriesInformation?> SearchSeries(SdarotDriver driver)
{
var searchResult = Array.Empty<SeriesInformation>();
do
{
var query = IOHelpers.Input("\nEnter series name or part of it: ");
if (query.Length < Constants.QUERY_MIN_LENGTH)
{
IOHelpers.Print($"Please enter at least {Constants.QUERY_MIN_LENGTH} characters.");
continue;
}

searchResult = await driver.SearchSeries(query);
if (searchResult.Length == 0)
IOHelpers.Print("Series not found.");
} while (searchResult.Length == 0);

IOHelpers.Print("\nResults:");
IOHelpers.Print("[0] Back to start");
for (var i = 0; i < searchResult.Length; i++)
{
IOHelpers.Print($"[{i + 1}] {searchResult[i].SeriesNameEn} - {searchResult[i].SeriesNameHe}");
}

var selection = IOHelpers.ChooseOptionRange(searchResult.Length, "Choose a series");
return selection == 0 ? null : searchResult[selection - 1];
}

static string GetDownloadLocation()
{
string path;
do
{
path = IOHelpers.Input($"\nEnter path for download (empty - {Constants.DEFAULT_DOWNLOAD_LOCATION}): ").Trim();
if (string.IsNullOrWhiteSpace(path))
path = Constants.DEFAULT_DOWNLOAD_LOCATION;

try
{
var fullPath = Path.GetFullPath(path);
if (!File.GetAttributes(fullPath).HasFlag(FileAttributes.Directory))
{
IOHelpers.Print("Please enter a path to a directory.");
path = "";
}
}
catch
{
IOHelpers.Print("Please enter a valid path.");
path = "";
}
} while (string.IsNullOrWhiteSpace(path));

return path;
}

static Modes GetMode()
{
IOHelpers.Print("\n" + Menus.MODES_MENU);
return (Modes)IOHelpers.ChooseOptionRange(Enum.GetNames(typeof(Modes)).Length - 1, "Choose a mode");
}

static async Task<SeasonInformation> GetSeason(SdarotDriver driver, SeriesInformation series)
{
var seasons = await driver.GetSeasonsAsync(series);
var seasonName = IOHelpers.ChooseOption(seasons.Select(s => s.SeasonName), "season", "Choose a season");
var season = seasons.Where(s => s.SeasonName == seasonName).First();
return season;
}
static async Task<EpisodeInformation> GetEpisode(SdarotDriver driver, SeasonInformation season)
{
var episodes = await driver.GetEpisodesAsync(season);
var episodeName = IOHelpers.ChooseOption(episodes.Select(e => e.EpisodeName), "episode", "Choose an episode");
var episode = episodes.Where(e => e.EpisodeName == episodeName).First();
return episode;
}

static int GetEpisodesAmount()
{
var amount = IOHelpers.InputInt("\nEnter episodes amount: ");
while (amount <= 0)
{
IOHelpers.Print("Please enter a positive amount.");
amount = IOHelpers.InputInt("Enter episodes amount: ");
}

return amount;
}

static async Task DownloadSeries(SdarotDriver driver, SeriesInformation series, string downloadLocation) => await DownloadEpisodes(driver, await driver.GetEpisodesAsync(series), downloadLocation);
static async Task DownloadSeason(SdarotDriver driver, SeasonInformation season, string downloadLocation) => await DownloadEpisodes(driver, await driver.GetEpisodesAsync(season), downloadLocation);
static async Task DownloadEpisode(SdarotDriver driver, EpisodeInformation episode, string downloadLocation) => await DownloadEpisodes(driver, new EpisodeInformation[] { episode }, downloadLocation);
static async Task DownloadEpisodes(SdarotDriver driver, EpisodeInformation episode, int episodesAmount, string downloadLocation) => await DownloadEpisodes(driver, await driver.GetEpisodesAsync(episode, episodesAmount), downloadLocation);
static async Task DownloadEpisodes(SdarotDriver driver, EpisodeInformation[] episodes, string downloadLocation)
{
for (var i = 0; i < episodes.Length; i++)
{
var episode = episodes[i];
IOHelpers.Print($"\n({i + 1}/{episodes.Length})");
IOHelpers.Print($"Loading {episode.Season.SeasonString} {episode.EpisodeString}...");
var episodeMedia = await GetEpisodeMediaDetails(driver, episode);
if (episodeMedia == null)
{
IOHelpers.Print("Failed. Proceeding to next episode.");
continue;
}

IOHelpers.Print($"Downloading {episode.Season.SeasonString} {episode.EpisodeString}...");
var cleanSeriesName = string.Concat(episode.Series.SeriesNameEn.Where(c => !Path.GetInvalidFileNameChars().Contains(c)));
var finalLocation = Path.Combine(downloadLocation, cleanSeriesName, episode.Season.SeasonString, episode.EpisodeString + ".mp4");
Directory.CreateDirectory(Path.GetDirectoryName(finalLocation)!);
await SdarotHelper.DownloadEpisode(episodeMedia, finalLocation);
}

IOHelpers.Print("\nDone. Returning to start.");
}

static async Task<EpisodeMediaDetails?> GetEpisodeMediaDetails(SdarotDriver driver, EpisodeInformation episode, int retries = 2)
{
do
{
try
{
return await driver.GetEpisodeMediaDetailsAsync(episode);
}
catch (WebDriverTimeoutException)
{
if (retries > 0)
IOHelpers.Print($"Failed. Trying again... ({retries} tries left)");
retries--;
}
} while (retries > -1);

return null;
}
}
20 changes: 19 additions & 1 deletion HoradotTV.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SdarotAPI", "SdarotAPI\Sdar
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SdarotAPITest", "SdarotAPITest\SdarotAPITest.csproj", "{F5E7F813-0F8E-4A82-A262-939F2A536AA1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HoradotTV", "HoradotTV\HoradotTV.csproj", "{F56EF4A4-FCAD-4B59-AF13-1A564839EDD3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HoradotTV", "HoradotTV\HoradotTV.csproj", "{F56EF4A4-FCAD-4B59-AF13-1A564839EDD3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HoradotTV.Console", "HoradotTV.Console\HoradotTV.Console.csproj", "{ADA53817-BD56-46E0-B81E-570D929FC848}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -77,6 +79,22 @@ Global
{F56EF4A4-FCAD-4B59-AF13-1A564839EDD3}.Release|x86.ActiveCfg = Release|Any CPU
{F56EF4A4-FCAD-4B59-AF13-1A564839EDD3}.Release|x86.Build.0 = Release|Any CPU
{F56EF4A4-FCAD-4B59-AF13-1A564839EDD3}.Release|x86.Deploy.0 = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|arm64.ActiveCfg = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|arm64.Build.0 = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|x64.ActiveCfg = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|x64.Build.0 = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|x86.ActiveCfg = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Debug|x86.Build.0 = Debug|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|Any CPU.Build.0 = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|arm64.ActiveCfg = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|arm64.Build.0 = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|x64.ActiveCfg = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|x64.Build.0 = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|x86.ActiveCfg = Release|Any CPU
{ADA53817-BD56-46E0-B81E-570D929FC848}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
15 changes: 0 additions & 15 deletions HoradotTV/App.xaml

This file was deleted.

Loading

0 comments on commit 5ba6d15

Please sign in to comment.