diff --git a/SteamAccountDataFetcher/FSAgent/WriteJSONAgent.cs b/SteamAccountDataFetcher/FSAgent/WriteJSONAgent.cs index 72cba33..7288664 100644 --- a/SteamAccountDataFetcher/FSAgent/WriteJSONAgent.cs +++ b/SteamAccountDataFetcher/FSAgent/WriteJSONAgent.cs @@ -1,13 +1,14 @@ using System.Collections; using System.Text.Json; +using SteamAccountDataFetcher.SteamDataClient; namespace SteamAccountDataFetcher.FSAgent; -internal class WriteJSONAgent : IList +internal class WriteJSONAgent : IList { private string FilePath { get; init; } - private List _accountDataList; + private List _accountDataList; private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { WriteIndented = true @@ -34,7 +35,7 @@ internal WriteJSONAgent(string path) { using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read)) { - var cachedType = JsonSerializer.Deserialize?>(file); + var cachedType = JsonSerializer.Deserialize?>(file); if (cachedType == null) { _accountDataList = new(); @@ -57,7 +58,7 @@ private void WriteCache() JsonSerializer.Serialize(file, _accountDataList, _jsonSerializerOptions); } - public SteamDataClient.SteamDataClient.ResponseData this[int index] + public ResponseData this[int index] { get => _accountDataList[index]; set => _accountDataList[index] = value; @@ -66,11 +67,11 @@ public SteamDataClient.SteamDataClient.ResponseData this[int index] public int Count => _accountDataList.Count; public bool IsReadOnly => - ((IList)_accountDataList).IsReadOnly; + ((IList)_accountDataList).IsReadOnly; - public int IndexOf(SteamDataClient.SteamDataClient.ResponseData item) => _accountDataList.IndexOf(item); + public int IndexOf(ResponseData item) => _accountDataList.IndexOf(item); - public void Insert(int index, SteamDataClient.SteamDataClient.ResponseData item) + public void Insert(int index, ResponseData item) { _accountDataList.Insert(index, item); WriteCache(); @@ -82,7 +83,7 @@ public void RemoveAt(int index) WriteCache(); } - public void Add(SteamDataClient.SteamDataClient.ResponseData item) + public void Add(ResponseData item) { _accountDataList.Add(item); WriteCache(); @@ -94,11 +95,11 @@ public void Clear() WriteCache(); } - public bool Contains(SteamDataClient.SteamDataClient.ResponseData item) => _accountDataList.Contains(item); + public bool Contains(ResponseData item) => _accountDataList.Contains(item); - public void CopyTo(SteamDataClient.SteamDataClient.ResponseData[] array, int arrayIndex) => _accountDataList.CopyTo(array, arrayIndex); + public void CopyTo(ResponseData[] array, int arrayIndex) => _accountDataList.CopyTo(array, arrayIndex); - public bool Remove(SteamDataClient.SteamDataClient.ResponseData item) + public bool Remove(ResponseData item) { var success = _accountDataList.Remove(item); if (success) @@ -107,7 +108,7 @@ public bool Remove(SteamDataClient.SteamDataClient.ResponseData item) return success; } - public IEnumerator GetEnumerator() => _accountDataList.GetEnumerator(); + public IEnumerator GetEnumerator() => _accountDataList.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/SteamAccountDataFetcher/Program.cs b/SteamAccountDataFetcher/Program.cs index 46d017a..2f5384f 100644 --- a/SteamAccountDataFetcher/Program.cs +++ b/SteamAccountDataFetcher/Program.cs @@ -22,8 +22,8 @@ public static async Task Main() { AccountLoginInfo account = csvAccounts.First(); - SteamDataClient.SteamDataClient steamDataClient = new(account.Username, account.Password, account.SharedSecret); - SteamDataClient.SteamDataClient.ResponseData data = await steamDataClient.GetResponseDataAsync(); + Client steamDataClient = new(account.Username, account.Password, account.SharedSecret); + ResponseData data = await steamDataClient.GetResponseDataAsync(); jsonAccounts.Add(data); csvAccounts.Remove(account); diff --git a/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj b/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj index 4ad7dea..0f9e77e 100644 --- a/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj +++ b/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj @@ -10,7 +10,7 @@ Andrii Lavrenko Utility for collecting Steam account information Andrii Lavrenko - 1.0.0 + 1.1.0 diff --git a/SteamAccountDataFetcher/SteamDataClient/SteamDataClient.cs b/SteamAccountDataFetcher/SteamDataClient/Client.cs similarity index 75% rename from SteamAccountDataFetcher/SteamDataClient/SteamDataClient.cs rename to SteamAccountDataFetcher/SteamDataClient/Client.cs index fced231..7173902 100644 --- a/SteamAccountDataFetcher/SteamDataClient/SteamDataClient.cs +++ b/SteamAccountDataFetcher/SteamDataClient/Client.cs @@ -3,7 +3,23 @@ namespace SteamAccountDataFetcher.SteamDataClient; -internal class SteamDataClient +public class ResponseData +{ + public class AppData + { + public uint AppId { get; set; } = 0; + public long RegistrationTime { get; set; } = 0; + public string AppName { get; set; } = string.Empty; + } + public string Username { get; set; } = string.Empty; + public ulong SteamId { get; set; } = 0; + public List Apps { get; set; } = new(); + public bool IsLimited { get; set; } + public bool IsBanned { get; set; } + public string ApiKey { get; set; } = string.Empty; +} + +internal class Client { private SteamClient _steamClient; private SteamTwoFactorGenerator _steamTwoFactorGenerator; @@ -14,6 +30,7 @@ internal class SteamDataClient private bool _isRunning = false; private bool _isLicensesProcessed = false; + private bool _isWebAPIProcessed = false; internal SteamConfiguration SteamConfiguration { @@ -29,29 +46,10 @@ internal SteamID? SteamID private static uint _instance = 0; private static DateTime _lastConnectionTime = DateTime.MinValue; - - public class ResponseData - { - public class AppData - { - public uint AppId { get; set; } = 0; - public long RegistrationTime { get; set; } = 0; - public string AppName { get; set; } = string.Empty; - } - public bool Success - { - get => SteamId != 0 && !string.IsNullOrEmpty(ApiKey); - } - public string Username { get; set; } = string.Empty; - public ulong SteamId { get; set; } = 0; - public List Apps { get; set; } = new(); - public bool IsLimited { get; set; } - public bool IsBanned { get; set; } - public string ApiKey { get; set; } = string.Empty; - } + private ResponseData _responseData; - internal SteamDataClient(string username, string password, string sharedSecret) + internal Client(string username, string password, string sharedSecret) { ++_instance; @@ -86,8 +84,8 @@ internal SteamDataClient(string username, string password, string sharedSecret) } _steamApps = steamApps; - _callbackManager.Subscribe(OnConnectedAsync); - _callbackManager.Subscribe(OnDisconnectedAsync); + _callbackManager.Subscribe(OnConnectedAsync); + _callbackManager.Subscribe(OnDisconnectedAsync); _callbackManager.Subscribe(OnLoggedOnAsync); _callbackManager.Subscribe(OnLoggedOffAsync); _callbackManager.Subscribe(OnLicenseListAsync); @@ -121,7 +119,7 @@ internal async Task RunAsync() { _callbackManager.RunCallbacks(); - if (_responseData.Success && _isLicensesProcessed) + if (_isLicensesProcessed && _isWebAPIProcessed) _steamClient.Disconnect(); await Task.Delay(TimeSpan.FromSeconds(1)); } @@ -138,7 +136,7 @@ private async Task LoginAsync() }); } - private async void OnConnectedAsync(SteamClient.ConnectedCallback callback) + private async void OnConnectedAsync(SteamKit2.SteamClient.ConnectedCallback callback) { _lastConnectionTime = DateTime.Now; @@ -161,21 +159,12 @@ private async void OnConnectedAsync(SteamClient.ConnectedCallback callback) await LoginAsync(); } - private async void OnDisconnectedAsync(SteamClient.DisconnectedCallback callback) + private async void OnDisconnectedAsync(SteamKit2.SteamClient.DisconnectedCallback callback) { if (callback.UserInitiated) { Log("Disconnected from Steam Network by user."); - if (!_responseData.Success || !_isLicensesProcessed) - { - _responseData.SteamId = 0; - _responseData.Apps.Clear(); - _responseData.IsLimited = false; - _responseData.IsBanned = false; - _responseData.ApiKey = string.Empty; - } - _isRunning = false; return; } @@ -223,22 +212,10 @@ private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback) _steamClient.Disconnect(); return; } - Log("Logged into Web Api. Retrieving Web API Key."); - await Task.Delay(Configuration.DefaultWebRequestTimeout); - - (bool success, string webApiKey) = await _steamWebClient.GetWebApiKeyAsync(); - if (!success) - { - Log("Unable to retrieve Web Api Key.", Logger.Level.Error); - _steamClient.Disconnect(); - return; - } - Log("Retrieved Web API Key."); - - _responseData.ApiKey = webApiKey; + Log("Logged into Web Api."); } - private void OnIsLimitedAccount(DataFetcher.IsLimitedAccountCallback callback) + private async void OnIsLimitedAccount(DataFetcher.IsLimitedAccountCallback callback) { if (callback.Locked) { @@ -247,11 +224,35 @@ private void OnIsLimitedAccount(DataFetcher.IsLimitedAccountCallback callback) return; } + _responseData.IsBanned = callback.CommunityBanned; + _responseData.IsLimited = callback.Limited; + if (callback.CommunityBanned) Log("Account is banned.", Logger.Level.Warning); - _responseData.IsBanned = callback.CommunityBanned; - _responseData.IsLimited = callback.Limited; + if (callback.Limited) + { + Log("Account is limited.", Logger.Level.Warning); + + // Limited accounts is unable to retrieve Web API + _isWebAPIProcessed = true; + return; + } + + Log("Retrieving Web API Key."); + await Task.Delay(Configuration.DefaultWebRequestTimeout); + + (bool success, string webApiKey) = await _steamWebClient.GetWebApiKeyAsync(); + if (!success) + { + Log("Unable to retrieve Web Api Key.", Logger.Level.Error); + _steamClient.Disconnect(); + return; + } + Log("Retrieved Web API Key."); + + _responseData.ApiKey = webApiKey; + _isWebAPIProcessed = true; } private async void OnLoggedOffAsync(SteamUser.LoggedOffCallback callback) @@ -287,14 +288,30 @@ private async void OnLicenseListAsync(SteamApps.LicenseListCallback callback) if (package.PackageID == 0) continue; - Log($"Retrieving PICS info for package {package.PackageID}"); + var packageUnixTime = (new DateTimeOffset(package.TimeCreated)).ToUnixTimeMilliseconds(); + + Log($"Retrieving PICS info for package {package.PackageID}."); List packages = new() { new(package.PackageID, package.AccessToken) }; - var productInfo = await _steamApps.PICSGetProductInfo(new List(0), packages); + AsyncJobMultiple.ResultSet productInfo; + while (true) + { + try + { + productInfo = await _steamApps.PICSGetProductInfo(new List(0), packages); + break; + } + catch (TaskCanceledException) + { + Log($"Failure to receive PICS info for package {package.PackageID}. Retrying...", Logger.Level.Warning); + await Task.Delay(1000); + continue; + } + } if (productInfo == null || !productInfo.Complete || productInfo.Failed || productInfo.Results == null) { Log($"Unable to receive PICS info", Logger.Level.Error); @@ -306,26 +323,37 @@ private async void OnLicenseListAsync(SteamApps.LicenseListCallback callback) { foreach (var packageResponse in product.Packages.Values) { - List apps = GetAppsInKeyValues(packageResponse.KeyValues); - if (apps.Count == 0) + uint[] apps = GetAppsInKeyValues(packageResponse.KeyValues); + List newApps = new(); + + foreach (var app in apps) + { + var appData = _responseData.Apps.SingleOrDefault(x => x?.AppId == app, null); + if (appData == null) + newApps.Add(app); + else + if (appData.RegistrationTime > packageUnixTime) + appData.RegistrationTime = packageUnixTime; + } + + if (newApps.Count == 0) continue; - (var success, var appNames) = await _steamWebClient.GetAppNamesAsync(apps.ToArray()); + (var success, var appNames) = await _steamWebClient.GetAppNamesAsync(newApps.ToArray()); if (!success || appNames == null) { - Log($"Unable to receive Apps info", Logger.Level.Error); + Log($"Unable to receive Apps info.", Logger.Level.Error); _steamClient.Disconnect(); return; } foreach (var app in appNames) { - var offset = new DateTimeOffset(package.TimeCreated); _responseData.Apps.Add(new() { AppId = app.Key, - RegistrationTime = offset.ToUnixTimeMilliseconds(), + RegistrationTime = packageUnixTime, AppName = app.Value }); } @@ -346,20 +374,17 @@ private async void OnLicenseListAsync(SteamApps.LicenseListCallback callback) _isLicensesProcessed = true; } - private List GetAppsInKeyValues(KeyValue keyValue) + private uint[] GetAppsInKeyValues(KeyValue keyValue) { List apps = new(); foreach (KeyValue appKeyValue in keyValue["appids"].Children) { var app = appKeyValue.AsUnsignedInteger(); - if (_responseData.Apps.Any(x => x.AppId == app)) - continue; - apps.Add(app); } - return apps; + return apps.ToArray(); } internal void Log(string message, Logger.Level level = Logger.Level.Info, [CallerMemberName] string callerName = "") => diff --git a/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs b/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs index 96813d1..6cda007 100644 --- a/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs +++ b/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs @@ -15,11 +15,11 @@ internal class SteamTwoFactorGenerator private static int _timeDiffirence = 0; private static bool _aligned = false; - private SteamDataClient _steamDataClient; + private Client _steamClient; - internal SteamTwoFactorGenerator(SteamDataClient steamDataClient) + internal SteamTwoFactorGenerator(Client steamClient) { - _steamDataClient = steamDataClient; + _steamClient = steamClient; } internal async Task GenerateSteamGuardCodeAsync(string sharedSecret) @@ -67,12 +67,12 @@ private async Task AlignTimeAsync() { long currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); KeyValue response; - using (var steamTwoFactorServiceInterface = _steamDataClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_TWO_FACTOR_SERVICE_INTERFACE)) + using (var steamTwoFactorServiceInterface = _steamClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_TWO_FACTOR_SERVICE_INTERFACE)) { response = await steamTwoFactorServiceInterface.CallAsync(HttpMethod.Post, STEAM_QUERY_TIME_METHOD); if (response == null) { - _steamDataClient.Log("Unable to align time.", Logger.Level.Error); + _steamClient.Log("Unable to align time.", Logger.Level.Error); return; } } @@ -80,7 +80,7 @@ private async Task AlignTimeAsync() var serverTime = response["server_time"].AsLong(); if (serverTime == 0) { - _steamDataClient.Log($"{nameof(serverTime)} is invalid.", Logger.Level.Error); + _steamClient.Log($"{nameof(serverTime)} is invalid.", Logger.Level.Error); return; } diff --git a/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs b/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs index 3429b83..9bac87e 100644 --- a/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs +++ b/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs @@ -1,7 +1,7 @@ -using SteamKit2; -using System.Net; +using System.Net; using System.Text; using System.Text.RegularExpressions; +using SteamKit2; namespace SteamAccountDataFetcher.SteamDataClient; @@ -19,23 +19,23 @@ internal class SteamWebClient: IDisposable private static readonly Regex API_KEY_EXPRESSION = new($@"

.*:*.(?'{API_GROUP_KEY}'[0-9A-F]{{32}})

", RegexOptions.Compiled); private HttpClient? _httpClient; - private SteamDataClient _steamDataClient; + private Client _steamClient; private string _webAPIKey = string.Empty; private SemaphoreSlim _webAPISemaphore = new(1); internal string SessionID { get; private set; } = string.Empty; private static DateTime _lastRequestTime = DateTime.MinValue; - internal SteamWebClient(SteamDataClient steamDataClient) + internal SteamWebClient(Client steamClient) { - _steamDataClient = steamDataClient; + _steamClient = steamClient; } internal async Task<(bool, string)> GetWebApiKeyAsync() { if (_httpClient == null) { - _steamDataClient.Log($"{nameof(_httpClient)} is not initialized.", Logger.Level.Error); + _steamClient.Log($"{nameof(_httpClient)} is not initialized.", Logger.Level.Error); return (false, string.Empty); } @@ -57,7 +57,7 @@ internal SteamWebClient(SteamDataClient steamDataClient) } catch (HttpRequestException e) { - _steamDataClient.Log($"Unable to load API page with status code {e.StatusCode}.", Logger.Level.Error); + _steamClient.Log($"Unable to load API page with status code {e.StatusCode}.", Logger.Level.Error); _webAPISemaphore.Release(); return (false, string.Empty); } @@ -66,7 +66,7 @@ internal SteamWebClient(SteamDataClient steamDataClient) var cleanHtml = CleanHtml(responseStream); if (string.IsNullOrEmpty(cleanHtml)) { - _steamDataClient.Log($"{nameof(responseStream)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(responseStream)} is empty.", Logger.Level.Error); _webAPISemaphore.Release(); return (false, string.Empty); } @@ -74,7 +74,7 @@ internal SteamWebClient(SteamDataClient steamDataClient) var webApiKey = MatchApiKey(cleanHtml); if (string.IsNullOrEmpty(webApiKey)) { - _steamDataClient.Log("API key is missing. Generating."); + _steamClient.Log("API key is missing. Generating."); await Task.Delay(Configuration.DefaultWebRequestTimeout); (bool success, webApiKey) = await RegisterWebApiKeyAsync(); @@ -98,7 +98,7 @@ internal SteamWebClient(SteamDataClient steamDataClient) if (apps.Length == 0) { var msg = $"{nameof(apps)} is invalid."; - _steamDataClient.Log(msg, Logger.Level.Error); + _steamClient.Log(msg, Logger.Level.Error); throw new ArgumentOutOfRangeException(msg); } @@ -109,12 +109,12 @@ internal SteamWebClient(SteamDataClient steamDataClient) postData.Add($"appids[{i}]", apps[i]); KeyValue response; - using (var steamUserAuthInterface = _steamDataClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_COMMUNITY_SERVICE_INTERFACE)) + using (var steamUserAuthInterface = _steamClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_COMMUNITY_SERVICE_INTERFACE)) { response = await steamUserAuthInterface.CallAsync(HttpMethod.Get, STEAM_GET_APPS_METHOD, args: postData); if (response == null || response.Children.Count == 0) { - _steamDataClient.Log("Unable to get information about Steam Apps.", Logger.Level.Error); + _steamClient.Log("Unable to get information about Steam Apps.", Logger.Level.Error); return (false, null); } } @@ -138,13 +138,13 @@ internal SteamWebClient(SteamDataClient steamDataClient) if (_httpClient == null) { - _steamDataClient.Log($"{nameof(_httpClient)} is not initialized.", Logger.Level.Error); + _steamClient.Log($"{nameof(_httpClient)} is not initialized.", Logger.Level.Error); return (false, result); } if (string.IsNullOrEmpty(SessionID)) { - _steamDataClient.Log($"{nameof(SessionID)} is null.", Logger.Level.Error); + _steamClient.Log($"{nameof(SessionID)} is null.", Logger.Level.Error); return (false, result); } @@ -164,7 +164,7 @@ internal SteamWebClient(SteamDataClient steamDataClient) } catch (HttpRequestException e) { - _steamDataClient.Log($"Unable to load API page with status code {e.StatusCode}.", Logger.Level.Error); + _steamClient.Log($"Unable to load API page with status code {e.StatusCode}.", Logger.Level.Error); return (false, result); } @@ -173,14 +173,14 @@ internal SteamWebClient(SteamDataClient steamDataClient) var cleanHtml = CleanHtml(responseStream); if (string.IsNullOrEmpty(cleanHtml)) { - _steamDataClient.Log($"{nameof(responseStream)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(responseStream)} is empty.", Logger.Level.Error); return (false, result); } result = MatchApiKey(cleanHtml); if (string.IsNullOrEmpty(result)) { - _steamDataClient.Log("Unable to generate API key.", Logger.Level.Error); + _steamClient.Log("Unable to generate API key.", Logger.Level.Error); return (false, result); } @@ -220,15 +220,15 @@ private string MatchApiKey(string html) internal async Task InitAsync(string webApiUserNonce) { - if (_steamDataClient.SteamID == null || !_steamDataClient.SteamID.IsValid || !_steamDataClient.SteamID.IsIndividualAccount) + if (_steamClient.SteamID == null || !_steamClient.SteamID.IsValid || !_steamClient.SteamID.IsIndividualAccount) { - _steamDataClient.Log($"{nameof(_steamDataClient.SteamID)} is invalid.", Logger.Level.Error); + _steamClient.Log($"{nameof(_steamClient.SteamID)} is invalid.", Logger.Level.Error); return false; } if (string.IsNullOrEmpty(webApiUserNonce)) { - _steamDataClient.Log($"{nameof(webApiUserNonce)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(webApiUserNonce)} is empty.", Logger.Level.Error); return false; } @@ -237,7 +237,7 @@ internal async Task InitAsync(string webApiUserNonce) var publicKey = KeyDictionary.GetPublicKey(EUniverse.Public); if (publicKey == null || publicKey.Length == 0) { - _steamDataClient.Log($"{nameof(KeyDictionary)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(KeyDictionary)} is empty.", Logger.Level.Error); return false; } @@ -254,16 +254,16 @@ internal async Task InitAsync(string webApiUserNonce) { { "encrypted_loginkey", encryptedLoginKey }, { "sessionkey", encryptedSessionKey }, - { "steamid", _steamDataClient.SteamID.ConvertToUInt64() } + { "steamid", _steamClient.SteamID.ConvertToUInt64() } }; KeyValue response; - using (var steamUserAuthInterface = _steamDataClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_USER_AUTH_INTERFACE)) + using (var steamUserAuthInterface = _steamClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_USER_AUTH_INTERFACE)) { response = await steamUserAuthInterface.CallAsync(HttpMethod.Post, STEAM_AUTH_USER_METHOD, args: postData); if (response == null) { - _steamDataClient.Log("Unable to authenticate user to web.", Logger.Level.Error); + _steamClient.Log("Unable to authenticate user to web.", Logger.Level.Error); return false; } } @@ -271,14 +271,14 @@ internal async Task InitAsync(string webApiUserNonce) string? steamLogin = response["token"].AsString(); if (string.IsNullOrWhiteSpace(steamLogin)) { - _steamDataClient.Log($"{nameof(steamLogin)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(steamLogin)} is empty.", Logger.Level.Error); return false; } string? steamLoginSecure = response["tokensecure"].AsString(); if (string.IsNullOrWhiteSpace(steamLoginSecure)) { - _steamDataClient.Log($"{nameof(steamLoginSecure)} is empty.", Logger.Level.Error); + _steamClient.Log($"{nameof(steamLoginSecure)} is empty.", Logger.Level.Error); return false; }