Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7866c8d
Refactor: Add localization support and modernize codebase
SebastianOehm Feb 21, 2026
582f09c
Remove redundant configReader method
SebastianOehm Feb 21, 2026
bb728bf
Move easterEgg string to globalVars
SebastianOehm Feb 21, 2026
8cdd440
Add nullforgiving operator to config
SebastianOehm Feb 21, 2026
10cab5e
fix formatting
SebastianOehm Feb 21, 2026
efb61f5
Bump SSH.NET from 2020.0.2 to 2026.0.0
dependabot[bot] Sep 30, 2026
0619018
Replace localized BCC "no" marker with yes/no menu
SebastianOehm Oct 3, 2026
3c3e830
Remove unused language list resources
SebastianOehm Oct 3, 2026
7639d56
Fix IDE1006 naming violations in types, methods and properties
SebastianOehm Oct 3, 2026
0e57bd1
Fix remaining IDE0044, IDE0059 and IDE0060 violations
SebastianOehm Oct 3, 2026
93babd5
Make GlobalVars fields readonly to fix CA2211
SebastianOehm Oct 3, 2026
179c6c7
Bump target framework from net6.0 to net8.0
SebastianOehm Oct 3, 2026
996a678
Use TryGetValue in LanguageLoader to fix CA1854
SebastianOehm Oct 3, 2026
6b7fad0
Apply collection expressions and primary constructor suggestions
SebastianOehm Oct 3, 2026
e1544d6
Treat HttpClient timeout as offline in APIisOnline
SebastianOehm Oct 3, 2026
9743703
Create the Reaper base directory on startup
SebastianOehm Oct 3, 2026
c20cf0c
Bump target framework from net8.0 to net10.0
SebastianOehm Oct 3, 2026
de83b01
Fix nullable reference warnings
SebastianOehm Oct 3, 2026
9df8684
Match leading whitespace of German resource entries
SebastianOehm Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 60 additions & 55 deletions AltReaper/Checks.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
using System.Globalization;
using System.Net;
using System.Net;
using System.Net.NetworkInformation;
using static System.Console;

namespace Reaper
{
internal class Checks
{
public static bool cfgChecker(JsonHandling.config config)
public static bool CfgChecker(JsonHandling.Config config)
{
bool[] bools = {
String.IsNullOrEmpty(config.apiKey),
String.IsNullOrEmpty(config.senderMail),
String.IsNullOrEmpty(config.senderMailPassword),
String.IsNullOrEmpty(config.hostDomain),
String.IsNullOrEmpty(config.portNumber),
false,
String.IsNullOrEmpty(config.bcc)
};
bool[] bools = [
string.IsNullOrEmpty(config.ApiKey),
string.IsNullOrEmpty(config.SenderMail),
string.IsNullOrEmpty(config.SenderMailPassword),
string.IsNullOrEmpty(config.HostDomain),
string.IsNullOrEmpty(config.PortNumber),
false
];
try
{
if (int.Parse(config.portNumber) < 0 && int.Parse(config.portNumber) > 65535)
if (int.Parse(config.PortNumber) < 0 || int.Parse(config.PortNumber) > 65535)
{ bools[5] = true; }
}
catch { return true; }
Expand All @@ -38,53 +36,60 @@ public static void DeviceIsOnline()
PingReply googleReply = googlePing.Send("8.8.8.8");
if (cloudflareReply.Status == IPStatus.Success | googleReply.Status == IPStatus.Success)
{
WriteLine("Device is online");
WriteLine(Properties.Resources.DeviceOnline);
} else
{
WriteLine(@"
Your device is not connected to the internet.
This application needs internet access.
Please connect your device to the internet to use this application.");
WriteLine(Properties.Resources.DeviceOfflineDetails);
Helper.Closer();
}
}
public static void APIisOnline()
{
//force internal output to be english
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
IWebProxy? defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy?.Credentials = CredentialCache.DefaultCredentials;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.openweathermap.org/data/2.5/weather?q=London");
request.Proxy = defaultWebProxy;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
var handler = new HttpClientHandler
{
Proxy = defaultWebProxy,
UseProxy = true,
DefaultProxyCredentials = CredentialCache.DefaultCredentials
};

using var client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(10);

HttpResponseMessage response = client.GetAsync("https://api.openweathermap.org/data/2.5/weather?q=London").GetAwaiter().GetResult();

// Treat HTTP 401 (Unauthorized) as online (API reachable but invalid key)
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
WriteLine(Properties.Resources.ApiOnline);
return;
}

// Any other HTTP response (including successful 200) is treated as offline for this check
WriteLine(Properties.Resources.ApiOffline);
Helper.Closer();
}
catch (WebException e)
catch (Exception e) when (e is HttpRequestException or TaskCanceledException)
{
if (e.Message.Equals("The remote server returned an error: (401) Unauthorized.", StringComparison.InvariantCultureIgnoreCase))
{ WriteLine("API is online"); }
else { WriteLine("API is not online. Please try again later."); Helper.Closer(); }
// network-level error (DNS, connection failure) or HttpClient.Timeout, which surfaces as TaskCanceledException
WriteLine(string.Format(Properties.Resources.NetworkErrorFormat, e.Message));
Helper.Closer();
}
catch (Exception e)
{
WriteLine("\nThe following Exception was raised : {0}", e.Message);
WriteLine(string.Format(Properties.Resources.ExceptionRaisedFormat, e.Message));
}
}
}
class Menu
class Menu(string inputPrompt, string[] options)
{
private int Index;
private string[] Options;
private string InputPrompt;
public Menu(string inputPrompt, string[] options)
{
InputPrompt = inputPrompt;
Options = options;
Index = 0;
}
private int Index = 0;
private readonly string[] Options = options;
private readonly string InputPrompt = inputPrompt;

public void DisplayAvailableOptions()
{
Console.WriteLine(InputPrompt);
Expand All @@ -95,28 +100,28 @@ public void DisplayAvailableOptions()
if (counter == Index)
{
prefix = ">";
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
BackgroundColor = ConsoleColor.White;
ForegroundColor = ConsoleColor.Black;
}
else
{
prefix = " ";
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;
BackgroundColor = ConsoleColor.Black;
ForegroundColor = ConsoleColor.White;
}
Console.WriteLine($"{prefix} {currentOption}");
WriteLine($"{prefix} {currentOption}");
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Green;
BackgroundColor = ConsoleColor.Black;
ForegroundColor = ConsoleColor.Green;
}
public int IRExcecute()
public int IRExecute()
{
ConsoleKey pressedKey;
do
{
Console.Clear();
Clear();
DisplayAvailableOptions();
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
ConsoleKeyInfo keyInfo = ReadKey(true);
pressedKey = keyInfo.Key;

if (pressedKey == ConsoleKey.UpArrow ^ pressedKey == ConsoleKey.W)
Expand All @@ -138,14 +143,14 @@ public int IRExcecute()
} while (pressedKey != ConsoleKey.Enter);
return Index;
}
public String SRExcecute()
public string SRExecute()
{
ConsoleKey pressedKey;
do
{
Console.Clear();
Clear();
DisplayAvailableOptions();
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
ConsoleKeyInfo keyInfo = ReadKey(true);
pressedKey = keyInfo.Key;

if (pressedKey == ConsoleKey.UpArrow ^ pressedKey == ConsoleKey.W)
Expand Down
101 changes: 48 additions & 53 deletions AltReaper/Helper.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
using Renci.SshNet;
using System.Text.Json;
using System.Text.RegularExpressions;
using static System.Console;
using Reaper.IO;

namespace Reaper
{
public static class globalVars
public static class GlobalVars
{
public static string[] fullySupportedLanguages = { "afrikaans", "albanian", "arabic", "azerbaijani", "bulgarian", "catalan", "czech", "danish", "german", "greek", "english", "basque", "persian", "farsi", "finnish", "french", "galician", "Hebrew", "hindi", "croatian", "hungarian", "indonesian", "italian", "japanese", "korean", "latvian", "lithuanian", "macedonian", "norwegian", "dutch", "polish", "portuguese", "romanian", "russian", "swedish", "slovak", "slovenian", "spanish", "serbian", "thai", "turkish", "ukrainian", "vietnamese", "chinese simplified", "chinese traditional", "zulu" };
public static string[] supportedShortCodes = { "af", "al", "ar", "az", "bg", "ca", "cz", "da", "de", "el", "en", "eu", "fa", "fa", "fi", "fr", "gl", "he", "hi", "hr", "hu", "id", "it", "ja", "kr", "la", "lt", "mk", "no", "nl", "pl", "pt", "pt_br", "ro", "ru", "se", "sk", "sl", "sr", "th", "tr", "ua", "vi", "zh_cn", "zh_tw", "zu" };
public static string appName = "Reaper", devName = "WetterSenseDev", versionNumber = "0.9.1";
public static string[] devData = { appName, devName };
public static string baseLoc = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}\\Reaper";
public static string tree = $"{baseLoc}\\langFiles\\", cfgLoc = $"{baseLoc}\\config.json";
// Load application metadata from resources
public static readonly string[] devData = [Properties.Resources.AppName, Properties.Resources.DevName];
public static readonly string baseLoc = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}\\Reaper";
public static readonly string cfgLoc = $"{baseLoc}\\config.json";
public static readonly string easterEgg = "https://bit.ly/3Gpgiyh";
// App visible languages and mapping to OpenWeatherMap API codes / culture names.
// Keep small for now: English and German only.
public static readonly string[] appLanguages = ["English", "German"];
public static readonly Dictionary<string, string> appToApiCode = new()
{
{ "English", "en" },
{ "German", "de" }
};
// Map app language display -> culture name used for ResourceManager lookups (use BCP-47 style)
public static readonly Dictionary<string, string> appToCulture = new()
{
{ "English", "en" },
{ "German", "de" }
};
}
internal class Helper
{
public static void MailOption(JsonHandling.langVal langValue, JsonHandling.config config, String[] content)
public static void MailOption(JsonHandling.Config config, String[] content)
{
string[] mailOptions = { langValue.yes, langValue.no };
Menu mailMenu = new Menu(langValue.mailWanted, mailOptions);
string[] mailOptions = [Properties.Resources.YesOption, Properties.Resources.NoOption];
Menu mailMenu = new(Properties.Resources.mailWanted, mailOptions);

if (mailMenu.IRExcecute() == 0)
if (mailMenu.IRExecute() == 0)
{
bool partSuccess = false;
while (!partSuccess)
{
if (Checks.cfgChecker(config))
if (Checks.CfgChecker(config))
{
Inputs.configGen(langValue, config);
config = JsonSerializer.Deserialize<JsonHandling.config>(File.ReadAllText(globalVars.cfgLoc));
Inputs.ConfigGen(config);
config = JsonSerializer.Deserialize<JsonHandling.Config>(File.ReadAllText(GlobalVars.cfgLoc))!;
}
Write($"\n{langValue.mailAddressQuery}\n>");
Write($"\n{Properties.Resources.mailAddressQuery}\n>");
CursorVisible = true;
ForegroundColor = ConsoleColor.White;
ForegroundColor = ConsoleColor.Green;
CursorVisible = false;
if (Outputs.MailOutput(ReadLine(), langValue.yourWeatherInfo, content, langValue, config))
if (Outputs.MailOutput(ReadLine() ?? "", Properties.Resources.yourWeatherInfo, content, config))
{
Closer(config, langValue);
Closer(config);
}
else { throw new Exception(); }
partSuccess = true;
Expand All @@ -61,7 +74,7 @@ public static string PasswordMaker()
{
if (password.Length > 0)
{
password = password.Remove(password.Length - 1);
password = password[..^1];
Write("\b \b");
}
}
Expand All @@ -78,64 +91,46 @@ public static string PasswordMaker()
}
public static void SuperUserMode(String superUserPwd, String appName, string directoryLoc)
{
SftpClient sftp = new SftpClient("ssh.strato.de", $"sftp_{appName}@wettersense.de", superUserPwd);
SftpClient sftp = new("ssh.strato.de", 22, $"sftp_{appName}@wettersense.de", superUserPwd);
sftp.Connect();

Stream configLoc = File.Create($"{directoryLoc}\\config.json");
sftp.DownloadFile(@"/config.json", configLoc);
configLoc.Close();

System.Collections.IEnumerator enumerator = sftp.ListDirectory("/langFiles/", null).GetEnumerator();
List<string> files = new List<string>();
while (enumerator.MoveNext())
{
Renci.SshNet.Sftp.SftpFile sftpFile = (Renci.SshNet.Sftp.SftpFile)enumerator.Current;
string name = sftpFile.Name;
files.Add(name);
}

Regex myRegex = new Regex(@"^[a-z]+Text\.json$");
List<string> downloadList = files.Where(f => myRegex.IsMatch(f)).ToList();
foreach (string str in downloadList)
{
Stream langFileLoc = File.Create($"{directoryLoc}\\langFiles\\{str}");
sftp.DownloadFile($"/langFiles/{str}", langFileLoc);
langFileLoc.Close();
}
sftp.Disconnect();
}
public static void Closer()
{
Uninstaller();
WriteLine($"\nThank you for using {globalVars.devData[0]}!");
WriteLine("Weather data powered by openweathermap.org");
WriteLine($"{globalVars.devData[0]} by {globalVars.devData[1]}");
WriteLine("Press any key to exit.");
WriteLine(string.Format(Properties.Resources.ThankYouFormat, GlobalVars.devData[0]));
WriteLine(Properties.Resources.WeatherPoweredBy);
WriteLine(string.Format(Properties.Resources.BylineFormat, GlobalVars.devData[0], GlobalVars.devData[1]));
WriteLine(Properties.Resources.PressAnyKeyExit);
ReadKey(true);
Environment.Exit(0);
}
public static void Closer(JsonHandling.config config, JsonHandling.langVal langValue)
public static void Closer(JsonHandling.Config config)
{
WriteLine($"{langValue.mailSuccessMessage}");
WriteLine($"{Properties.Resources.mailSuccessMessage}");
Uninstaller();
WriteLine($"\nThank you for using {globalVars.devData[0]}!");
WriteLine("Weather data powered by openweathermap.org");
WriteLine($"Mail powered by htmlemail.io & {config.senderMail.Split('@')[1]}");
WriteLine($"{globalVars.devData[0]} by {globalVars.devData[1]}");
WriteLine("Press any key to exit.");
WriteLine(string.Format(Properties.Resources.ThankYouFormat, GlobalVars.devData[0]));
WriteLine(Properties.Resources.WeatherPoweredBy);
WriteLine(string.Format(Properties.Resources.MailPoweredByFormat, config.SenderMail.Split('@')[1]));
WriteLine(string.Format(Properties.Resources.BylineFormat, GlobalVars.devData[0], GlobalVars.devData[1]));
WriteLine(Properties.Resources.PressAnyKeyExit);
ReadKey(true);
Environment.Exit(0);
}
public static void Uninstaller()
{
string baseLoc = $"{Environment.GetEnvironmentVariable("USERPROFILE")}\\Desktop\\Reaper";
string uninstallPrompt = $"\nDo you want to unistall {globalVars.devData[0]}?";
string[] uninstallOptions = { "yes", "no" };
string uninstallPrompt = string.Format(Properties.Resources.UninstallPromptFormat, GlobalVars.devData[0]);
string[] uninstallOptions = [Properties.Resources.YesOption, Properties.Resources.NoOption];
Menu uninstallMenu = new(uninstallPrompt, uninstallOptions);
int uninstallChoice = uninstallMenu.IRExcecute();
int uninstallChoice = uninstallMenu.IRExecute();
if (uninstallChoice == 0)
{
Directory.Delete(baseLoc, true);
Directory.Delete(GlobalVars.baseLoc, true);
}
}
}
Expand Down
Loading
Loading