private static void PrintImportants(SteamApps.PICSChangesCallback callback)
        {
            // Apps
            var important = callback.AppChanges.Keys.Intersect(Application.ImportantApps.Keys);

            foreach (var app in important)
            {
                var appName = Steam.GetAppName(app, out var appType);

                IRC.Instance.AnnounceImportantAppUpdate(app, $"{appType} update: {Colors.BLUE}{appName}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(app, "history")}");

                if (Settings.Current.CanQueryStore)
                {
                    Steam.Instance.UnifiedMessages.SendMessage("ChatRoom.SendChatMessage#1", new CChatRoom_SendChatMessage_Request
                    {
                        chat_group_id = 1147,
                        chat_id       = 10208600,
                        message       = $"{appType} update: {appName}\n<{SteamDB.GetAppUrl(app, "history")}?changeid={callback.CurrentChangeNumber}>"
                    });
                }
            }

            // Packages
            important = callback.PackageChanges.Keys.Intersect(Application.ImportantSubs.Keys);

            foreach (var package in important)
            {
                IRC.Instance.SendMain($"Package update: {Colors.BLUE}{Steam.GetPackageName(package)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPackageUrl(package, "history")}");
            }
        }
Beispiel #2
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply($"Usage:{Colors.OLIVE} app <appid or partial game name>");

                return;
            }

            if (!uint.TryParse(command.Message, out var appID))
            {
                appID = await TrySearchAppId(command);

                if (appID == 0)
                {
                    return;
                }
            }

            var info = await GetAppData(appID);

            if (info == null)
            {
                command.Reply($"Unknown AppID: {Colors.BLUE}{appID}{(LicenseList.OwnedApps.ContainsKey(appID) ? SteamDB.StringCheckmark : string.Empty)}");

                return;
            }

            string name;

            if (info.KeyValues["common"]["name"].Value != null)
            {
                name = Utils.LimitStringLength(Utils.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString()));
            }
            else
            {
                name = Steam.GetAppName(info.ID);
            }

            var filename = $"{Utils.ByteArrayToString(info.SHAHash)}.vdf";

            info.KeyValues.SaveToFile(Path.Combine(Application.Path, "app", filename), false);

            command.Reply($"{Colors.BLUE}{name}{Colors.NORMAL} -{Colors.DARKBLUE} <{SteamDB.GetAppUrl(info.ID)}>{Colors.NORMAL} - Dump:{Colors.DARKBLUE} <{SteamDB.GetRawAppUrl(filename)}>{Colors.NORMAL}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty)}");

            if (command.Recipient == Settings.Current.IRC.Channel.Ops && !LicenseList.OwnedApps.ContainsKey(info.ID))
            {
                JobManager.AddJob(() => Steam.Instance.Apps.RequestFreeLicense(info.ID));
            }
        }
Beispiel #3
0
        private static void PrintImportants(SteamApps.PICSChangesCallback callback)
        {
            // Apps
            var important = callback.AppChanges.Keys.Intersect(Application.ImportantApps);

            foreach (var app in important)
            {
                var changeNumber = callback.AppChanges[app].ChangeNumber;

                TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type         = "ImportantAppUpdate",
                    AppID        = app,
                    ChangeNumber = changeNumber,
                    Url          = $"{SteamDB.GetAppUrl(app, "history")}?changeid={changeNumber}",
                }));

                var appName = Steam.GetAppName(app, out var appType);
                IRC.Instance.SendAnnounce($"{appType} update: {Colors.BLUE}{appName}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(app, "history")}");
            }

            // Packages
            important = callback.PackageChanges.Keys.Intersect(Application.ImportantSubs);

            foreach (var package in important)
            {
                var changeNumber = callback.PackageChanges[package].ChangeNumber;

                TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type         = "ImportantSubUpdate",
                    SubID        = package,
                    ChangeNumber = changeNumber,
                    Url          = $"{SteamDB.GetPackageUrl(package, "history")}?changeid={changeNumber}",
                }));

                var subName = Steam.GetPackageName(package);
                IRC.Instance.SendAnnounce($"Package update: {Colors.BLUE}{subName}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPackageUrl(package, "history")}");
            }
        }
Beispiel #4
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply($"Usage:{Colors.OLIVE} app <appid or partial game name>");

                return;
            }

            string name;

            if (!uint.TryParse(command.Message, out var appID))
            {
                appID = await TrySearchAppId(command);

                if (appID == 0)
                {
                    return;
                }
            }

            var tokenTask = Steam.Instance.Apps.PICSGetAccessTokens(appID, null);

            tokenTask.Timeout = TimeSpan.FromSeconds(10);
            var tokenCallback = await tokenTask;

            SteamApps.PICSRequest request;

            if (tokenCallback.AppTokens.ContainsKey(appID))
            {
                request = PICSTokens.NewAppRequest(appID, tokenCallback.AppTokens[appID]);
            }
            else
            {
                request = PICSTokens.NewAppRequest(appID);
            }

            var infoTask = Steam.Instance.Apps.PICSGetProductInfo(new List <SteamApps.PICSRequest> {
                request
            }, Enumerable.Empty <SteamApps.PICSRequest>());

            infoTask.Timeout = TimeSpan.FromSeconds(10);
            var job      = await infoTask;
            var callback = job.Results?.FirstOrDefault(x => x.Apps.ContainsKey(appID));

            if (callback == null)
            {
                command.Reply($"Unknown AppID: {Colors.BLUE}{appID}{(LicenseList.OwnedApps.ContainsKey(appID) ? SteamDB.StringCheckmark : string.Empty)}");

                return;
            }

            var info = callback.Apps[appID];

            if (info.KeyValues["common"]["name"].Value != null)
            {
                name = Utils.LimitStringLength(Utils.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString()));
            }
            else
            {
                name = Steam.GetAppName(info.ID);
            }

            info.KeyValues.SaveToFile(Path.Combine(Application.Path, "app", $"{info.ID}.vdf"), false);

            command.Reply($"{Colors.BLUE}{name}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(info.ID)}{Colors.NORMAL} - Dump:{Colors.DARKBLUE} {SteamDB.GetRawAppUrl(info.ID)}{Colors.NORMAL}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty)}");

            if (command.IsUserAdmin && !LicenseList.OwnedApps.ContainsKey(info.ID))
            {
                JobManager.AddJob(() => Steam.Instance.Apps.RequestFreeLicense(info.ID));
            }
        }
Beispiel #5
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply($"Usage:{Colors.OLIVE} players <appid or partial game name>");

                return;
            }

            if (!uint.TryParse(command.Message, out var appID))
            {
                appID = await AppCommand.TrySearchAppId(command);

                if (appID == 0)
                {
                    return;
                }
            }

            var task = Steam.Instance.UserStats.GetNumberOfCurrentPlayers(appID);

            task.Timeout = TimeSpan.FromSeconds(10);
            var callback = await task;

            if (appID == 0)
            {
                appID = 753;
            }

            var name = Steam.GetAppName(appID, out var appType);

            if (callback.Result != EResult.OK)
            {
                command.Reply($"Unable to request player count for {Colors.BLUE}{name}{Colors.NORMAL}: {Colors.RED}{callback.Result}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(appID, "graphs")}");

                return;
            }

            var  numPlayers = callback.NumPlayers;
            uint dailyPlayers;

            await using (var db = await Database.GetConnectionAsync())
            {
                dailyPlayers = await db.ExecuteScalarAsync <uint>("SELECT `MaxDailyPlayers` FROM `OnlineStats` WHERE `AppID` = @appID", new { appID });

                if (appID == 753 && numPlayers == 0)
                {
                    numPlayers = await db.ExecuteScalarAsync <uint>("SELECT `CurrentPlayers` FROM `OnlineStats` WHERE `AppID` = @appID", new { appID });
                }
            }

            if (dailyPlayers < numPlayers)
            {
                dailyPlayers = numPlayers;
            }

            var type = "playing";

            switch (appType)
            {
            case EAppType.Tool:
            case EAppType.Config:
            case EAppType.Application:
                type = "using";
                break;

            case EAppType.Media:
            case EAppType.Series:
            case EAppType.Video:
                type = "watching";
                break;

            case EAppType.Demo:
                type = "demoing";
                break;

            case EAppType.Guide:
            case EAppType.Comic:
                type = "reading";
                break;

            case EAppType.Hardware:
                type = "bricking";
                break;
            }

            command.Reply(
                $"{Colors.OLIVE}{numPlayers:N0}{Colors.NORMAL} {type} {Colors.BLUE}{name}{Colors.NORMAL} - 24h:{Colors.GREEN} {dailyPlayers:N0}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(appID, "graphs")}"
                );
        }
        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}");
            }
        }
        private void PrintLinux()
        {
            var name = Steam.GetAppName(AppID, out var appType);

            if (appType != "Game" && appType != "Application")
            {
                return;
            }

            if (!Settings.IsMillhaven)
            {
                return;
            }

            IRC.Instance.SendLinuxAnnouncement($"\U0001F427 {name} now lists Linux - {SteamDB.GetAppUrl(AppID, "history")}");
        }
        private async Task <bool> ProcessKey(string keyName, string displayName, string value, KeyValue newKv = null)
        {
            if (keyName.Length > 90)
            {
                Log.WriteError(nameof(AppProcessor), $"Key {keyName} for AppID {AppID} is too long, not inserting info.");

                return(false);
            }

            // All keys in PICS are supposed to be lower case
            keyName = keyName.ToLowerInvariant().Trim();

            if (!CurrentData.ContainsKey(keyName))
            {
                CurrentData[keyName] = new PICSInfo
                {
                    Processed = true,
                };

                var key = KeyNameCache.GetAppKeyID(keyName);

                if (key == 0)
                {
                    var type = newKv != null ? 86 : 0; // 86 is a hardcoded const for the website

                    key = await KeyNameCache.CreateAppKey(keyName, displayName, type);

                    if (key == 0)
                    {
                        // We can't insert anything because key wasn't created
                        Log.WriteError(nameof(AppProcessor), $"Failed to create key {keyName} for AppID {AppID}, not inserting info.");

                        return(false);
                    }

                    IRC.Instance.SendOps($"New app keyname: {Colors.BLUE}{keyName} {Colors.LIGHTGRAY}(ID: {key}) ({displayName}) - {SteamDB.GetAppUrl(AppID, "history")}");
                }

                await DbConnection.ExecuteAsync("INSERT INTO `AppsInfo` (`AppID`, `Key`, `Value`) VALUES (@AppID, @Key, @Value)", new { AppID, Key = key, Value = value });
                await MakeHistory("created_key", key, string.Empty, value);

                if ((keyName == "extended_developer" || keyName == "extended_publisher") && value == "Valve")
                {
                    IRC.Instance.SendOps($"New {displayName}=Valve app: {Colors.BLUE}{Steam.GetAppName(AppID)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(AppID, "history")}");
                }

                if (keyName == "common_oslist" && value.Contains("linux"))
                {
                    PrintLinux();
                }

                return(true);
            }

            var data = CurrentData[keyName];

            if (data.Processed)
            {
                Log.WriteWarn(nameof(AppProcessor), $"Duplicate key {keyName} in AppID {AppID}");

                return(false);
            }

            data.Processed = true;

            CurrentData[keyName] = data;

            if (data.Value == value)
            {
                return(false);
            }

            await DbConnection.ExecuteAsync("UPDATE `AppsInfo` SET `Value` = @Value WHERE `AppID` = @AppID AND `Key` = @Key", new { AppID, data.Key, Value = value });

            if (newKv != null)
            {
                await MakeHistoryForJson(data.Key, data.Value, newKv);
            }
            else
            {
                await MakeHistory("modified_key", data.Key, data.Value, value);
            }

            if (keyName == "common_oslist" && value.Contains("linux") && !data.Value.Contains("linux"))
            {
                PrintLinux();
            }

            return(true);
        }
Beispiel #9
0
        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 isPublicOnly = false;
            var newAppName   = ProductInfo.KeyValues["common"]["name"].AsString();
            var newAppType   = EAppType.Invalid;

            if (newAppName != null)
            {
                var currentType = ProductInfo.KeyValues["common"]["type"].AsString().ToLowerInvariant();

                newAppType = Utils.GetAppType(currentType);
                var modifiedNameOrType = false;

                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    = (int)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 != newAppType)
                {
                    await DbConnection.ExecuteAsync("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID", new { AppID, Type = (int)newAppType });

                    if (app.AppType == EAppType.Invalid)
                    {
                        await MakeHistory("created_info", SteamDB.DatabaseAppType, string.Empty, newAppType.ToString("d"));
                    }
                    else
                    {
                        await MakeHistory("modified_info", SteamDB.DatabaseAppType, app.AppType.ToString(), newAppType.ToString("d"));
                    }

                    modifiedNameOrType = true;
                }

                if (modifiedNameOrType && Triggers.Any(newAppName.Contains))
                {
                    IRC.Instance.SendOps($"New {newAppType}: {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.Value != null)
                        {
                            await ProcessKey(keyName, keyvalue.Name, keyvalue.Value);
                        }
                        else
                        {
                            await ProcessKey(keyName, keyvalue.Name, Utils.JsonifyKeyValue(keyvalue), keyvalue);
                        }
                    }
                }
                else if (sectionName == "public_only")
                {
                    isPublicOnly = section.Value == "1";

                    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)
                {
                    // This key still exists in appinfo and was correctly processed above
                    if (data.Processed)
                    {
                        continue;
                    }

                    // This is a key that is created and handled by steamdb.info; not in appinfo
                    if (data.KeyName.StartsWith("website", StringComparison.Ordinal))
                    {
                        continue;
                    }

                    // If this app requires a token, but previously was public and we had stored data, keep it around
                    if (isPublicOnly && !data.KeyName.StartsWith("common", StringComparison.Ordinal))
                    {
                        continue;
                    }

                    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` = @AppType WHERE `AppID` = @AppID", new {
                        AppID,
                        AppType = (int)EAppType.Invalid,
                        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 && newAppType == EAppType.Beta && !LicenseList.OwnedApps.ContainsKey(AppID))
            {
                var betaAppId = ProductInfo.KeyValues["extended"]["betaforappid"].AsUnsignedInteger();

                if (betaAppId == 0)
                {
                    betaAppId = ProductInfo.KeyValues["common"]["parent"].AsUnsignedInteger();
                }

                Log.WriteDebug(nameof(AppProcessor), $"Adding beta access request for app {AppID} ({betaAppId})");

                Steam.Instance.FreeLicense.AddBeta(betaAppId);
            }
        }