diff --git a/CCTV/CameraSearcher.cs b/CCTV/CameraSearcher.cs
new file mode 100644
index 0000000..decb73f
--- /dev/null
+++ b/CCTV/CameraSearcher.cs
@@ -0,0 +1,106 @@
+using Leaf.xNet;
+using System.Drawing;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.RegularExpressions;
+
+namespace OpenCCTV.CCTV
+{
+ internal class CameraSearcher
+ {
+ ///
+ /// Finds Cameras with ease.
+ ///
+ /// The country code
+ public static void FindCameras(string countryCode)
+ {
+ Console.Clear();
+ Console.Title = $"CCTV Browser by Serialized - Searching Cameras in {countryCode}";
+ Interface.InterfaceMaker.PrintLogo();
+
+ List camerasList = new List();
+
+ HttpRequest hr = new HttpRequest()
+ {
+ IgnoreProtocolErrors = true,
+ };
+
+ hr.SslCertificateValidatorCallback += (obj, cert, ssl, error) => (cert as X509Certificate2).Verify();
+
+ try
+ {
+ int lastPage = GetLastPage(hr, countryCode);
+
+ for (int i = 0; i < lastPage; i++)
+ {
+ string[] cameras = GetCameras(hr, countryCode, i);
+
+ for (int j = 0; j < cameras.Length; j++)
+ {
+ camerasList.Add(cameras[j]);
+ Interface.InterfaceMaker.WriteLine(Color.White, $"{countryCode}-{camerasList.Count}", $"{cameras[j]}\n");
+ }
+ }
+
+ Console.WriteLine();
+ Interface.InterfaceMaker.WriteLine(Color.White, "A", $"Export All Cameras\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, "B", $"Open All Cameras\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, "C", $"Back\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, ">", $"");
+
+ string userInput = Console.ReadLine();
+
+ switch (userInput)
+ {
+ case "A":
+ File.WriteAllLines($"{countryCode}.txt", camerasList);
+ Interface.InterfaceMaker.WriteLine(Color.White, "~", "Exported All Cameras!\n");
+ Utils.CountryMenu();
+ break;
+
+ case "B":
+ foreach (string cameras in camerasList)
+ Program.OpenWebpage(cameras);
+
+ break;
+
+ case "C":
+ Utils.CountryMenu();
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ Interface.InterfaceMaker.WriteLine(Color.White, "Error", "Couldn't Obtain Cameras.\n");
+ }
+ }
+
+ ///
+ /// Checks how many pages there are to check
+ ///
+ /// The HttpRequest
+ /// The country
+ /// The number of pages
+ private static int GetLastPage(HttpRequest hr, string country)
+ {
+ var camResp = hr.Get($"http://www.insecam.org/en/bycountry/{country}");
+ var lastPageMatch = Regex.Match(camResp.ToString(), @"pagenavigator\(""\?page="", (\d+)");
+ return lastPageMatch.Success ? int.Parse(lastPageMatch.Groups[1].Value) : 0;
+ }
+
+ ///
+ /// Gets the list of open cameras.
+ ///
+ ///
+ ///
+ ///
+ /// A list of cameras
+ private static string[] GetCameras(HttpRequest hr, string country, int page)
+ {
+ var camResp = hr.Get($"http://www.insecam.org/en/bycountry/{country}/?page={page}");
+ return Regex.Matches(camResp.ToString(), @"http://\d+.\d+.\d+.\d+:\d+")
+ .Cast()
+ .Select(match => match.Value)
+ .ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CCTV/CountrySearcher.cs b/CCTV/CountrySearcher.cs
new file mode 100644
index 0000000..0a0e7dc
--- /dev/null
+++ b/CCTV/CountrySearcher.cs
@@ -0,0 +1,49 @@
+using Leaf.xNet;
+using Newtonsoft.Json;
+using System.Drawing;
+using System.Security.Cryptography.X509Certificates;
+
+namespace OpenCCTV.CCTV
+{
+ internal class CountrySearcher
+ {
+ ///
+ /// Displays the amount of cameras per country.
+ ///
+ public static void DisplayCountryInfo()
+ {
+ HttpRequest hr = new HttpRequest()
+ {
+ IgnoreProtocolErrors = true,
+ };
+
+ hr.SslCertificateValidatorCallback += (obj, cert, ssl, error) => (cert as X509Certificate2).Verify();
+
+ try
+ {
+ HttpResponse countryResp = hr.Get("http://www.insecam.org/en/jsoncountries/");
+ string countryJson = countryResp.ToString();
+ var countryData = JsonConvert.DeserializeObject(countryJson);
+
+ if (countryData != null && countryData.Countries != null)
+ {
+ foreach (var country in countryData.Countries)
+ {
+ if (country.Value.Country == "-") continue;
+
+ Interface.InterfaceMaker.PrintCountryInfo(
+ country.Key.PadRight(2),
+ country.Value.Country.PadRight(25),
+ country.Value.Count);
+ }
+ }
+
+ Console.WriteLine();
+ }
+ catch (Exception ex)
+ {
+ Interface.InterfaceMaker.WriteLine(Color.White, "Error", "Couldn't obtain Country Statistics.\n");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CCTV/Utils.cs b/CCTV/Utils.cs
new file mode 100644
index 0000000..f581c6b
--- /dev/null
+++ b/CCTV/Utils.cs
@@ -0,0 +1,55 @@
+using System.Drawing;
+
+namespace OpenCCTV.CCTV
+{
+ ///
+ /// Utility class containing various visible output methods.
+ ///
+ internal class Utils
+ {
+ ///
+ /// Menu that helps displaying country statistics and user input.
+ ///
+ public static void CountryMenu()
+ {
+ SetConsoleTitle("CCTV Browser by Serialized - Displaying Information By Country");
+ Interface.InterfaceMaker.WriteLine(Color.White, "~", "Retrieving Country Statistics\n\n");
+
+ /* Display the fetched information. */
+ CountrySearcher.DisplayCountryInfo();
+
+ Interface.InterfaceMaker.WriteLine(Color.White, "~", "Enter Your Wanted Country\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, ">", "");
+
+ Console.ForegroundColor = ConsoleColor.White;
+ string countryInput = Console.ReadLine();
+ CameraSearcher.FindCameras(countryInput);
+ }
+
+ ///
+ /// Set Console.Title and Flush Console.
+ ///
+ /// The consoles title.
+ private static void SetConsoleTitle(string title)
+ {
+ Console.Clear();
+ Console.Title = title;
+ Interface.InterfaceMaker.PrintLogo();
+ }
+
+ ///
+ /// Deserializable class holding Country Information
+ ///
+ public class Country
+ {
+ public string? Status { get; set; }
+ public Dictionary? Countries { get; set; }
+ }
+
+ public class CountryInfo
+ {
+ public string? Country { get; set; }
+ public int Count { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Interface/InterfaceMaker.cs b/Interface/InterfaceMaker.cs
new file mode 100644
index 0000000..e5b9fc0
--- /dev/null
+++ b/Interface/InterfaceMaker.cs
@@ -0,0 +1,119 @@
+using System.Drawing;
+using Console = Colorful.Console;
+
+namespace OpenCCTV.Interface
+{
+ ///
+ /// Provides methods for the interface.
+ ///
+ public class InterfaceMaker
+ {
+ ///
+ /// Represents the ASCII 'header' for the application.
+ ///
+ private static readonly string[] _asciiLogo =
+ {
+ " ",
+ " ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗████████╗██╗ ██╗",
+ "██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██╔════╝╚══██╔══╝██║ ██║",
+ "██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ██║ ██║ ██║",
+ "██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██║ ╚██╗ ██╔╝",
+ "╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗╚██████╗ ██║ ╚████╔╝ ",
+ " ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ",
+ " ",
+ };
+
+ ///
+ /// Represents the colors for the ASCII logo.
+ ///
+ private static readonly List _colorList = new List()
+ {
+ Color.White,
+ ColorTranslator.FromHtml("#FCEE2B"),
+ ColorTranslator.FromHtml("#E2D626"),
+ ColorTranslator.FromHtml("#C8BD21"),
+ ColorTranslator.FromHtml("#AFA51D"),
+ ColorTranslator.FromHtml("#958C18"),
+ ColorTranslator.FromHtml("#7B7413"),
+ };
+
+ ///
+ /// Prints the ASCII logo to the console with customizable colors (scheme set to yellow -> black).
+ ///
+ public static void PrintLogo()
+ {
+ for (int i = 0; i < _asciiLogo.Length - 1; i++)
+ {
+ Console.SetCursorPosition(Console.WindowWidth / 2 - _asciiLogo[0].Length / 2, i);
+ Console.WriteLine(_asciiLogo[i], _colorList[i]);
+
+ switch (i)
+ {
+ case 1:
+ Colors.Primary = _colorList[i];
+ break;
+
+ case 2:
+ Colors.Secondary = _colorList[i];
+ break;
+ }
+ }
+
+ // Bottom padding.
+ Console.WriteLine();
+ Console.WriteLine();
+ Console.WriteLine();
+ }
+
+ ///
+ /// Override method to display formatted and colorized WriteLine.
+ ///
+ /// The color of the message.
+ /// The prefix of the message.
+ /// The message to be displayed.
+ public static void WriteLine(Color color, string prefix, string msg)
+ {
+ Console.Write(" [", Colors.Secondary);
+ Console.Write(prefix, Color.White);
+ Console.Write("]", Colors.Secondary);
+ Console.Write(" → ", Color.White);
+ Console.Write(msg, color);
+ }
+
+ ///
+ /// Method to print country information formatted and colorized.
+ ///
+ /// The two characters to identify a country.
+ /// The name of the country.
+ /// The count of cameras.
+ public static void PrintCountryInfo(string countryCode, string countryName, int count)
+ {
+ Console.Write(" [", Colors.Secondary);
+ Console.Write($"{countryCode}", Color.White);
+ Console.Write("]", Colors.Secondary);
+ Console.Write(" → ", Color.White);
+ Console.Write($"{countryName}", Colors.Primary);
+ Console.Write(" → ", Color.White);
+
+ switch (count == 1)
+ {
+ case true:
+ Console.WriteLine(count + " Camera", Color.White);
+ break;
+
+ case false:
+ Console.WriteLine(count + " Cameras", Color.White);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Provides Color variables for the interface.
+ ///
+ public class Colors
+ {
+ public static Color Primary { get; set; }
+ public static Color Secondary { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenCCTV.csproj b/OpenCCTV.csproj
new file mode 100644
index 0000000..83acc52
--- /dev/null
+++ b/OpenCCTV.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/OpenCCTV.sln b/OpenCCTV.sln
new file mode 100644
index 0000000..0a7d2b5
--- /dev/null
+++ b/OpenCCTV.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34202.233
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenCCTV", "OpenCCTV.csproj", "{6E7AA6DD-E5E8-42C4-ADE8-A25C39A36C18}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6E7AA6DD-E5E8-42C4-ADE8-A25C39A36C18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E7AA6DD-E5E8-42C4-ADE8-A25C39A36C18}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E7AA6DD-E5E8-42C4-ADE8-A25C39A36C18}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E7AA6DD-E5E8-42C4-ADE8-A25C39A36C18}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5A07CEC4-DDD4-4118-AD8F-66F2A66E457C}
+ EndGlobalSection
+EndGlobal
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..1436a75
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,77 @@
+using System.Diagnostics;
+using System.Drawing;
+
+namespace OpenCCTV
+{
+ internal class Program
+ {
+ private static void Main(string[] args)
+ {
+ Console.Title = "CCTV Browser by Serialized";
+ Console.CursorVisible = false;
+
+ OpenMenu();
+ Console.ReadKey(); /* Prevent from closing */
+ }
+
+ ///
+ /// Displays the main menu for OpenCCTV.
+ ///
+ private static void OpenMenu()
+ {
+ Console.Clear();
+ Interface.InterfaceMaker.PrintLogo();
+ Interface.InterfaceMaker.WriteLine(Color.White, "~", "Welcome to OpenCCTV - Select An Option\n\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, "1", "View Countries\n");
+ Interface.InterfaceMaker.WriteLine(Color.White, "2", "Goto Repository\n");
+
+ ConsoleKey cki = getCki;
+
+ switch (cki)
+ {
+ case ConsoleKey.NumPad1:
+ case ConsoleKey.D1:
+ CCTV.Utils.CountryMenu();
+ break;
+
+ case ConsoleKey.NumPad2:
+ case ConsoleKey.D2:
+ OpenWebpage("https://github.com/nak0823/OpenCCTV"); // Star the repo ;)
+ OpenMenu();
+ break;
+
+ default:
+ OpenMenu();
+ break;
+ }
+ }
+
+ ///
+ /// Opens a webpage using cmd.
+ ///
+ ///
+ public static void OpenWebpage(string url)
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "cmd",
+ Arguments = $"/c start {url}",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true
+ });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Returns the users selected ConsoleKey.
+ ///
+ private static ConsoleKey getCki => Console.ReadKey(true).Key;
+ }
+}
\ No newline at end of file