Files
TrackTrendsDoc/temp_annexes/Code/F1TVEmulator.md
T
2023-06-05 16:17:17 +02:00

13 KiB

F1TVEmulator.cs

/// Author : Maxime Rohmer
/// Date : 30/05/2023
/// File : F1TVEmulator.cs
/// Brief : Class that contains methods to emulate a browser and navigate the F1TV website
/// Version : Alpha 1.0

using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TrackTrends
{
    internal class F1TVEmulator
    {
        public const string COOKIE_HOST = ".formula1.com";
        public const string PYTHON_COOKIE_RETRIEVAL_FILENAME = "recoverCookiesCSV.py";
        public const string GECKODRIVER_FILENAME = @"geckodriver-v0.27.0-win64\geckodriver.exe";
        //BE CAREFULL IF YOU CHANGE IT HERE YOU NEED TO CHANGE IT IN THE PYTHON SCRIPT TOO
        public const string COOKIES_CSV_FILENAME = "cookies.csv";

        private FirefoxDriver Driver;

        private bool _ready;
        private string _grandPrixUrl;
        public string GrandPrixUrl { get => _grandPrixUrl; private set => _grandPrixUrl = value; }
        public bool Ready { get => _ready; set => _ready = value; }
        public F1TVEmulator(string grandPrixUrl)
        {
            GrandPrixUrl = grandPrixUrl;
            Ready = false;
        }
        /// <summary>
        /// Will start the python programm that runs the Cookie Recovering
        /// </summary>
        private void StartCookieRecovering()
        {
            string scriptPath = PYTHON_COOKIE_RETRIEVAL_FILENAME;
            Process process = new Process();
            process.StartInfo.FileName = "python.exe";
            process.StartInfo.Arguments = scriptPath;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
        }
        /// <summary>
        /// Method that will recover the needed cookies in the DB
        /// </summary>
        /// <param name="host"> The host of the wanted cookie ex: ./formula1.com</param>
        /// <param name="name">The name of the wanted cookie ex: login</param>
        /// <returns>returns the value of the cookie if it has been found</returns>
        /// <exception cref="InvalidOperationException"></exception>
        public string GetCookie(string host, string name)
        {
            StartCookieRecovering();
            string value = "";
            List<Cookie> cookies = new List<Cookie>();
            if (File.Exists(COOKIES_CSV_FILENAME))
            {
                using (var reader = new StreamReader(COOKIES_CSV_FILENAME))
                {
                    // Read the header row and validate column order
                    string header = reader.ReadLine();
                    string[] expectedColumns = { "host_key", "name", "value", "path", "expires_utc", "is_secure", "is_httponly" };
                    string[] actualColumns = header.Split(',');
                    for (int i = 0; i < expectedColumns.Length; i++)
                    {
                        if (expectedColumns[i] != actualColumns[i])
                        {
                            throw new InvalidOperationException($"Expected column '{expectedColumns[i]}' at index {i} but found '{actualColumns[i]}'");
                        }
                    }

                    // Read each data row and parse values into a Cookie object
                    while (!reader.EndOfStream)
                    {
                        string line = reader.ReadLine();
                        string[] fields = line.Split(',');

                        string hostname = fields[0];
                        string cookieName = fields[1];

                        if (hostname == host && cookieName == name)
                        {
                            value = fields[2];
                        }
                    }
                }
            }
            return value;
        }
        /// <summary>
        /// Starts the headless browser
        /// </summary>
        /// <returns>Error code 1xx</returns>
        public async Task<int> Start()
        {
            Ready = false;

            string loginCookieName = "login";
            string loginSessionCookieName = "login-session";
            string loginCookieValue = GetCookie(COOKIE_HOST, loginCookieName);
            string loginSessionValue = GetCookie(COOKIE_HOST, loginSessionCookieName);

            //Cookie retreival has gone wrong (usually its because of python not being installed properly)
            if (loginCookieValue == "" || loginSessionValue == "")
                return 100;

            var service = FirefoxDriverService.CreateDefaultService(GECKODRIVER_FILENAME);
            service.Host = "127.0.0.1";
            service.Port = 5555;

            FirefoxProfile profile = new FirefoxProfile();
            FirefoxOptions options = new FirefoxOptions();
            //profile.SetPreference("full-screen-api.ignore-widgets", true);
            //profile.SetPreference("media.hardware-video-decoding.enabled", true);
            //profile.SetPreference("full-screen-api.enabled", true);
            options.Profile = profile;
            profile.SetPreference("layout.css.devPixelsPerPx", "1.0");

            options.AcceptInsecureCertificates = true;
            options.AddArgument("--headless");
            //options.AddArgument("--start-maximized");
            //options.AddArgument("--window-size=1920x1080");
            //options.AddArgument("--width=" + windowWidth);
            //options.AddArgument("--height=" + windowHeight);
            //options.AddArgument("-window-size=1920x1080");
            //options.AddArgument("--width=1920");
            //options.AddArgument("--height=1080");
            //profile

            try
            {
                Driver = new FirefoxDriver(service, options);
            }
            catch
            {
                Ready = false;
                return 101;
            }

            Actions actions = new Actions(Driver);
            var loginCookie = new Cookie(loginCookieName, loginCookieValue, COOKIE_HOST, "/", DateTime.Now.AddDays(5));
            var loginSessionCookie = new Cookie(loginSessionCookieName, loginSessionValue, COOKIE_HOST, "/", DateTime.Now.AddDays(5));

            Driver.Navigate().GoToUrl("https://f1tv.formula1.com/");

            Driver.Manage().Cookies.AddCookie(loginCookie);
            Driver.Manage().Cookies.AddCookie(loginSessionCookie);

            try
            {
                Driver.Navigate().GoToUrl(GrandPrixUrl);
            }
            catch
            {
                //The url is not a valid url
                Driver.Dispose();
                return 103;
            }

            //Waits for the page to fully load
            Driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(30);

            //Removes the cookie prompt
            try
            {
                IWebElement conscentButton = Driver.FindElement(By.Id("truste-consent-button"));
                conscentButton.Click();
            }
            catch
            {
                //Could not locate the cookie button
                Screenshot("ERROR104");
                Driver.Dispose();
                return 104;
            }

            //Again waits for the page to fully load (when you accept cookies it takes a little time for the page to load)
            //Cannot use The timeout because the feed loading is not really loading so there is not event or anything
            Thread.Sleep(5000);

            //Switches to the Data channel
            try
            {
                IWebElement dataChannelButton = Driver.FindElement(By.ClassName("data-button"));
                dataChannelButton.Click();
            }
            catch
            {
                //If the data button does not exists its because the user is not connected
                Screenshot("ERROR102");
                Driver.Dispose();
                return 102;
            }

            //Open settings 
            // Press the space key, this should make the setting button visible
            // It does not matter if the feed is paused because when changing channel it autoplays
            actions.SendKeys(OpenQA.Selenium.Keys.Space).Perform();
            //Clicks on the settings Icon

            int settingsClickTries = 0;
            bool settingsClickSuccess = false;
            while (settingsClickTries < 100 && !settingsClickSuccess)
            {
                Thread.Sleep(100);
                try
                {
                    IWebElement settingsButton = Driver.FindElement(By.ClassName("bmpui-ui-settingstogglebutton"));
                    settingsButton.Click();
                    IWebElement selectElement = Driver.FindElement(By.ClassName("bmpui-ui-videoqualityselectbox"));
                    SelectElement select = new SelectElement(selectElement);
                    IWebElement selectOption = selectElement.FindElement(By.CssSelector("option[value^='1080_']"));
                    selectOption.Click();
                    settingsClickSuccess = true;
                }
                catch
                {
                    //Sometimes it can crash because it could not get the options to show up in time. When it happens just retry
                    settingsClickSuccess = false;
                    settingsClickTries++;
                }
            }

            if (!settingsClickSuccess)
            {
                Screenshot("ERROR105");
                Driver.Dispose();
                return 105;
            }

            Screenshot("BEFOREFULLSCREEN");

            //Makes the feed fullscreen
            int fullScreenClickTries = 0;
            bool fullScreenClickSuccess = false;
            Driver.Manage().Window.Maximize();
            //WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
            while (fullScreenClickTries < 100 && !fullScreenClickSuccess)
            {
                Thread.Sleep(150);
                try
                {
                    IWebElement fullScreenButton = Driver.FindElement(By.ClassName("bmpui-ui-fullscreentogglebutton"));
                    fullScreenButton.Click();
                    fullScreenClickSuccess = true;
                }
                catch
                {
                    fullScreenClickSuccess = false;
                    fullScreenClickTries++;
                }
            }

            if (!fullScreenClickSuccess)
            {
                Screenshot("ERROR106");
                Driver.Dispose();
                return 106;
            }

            Screenshot("AFTERFULLSCREEN");

            //STARTUP FINISHED READY TO SCREENSHOT
            Ready = true;
            return 0;
        }
        /// <summary>
        /// Takes a screenshot of what the headless browser is displaying
        /// </summary>
        /// <param name="name">Optional ! The name of the picture so it can be saved</param>
        /// <returns>Returns the screenshot in the bitmap format</returns>
        public Bitmap Screenshot(string name = "TEST")
        {
            Bitmap result = new Bitmap(4242, 6969);
            try
            {
                //Screenshot scrsht = ((ITakesScreenshot)Driver).GetScreenshot();
                //profileriver.SetPreferencC:\Users\Moi\source\repos\Test_Merge\README.mde("layout.css.devPixelsPerPx", "1.0");

                //Screenshot scrsht = Driver.GetFullPageScreenshot();
                Screenshot scrsht = Driver.GetScreenshot();

                byte[] screenshotBytes = Convert.FromBase64String(scrsht.AsBase64EncodedString);
                MemoryStream stream = new MemoryStream(screenshotBytes);

                result = new Bitmap(stream);
                //result.Save(name + ".png");
                scrsht.SaveAsFile(name + ".png");
            }
            catch
            {
                //Nothing for now
            }
            return result;
        }
        /// <summary>
        /// Stops the Emulation. Note: if you plan to start it again please use ResetDriver() instead
        /// </summary>
        public void Stop()
        {
            Ready = false;
            if (Driver != null)
                Driver.Dispose();
        }
        /// <summary>
        /// Resets the emulation
        /// </summary>
        public void ResetDriver()
        {
            Ready = false;
            if (Driver != null)
                Driver.Dispose();
            Driver = null;
        }
    }
}