private void OnTimer(object state) { if (Settings.Current.IRC.Enabled && !IRC.Instance.IsConnected) { Log.WriteWarn("Watchdog", "Forcing IRC reconnect."); IRC.Instance.Connect(); } if (Steam.Instance.Client.IsConnected && Application.ChangelistTimer.Enabled) { AccountInfo.Sync(); if (WebAuth.IsAuthorized) { TaskManager.RunAsync(async() => await AccountInfo.RefreshAppsToIdle()); } else { WebAuth.AuthenticateUser(); } } else if (DateTime.Now.Subtract(Connection.LastSuccessfulLogin).TotalMinutes >= 5.0) { Log.WriteWarn("Watchdog", "Forcing a Steam reconnect."); Connection.Reconnect(null, null); } }
public async static Task RefreshAppsToIdle() { if (!Settings.Current.CanQueryStore) { return; } List <uint> newAppsToIdle; using (var handler = new HttpClientHandler()) using (var client = new HttpClient(handler)) { handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; client.DefaultRequestHeaders.Add("Host", "steamdb.info"); var response = await client.GetAsync("https://localhost/api/GetNextAppIdToIdle/"); if (!response.IsSuccessStatusCode) { Log.WriteWarn("AccountInfo", $"GetNextAppIdToIdle returned {response.StatusCode}"); if (response.StatusCode == HttpStatusCode.Unauthorized) { await WebAuth.AuthenticateUser(); } return; } var data = await response.Content.ReadAsStringAsync(); newAppsToIdle = JsonConvert.DeserializeObject <List <uint> >(data); } Log.WriteInfo("AccountInfo", $"{newAppsToIdle.Count} apps to idle: {string.Join(", ", newAppsToIdle)}"); if (!AppsToIdle.SequenceEqual(newAppsToIdle)) { AppsToIdle = newAppsToIdle; Sync(); } }
private async Task RequestBetas() { var removed = false; foreach (var appId in BetasToRequest) { Log.WriteDebug(nameof(FreeLicense), $"Requesting beta {appId}"); try { var response = await WebAuth.PerformRequest( HttpMethod.Post, new Uri($"https://store.steampowered.com/ajaxrequestplaytestaccess/{appId}"), new List <KeyValuePair <string, string> > { new KeyValuePair <string, string>("sessionid", nameof(SteamDatabaseBackend)) } ); var data = await response.Content.ReadAsStringAsync(); BetasToRequest.Remove(appId); removed = true; Log.WriteDebug(nameof(FreeLicense), $"Beta {appId}: {data}"); } catch (Exception e) { Log.WriteWarn(nameof(FreeLicense), $"Failed to request beta {appId}: {e.Message}"); } } if (removed) { await SaveBetas(); } }
private static void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback) { JobManager.TryRemoveJob(callback.JobID); var packageIDs = callback.GrantedPackages; var appIDs = callback.GrantedApps; Log.WriteDebug("FreeLicense", "Received free license: {0} ({1} apps: {2}, {3} packages: {4})", callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs)); if (appIDs.Count > 0) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty <uint>())); } if (packageIDs.Count > 0) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty <uint>(), packageIDs)); // We don't want to block our main thread with web requests TaskManager.Run(() => { string data = null; try { var response = WebAuth.PerformRequest("GET", "https://store.steampowered.com/account/licenses/"); using (var responseStream = response.GetResponseStream()) { using (var reader = new StreamReader(responseStream)) { data = reader.ReadToEnd(); } } } catch (WebException e) { Log.WriteError("FreeLicense", "Failed to fetch account details page: {0}", e.Message); } using (var db = Database.GetConnection()) { foreach (var package in packageIDs) { var packageData = db.Query <Package>("SELECT `SubID`, `Name`, `LastKnownName` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = package }).FirstOrDefault(); if (!string.IsNullOrEmpty(data)) { // Tell me all about using regex var match = Regex.Match(data, string.Format("RemoveFreeLicense\\( ?{0}, ?'(.+)' ?\\)", package)); if (match.Success) { var grantedName = Encoding.UTF8.GetString(Convert.FromBase64String(match.Groups[1].Value)); // Update last known name if we can if (packageData.SubID > 0 && (string.IsNullOrEmpty(packageData.LastKnownName) || packageData.LastKnownName.StartsWith("Steam Sub ", StringComparison.Ordinal))) { db.Execute("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID = package, Name = grantedName }); db.Execute(SubProcessor.GetHistoryQuery(), new PICSHistory { ID = package, Key = SteamDB.DATABASE_NAME_TYPE, OldValue = "free on demand; account page", NewValue = grantedName, Action = "created_info" } ); // Add a app comment on each app in this package var comment = string.Format("This app is in a free on demand package called <b>{0}</b>", SecurityElement.Escape(grantedName)); var apps = db.Query <PackageApp>("SELECT `AppID` FROM `SubsApps` WHERE `SubID` = @SubID", new { SubID = package }).ToList(); var types = db.Query <App>("SELECT `AppID` FROM `Apps` WHERE `AppType` > 0 AND `AppID` IN @Ids", new { Ids = apps.Select(x => x.AppID) }).ToDictionary(x => x.AppID, x => true); var key = db.ExecuteScalar <uint>("SELECT `ID` FROM `KeyNames` WHERE `Name` = 'website_comment'"); foreach (var app in apps) { if (types.ContainsKey(app.AppID)) { continue; } db.Execute("INSERT INTO `AppsInfo` VALUES (@AppID, @Key, @Value) ON DUPLICATE KEY UPDATE `Key` = `Key`", new { app.AppID, Key = key, value = comment }); } } packageData.LastKnownName = grantedName; } } IRC.Instance.SendMain("New free license granted: {0}{1}{2} -{3} {4}", Colors.BLUE, Steam.FormatPackageName(package, packageData), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(package) ); } } }); } }
private async Task RefreshPackageNames() { if (CurrentlyUpdatingNames) { return; } string data; try { CurrentlyUpdatingNames = true; var response = await WebAuth.PerformRequest(HttpMethod.Get, "https://store.steampowered.com/account/licenses/"); data = await response.Content.ReadAsStringAsync(); } catch (Exception e) { Log.WriteError(nameof(FreeLicense), $"Failed to fetch account details page: {e.Message}"); return; } finally { CurrentlyUpdatingNames = false; } var matches = PackageRegex.Matches(data); var names = new Dictionary <uint, string>(); foreach (Match match in matches) { var subID = uint.Parse(match.Groups["subid"].Value); var name = Encoding.UTF8.GetString(Convert.FromBase64String(match.Groups["name"].Value)); names[subID] = name; } if (names.Count == 0) { Log.WriteError(nameof(FreeLicense), "Failed to find any package names on licenses page"); return; } // Skip packages that have a store name to avoid messing up history await using var db = await Database.GetConnectionAsync(); var packageData = await db.QueryAsync <Package>("SELECT `SubID`, `LastKnownName` FROM `Subs` WHERE `SubID` IN @Ids AND `StoreName` = ''", new { Ids = names.Keys }); foreach (var package in packageData) { var newName = names[package.SubID]; if (package.LastKnownName != newName) { Log.WriteInfo(nameof(FreeLicense), $"Changed package name for {package.SubID} from \"{package.LastKnownName}\" to \"{newName}\""); await db.ExecuteAsync("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { package.SubID, Name = newName }); await db.ExecuteAsync( SubProcessor.HistoryQuery, new PICSHistory { ID = package.SubID, Key = SteamDB.DatabaseNameType, OldValue = "free on demand; account page", NewValue = newName, Action = "created_info" } ); } } }
private void RefreshPackageNames() { if (CurrentlyUpdatingNames) { return; } string data; try { CurrentlyUpdatingNames = true; var response = WebAuth.PerformRequest("GET", "https://store.steampowered.com/account/licenses/"); using (var responseStream = response.GetResponseStream()) { using (var reader = new StreamReader(responseStream)) { data = reader.ReadToEnd(); } } } catch (WebException e) { Log.WriteError("FreeLicense", "Failed to fetch account details page: {0}", e.Message); return; } finally { CurrentlyUpdatingNames = false; } var matches = PackageRegex.Matches(data); var names = new Dictionary <uint, string>(); foreach (Match match in matches) { var subID = uint.Parse(match.Groups["subid"].Value); var name = Encoding.UTF8.GetString(Convert.FromBase64String(match.Groups["name"].Value)); names[subID] = name; } using (var db = Database.Get()) { // Skip packages that have a store name to avoid messing up history var packageData = db.Query <Package>("SELECT `SubID`, `LastKnownName` FROM `Subs` WHERE `SubID` IN @Ids AND `StoreName` = ''", new { Ids = names.Keys }); foreach (var package in packageData) { var newName = names[package.SubID]; if (package.LastKnownName != newName) { Log.WriteInfo("FreeLicense", "Changed package name for {0} from \"{1}\" to \"{2}\"", package.SubID, package.LastKnownName, newName); db.Execute("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { package.SubID, Name = newName }); db.Execute( SubProcessor.HistoryQuery, new PICSHistory { ID = package.SubID, Key = SteamDB.DATABASE_NAME_TYPE, OldValue = "free on demand; account page", NewValue = newName, Action = "created_info" } ); } } } }
protected override async Task ProcessData() { ChangeNumber = ProductInfo.ChangeNumber; if (Settings.IsFullRun) { await DbConnection.ExecuteAsync("INSERT INTO `Changelists` (`ChangeID`) VALUES (@ChangeNumber) ON DUPLICATE KEY UPDATE `Date` = `Date`", new { ProductInfo.ChangeNumber }); await DbConnection.ExecuteAsync("INSERT INTO `ChangelistsApps` (`ChangeID`, `AppID`) VALUES (@ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = `AppID`", new { AppID, ProductInfo.ChangeNumber }); } await ProcessKey("root_changenumber", "changenumber", ChangeNumber.ToString()); var app = (await DbConnection.QueryAsync <App>("SELECT `Name`, `AppType` FROM `Apps` WHERE `AppID` = @AppID LIMIT 1", new { AppID })).SingleOrDefault(); var newAppName = ProductInfo.KeyValues["common"]["name"].AsString(); var newAppType = -1; if (newAppName != null) { var currentType = ProductInfo.KeyValues["common"]["type"].AsString().ToLowerInvariant(); newAppType = await DbConnection.ExecuteScalarAsync <int?>("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType }) ?? -1; var modifiedNameOrType = false; if (newAppType == -1) { await DbConnection.ExecuteAsync("INSERT INTO `AppsTypes` (`Name`, `DisplayName`) VALUES(@Name, @DisplayName)", new { Name = currentType, DisplayName = ProductInfo.KeyValues["common"]["type"].AsString() }); // We don't need to lower display name Log.WriteInfo(nameof(AppProcessor), $"Creating new apptype \"{currentType}\" (AppID {AppID})"); IRC.Instance.SendOps($"New app type: {Colors.BLUE}{currentType}{Colors.NORMAL} - {SteamDB.GetAppUrl(AppID, "history")}"); newAppType = await DbConnection.ExecuteScalarAsync <int>("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType }); } if (string.IsNullOrEmpty(app.Name) || app.Name.StartsWith(SteamDB.UnknownAppName, StringComparison.Ordinal)) { await DbConnection.ExecuteAsync("INSERT INTO `Apps` (`AppID`, `AppType`, `Name`, `LastKnownName`) VALUES (@AppID, @Type, @AppName, @AppName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`), `LastKnownName` = VALUES(`LastKnownName`), `AppType` = VALUES(`AppType`)", new { AppID, Type = newAppType, AppName = newAppName } ); await MakeHistory("created_app"); await MakeHistory("created_info", SteamDB.DatabaseNameType, string.Empty, newAppName); modifiedNameOrType = true; } else if (app.Name != newAppName) { await DbConnection.ExecuteAsync("UPDATE `Apps` SET `Name` = @AppName, `LastKnownName` = @AppName WHERE `AppID` = @AppID", new { AppID, AppName = newAppName }); await MakeHistory("modified_info", SteamDB.DatabaseNameType, app.Name, newAppName); modifiedNameOrType = true; } if (app.AppType == 0 || app.AppType != newAppType) { await DbConnection.ExecuteAsync("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID", new { AppID, Type = newAppType }); if (app.AppType == 0) { await MakeHistory("created_info", SteamDB.DatabaseAppType, string.Empty, newAppType.ToString()); } else { await MakeHistory("modified_info", SteamDB.DatabaseAppType, app.AppType.ToString(), newAppType.ToString()); } modifiedNameOrType = true; } if (modifiedNameOrType) { if ((newAppType > 9 && newAppType != 13 && newAppType != 15 && newAppType != 17 && newAppType != 18) || Triggers.Any(newAppName.Contains)) { IRC.Instance.SendOps($"New {currentType}: {Colors.BLUE}{Utils.LimitStringLength(newAppName)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(AppID, "history")}"); } } } foreach (var section in ProductInfo.KeyValues.Children) { var sectionName = section.Name.ToLowerInvariant(); if (sectionName == "appid" || sectionName == "change_number") { continue; } if (sectionName == "common" || sectionName == "extended") { foreach (var keyvalue in section.Children) { var keyName = $"{sectionName}_{keyvalue.Name}"; if (keyName == "common_type" || keyName == "common_gameid" || keyName == "common_name" || keyName == "extended_order") { // Ignore common keys that are either duplicated or serve no real purpose continue; } if (keyvalue.Children.Count > 0) { await ProcessKey(keyName, keyvalue.Name, Utils.JsonifyKeyValue(keyvalue), keyvalue); } else if (!string.IsNullOrEmpty(keyvalue.Value)) { await ProcessKey(keyName, keyvalue.Name, keyvalue.Value); } } } else if (sectionName == "public_only") { await ProcessKey($"root_{sectionName}", section.Name, section.Value); } else { sectionName = $"root_{sectionName}"; if (await ProcessKey(sectionName, sectionName, Utils.JsonifyKeyValue(section), section) && sectionName == "root_depots") { await DbConnection.ExecuteAsync("UPDATE `Apps` SET `LastDepotUpdate` = CURRENT_TIMESTAMP() WHERE `AppID` = @AppID", new { AppID }); } } } // If app gets hidden but we already have data, do not delete the already existing app info if (newAppName != null) { foreach (var data in CurrentData.Values.Where(data => !data.Processed && !data.KeyName.StartsWith("website", StringComparison.Ordinal))) { await DbConnection.ExecuteAsync("DELETE FROM `AppsInfo` WHERE `AppID` = @AppID AND `Key` = @Key", new { AppID, data.Key }); await MakeHistory("removed_key", data.Key, data.Value); } } else { if (string.IsNullOrEmpty(app.Name)) // We don't have the app in our database yet { await DbConnection.ExecuteAsync("INSERT INTO `Apps` (`AppID`, `Name`) VALUES (@AppID, @AppName) ON DUPLICATE KEY UPDATE `AppType` = `AppType`", new { AppID, AppName = $"{SteamDB.UnknownAppName} {AppID}" }); } else if (!app.Name.StartsWith(SteamDB.UnknownAppName, StringComparison.Ordinal)) // We do have the app, replace it with default name { await DbConnection.ExecuteAsync("UPDATE `Apps` SET `Name` = @AppName, `AppType` = 0 WHERE `AppID` = @AppID", new { AppID, AppName = $"{SteamDB.UnknownAppName} {AppID}" }); await MakeHistory("deleted_app", 0, app.Name); } } // Close the connection as it's no longer needed going into depot processor DbConnection.Close(); if (ProductInfo.KeyValues["depots"].Children.Any()) { await Steam.Instance.DepotProcessor.Process(AppID, ChangeNumber, ProductInfo.KeyValues["depots"]); } if (ProductInfo.MissingToken && PICSTokens.HasAppToken(AppID)) { Log.WriteError(nameof(PICSTokens), $"Overridden token for appid {AppID} is invalid?"); IRC.Instance.SendOps($"[Tokens] Looks like the overridden token for appid {AppID} ({newAppName}) is invalid"); } if (Settings.IsMillhaven && app.AppType == 0 && newAppType == 18) { var betaAppId = ProductInfo.KeyValues["extended"]["betaforappid"].AsUnsignedInteger(); if (betaAppId == 0) { betaAppId = ProductInfo.KeyValues["common"]["parent"].AsUnsignedInteger(); } Log.WriteDebug(nameof(AppProcessor), $"Requesting beta access for {AppID} ({betaAppId})"); var response = await WebAuth.PerformRequest( HttpMethod.Post, new Uri($"https://store.steampowered.com/ajaxrequestplaytestaccess/{betaAppId}"), new List <KeyValuePair <string, string> > { new KeyValuePair <string, string>("sessionid", nameof(SteamDatabaseBackend)) } ); var data = await response.Content.ReadAsStringAsync(); Log.WriteDebug(nameof(AppProcessor), $"Beta {AppID}: {data}"); } }