Skip to content

Commit

Permalink
Add CLI options & improve rescue as well as logging
Browse files Browse the repository at this point in the history
  • Loading branch information
NoxModule committed Jun 22, 2021
1 parent 5f3e078 commit 013176e
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
bin/
obj/
User Data/

*.log
1 change: 1 addition & 0 deletions PlexAutoIntroSkip.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.0.0-beta2" />
</ItemGroup>

Expand Down
168 changes: 129 additions & 39 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using CommandLine;
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Remote;
Expand All @@ -12,77 +14,119 @@ namespace PlexAutoIntroSkip
{
public class Program
{
public class ProgramOptions
{
[Option('d', "debug", Required = false,
HelpText = "Show console window.")]
public bool ShowConsoleWindow { get; set; }

[Option('w', "wait-time", Required = false, Default = 2500,
HelpText = "Time to wait after Skip Button becomes visible before clicking.")]
public int SkipButtonWaitTime { get; set; }

[Value(0, MetaName = "plex-url", HelpText = "Plex URL to use.")]
public string PlexUrl { get; set; }
}

/// <summary>
/// Sets the specified window's show state.
/// </summary>
/// <remarks>
/// See <see href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow">ShowWindow</see> MS Docs for more information.
/// </remarks>
/// <param name="hWnd">A handle to the window.</param>
/// <param name="nCmdShow">Controls how the window is to be shown.</param>
/// <returns>
/// If the window was previously visible, the return value is nonzero.
/// If the window was previously hidden, the return value is zero.
/// </returns>
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

/// <summary>
/// Program entry point.
/// </summary>
/// <param name="args">Command-line arguments.</param>
public static void Main(string[] args)
{
// Hide console application's console window.
var options = GetProgramOptions(args);
var hWnd = Process.GetCurrentProcess().MainWindowHandle;
var edgeProcessName = "msedge";

// Called using own process window?
if (hWnd.ToInt32() != 0)
if (options.ShowConsoleWindow == false && hWnd.ToInt32() != 0)
{
// Hide console application's console window.
ShowWindow(hWnd, 0);
}

var driver = OpenBrowserToPlexApp(args);
MainProgramLoop(driver);
}

private static RemoteWebDriver OpenBrowserToPlexApp(string[] args)
{
var plexUrl = args[0];
var currentWorkingPath = Directory.GetCurrentDirectory();
var options = new EdgeOptions();
options.UseChromium = true;
var edgeOptions = new EdgeOptions();
edgeOptions.UseChromium = true;

// Disable infobar "Chrome is being controlled by automated test software".
options.AddExcludedArgument("enable-automation");
options.AddAdditionalOption("useAutomationExtension", false);
// Disable "Chrome is being controlled by automated test software" infobar.
edgeOptions.AddExcludedArgument("enable-automation");
edgeOptions.AddAdditionalOption("useAutomationExtension", false);

options.AddArguments(
$"user-data-dir={currentWorkingPath}\\User Data",
edgeOptions.AddArguments(
$"user-data-dir={Directory.GetCurrentDirectory()}\\User Data",
"profile-directory=Profile 1",
$"app={plexUrl}");
$"app={options.PlexUrl}");

var edgeProcessIds = Process.GetProcessesByName(edgeProcessName).Select(p => p.Id);
var service = EdgeDriverService.CreateDefaultService();
var driver = new EdgeDriver(service, edgeOptions);
var browserProcessId = Process.GetProcessesByName(edgeProcessName).Select(p => p.Id)
.Except(edgeProcessIds)
.First();

var driver = new EdgeDriver(options);;
while (ProcessExistsById(browserProcessId))
{
try
{
MainProgramLoop(driver, options, browserProcessId);
}
catch (Exception exception)
{
LogException(exception);
}
}

return driver;
Process.GetProcessById(service.ProcessId).Kill();
}

private static void MainProgramLoop(RemoteWebDriver driver)
/// <summary>
/// Run main program loop.
/// </summary>
/// <param name="driver"><see name="RemoteWebDriver"/> to be used to drive web browser.</param>
/// <param name="options"><see name="ProgramOptions"/>.</param>
/// <param name="browserProcessId">Web browser process ID to check if exists.</param>
private static void MainProgramLoop(RemoteWebDriver driver, ProgramOptions options, int browserProcessId)
{
var waitDriver = new WebDriverWait(driver, TimeSpan.FromDays(365));
var nullWebElement = new RemoteWebElement(driver, string.Empty);

var skipIntroButtonXPath = "//button[text()='Skip Intro']";
RemoteWebElement skipIntroButton;
while (true)
while (ProcessExistsById(browserProcessId))
{
try
// Waiting for Skip Intro button to be visible, or for browser to be closed manually.
var skipIntroButton = (RemoteWebElement)waitDriver.Until(webDriver =>
ProcessExistsById(browserProcessId)
? webDriver.FindElement(By.XPath(skipIntroButtonXPath))
: nullWebElement);

// Skip Intro button will only be equal to `nullWebElement` if the browser process
// no longer exists.
if (skipIntroButton == nullWebElement)
{
// Waiting for Skip Intro button...
skipIntroButton = (RemoteWebElement)waitDriver.Until(webDriver =>
{
// Check if web browser window exists by getting window's position.
_ = driver.Manage().Window.Position;

return webDriver.FindElement(By.XPath(skipIntroButtonXPath));
});
}
catch (WebDriverException)
{
// Unable to connect to web browser window, was most likely closed manually.
driver.Quit();
break;
}

// Skip Intro button is visible before intro starts, so wait for the actual intro to start.
Thread.Sleep(2000);
Thread.Sleep(options.SkipButtonWaitTime);

skipIntroButton.Click();

// Waiting for Skip Intro button to not be visible...
// Waiting for Skip Intro button to no longer be visible.
waitDriver.Until(webDriver =>
{
try
Expand All @@ -98,5 +142,51 @@ private static void MainProgramLoop(RemoteWebDriver driver)
});
}
}

/// <summary>
/// Parse <paramref name="args"/> into <see cref="ProgramOptions"/>.
/// </summary>
/// <param name="args">Command-line arguments.</param>
/// <returns><see cref="ProgramOptions"/></returns>
private static ProgramOptions GetProgramOptions(string[] args)
{
ProgramOptions options = null;
CommandLine.Parser.Default.ParseArguments<ProgramOptions>(args)
.WithParsed(parsedOptions => options = parsedOptions);

return options;
}

/// <summary>
/// Recursively write <see name="Exception"/> to error log file.
/// </summary>
/// <param name="exception">Root <see name="Exception"/>.</param>
private static void LogException(Exception exception)
{
using (var writer = new StreamWriter("error.log", append: true))
{
writer.WriteLine("--------------------------------------------------");
writer.WriteLine($"[{DateTime.Now}]");
writer.WriteLine();

while (exception != null)
{
writer.WriteLine(exception.GetType().FullName);
writer.WriteLine(exception.Message);
writer.WriteLine(exception.StackTrace);
writer.WriteLine();

exception = exception.InnerException;
}
}
}

/// <summary>
/// Check if the process exists by given <paramref name="processId"/>.
/// </summary>
/// <param name="processId">Process ID.</param>
/// <returns>True if process exists, otherwise false.</returns>
private static bool ProcessExistsById(int processId)
=> Process.GetProcesses().Any(p => p.Id == processId);
}
}

0 comments on commit 013176e

Please sign in to comment.