using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Timers; using Timer = System.Timers.Timer; namespace ParentalControlService; public class ParentalControlSvc : BackgroundService { private readonly string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); private readonly string statusPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "status.json"); private readonly ILogger _logger; private Config config; private Status status; private bool currentAction; private readonly Timer checkTimer; private readonly HttpClient httpClient; public ParentalControlSvc(ILogger logger) { _logger = logger; // Load status try { status = JsonSerializer.Deserialize(File.ReadAllText(statusPath))!; } catch (FileNotFoundException) { status = new Status() { Action = true, Until = DateTime.MaxValue }; } currentAction = status.Action; // Load config try { config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; } catch (FileNotFoundException) { config = new Config() { Username = "", Password = "" }; } // Create HTTP Client httpClient = new() { BaseAddress = new Uri(config.Url), Timeout = TimeSpan.FromSeconds(5) }; string credentials = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{config.Username}:{config.Password}")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("ParentalControl", "1.1.0")); // Initialize Timer checkTimer = new(5000); checkTimer.Elapsed += new ElapsedEventHandler(CheckTimerTick); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { await CheckTimerTick(); checkTimer.Start(); while (!stoppingToken.IsCancellationRequested) { await Task.Delay(1000, stoppingToken); } } catch (TaskCanceledException) { checkTimer.Stop(); } catch (Exception ex) { _logger.LogError(ex, "{Message}", ex.Message); Environment.Exit(1); } } private void SaveStatus() { File.WriteAllText(statusPath, JsonSerializer.Serialize(status)); } private static void RunScript(string scriptName) { scriptName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{scriptName}.ps1"); if (!File.Exists(scriptName)) { return; } ProcessStartInfo psi = new() { FileName = "powershell.exe", Arguments = $"-ExecutionPolicy Bypass -File \"{scriptName}\"", UseShellExecute = false, WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory, }; Process.Start(psi); } private async Task CheckTimerTick() { try { Data data = await httpClient.GetFromJsonAsync("api/data"); if (data.Status != status) { status = data.Status; SaveStatus(); } } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { _logger.LogError(ex, "{Message}", ex.Message); } if (status.Until != null && DateTime.Now >= status.Until) { // Fetch failed, we need to advance the schedule on our own status.Action = !status.Action; status.Until = status.ThenUntil; status.ThenUntil = null; SaveStatus(); } if (currentAction == status.Action) { return; } currentAction = !currentAction; if (currentAction) { _logger.LogInformation("[{time}] Unblocked", DateTime.Now); RunScript("unblock"); } else { _logger.LogInformation("[{time}] Blocked", DateTime.Now); RunScript("block"); } } private async void CheckTimerTick(object? sender, ElapsedEventArgs e) { await CheckTimerTick(); } }