private void OnConnectionStatus(uint appID, IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgConnectionStatus>(packetMsg).Body;

            GetSessionInfo(appID).Status = msg.status;

            string extraInfo = string.Empty;

            if (msg.status == GCConnectionStatus.GCConnectionStatus_NO_SESSION_IN_LOGON_QUEUE)
            {
                extraInfo = string.Format(" {0}(queue: {1}/{2}, waited {3} of an estimated {4} seconds)",
                                          Colors.DARKGRAY, msg.queue_position, msg.queue_size, msg.wait_seconds, msg.estimated_wait_seconds_remaining
                                          );
            }

            IRC.Instance.SendAnnounce("{0}{1}{2} status:{3} {4}{5}", Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL, Colors.OLIVE, msg.status, extraInfo);
        }
Esempio n. 2
0
        private void OnItemBroadcast(uint appID, IPacketGCMsg packetMsg)
        {
            if (appID != 440)
            {
                // This message should be TF2 specific, but just in case
                return;
            }

            var msg = new ClientGCMsgProtobuf <CMsgGCTFSpecificItemBroadcast>(packetMsg).Body;

            var itemName = GetItemName(441, msg.item_def_index);

            IRC.Instance.SendMain("{0}{1}{2} item notification: {3}{4}{5} {6} {7}{8}{9}!",
                                  Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL,
                                  Colors.BLUE, msg.user_name, Colors.NORMAL,
                                  msg.was_destruction ? "has destroyed their" : "just received a",
                                  Colors.OLIVE, itemName, Colors.NORMAL
                                  );
        }
        private void OnItemSchemaUpdate(uint appID, IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgUpdateItemSchema>(packetMsg).Body;

            var info = GetSessionInfo(appID);

            if (info.SchemaVersion != 0 && info.SchemaVersion != msg.item_schema_version)
            {
                var message = string.Format(
                    "{0}{1}{2} item schema updated: {3}{4}{5} -{6} {7}",
                    Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL, Colors.DARKGRAY, msg.item_schema_version.ToString("X4"), Colors.NORMAL, Colors.DARKBLUE, msg.items_game_url
                    );

                IRC.Instance.SendMain(message);
                IRC.Instance.SendAnnounce(message);
            }

            info.SchemaVersion = msg.item_schema_version;
        }
        private void OnWrenchBroadcast(uint appID, IPacketGCMsg packetMsg)
        {
            if (appID != 440)
            {
                // This message should be TF2 specific, but just in case
                return;
            }

            var msg = new ClientGCMsgProtobuf <CMsgTFGoldenWrenchBroadcast>(packetMsg).Body;

            var message = string.Format("{0}{1}{2} item notification: {3}{4}{5} has {6} Golden Wrench no. {7}{8}{9}!",
                                        Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL,
                                        Colors.BLUE, msg.user_name, Colors.NORMAL,
                                        msg.deleted ? "destroyed" : "found",
                                        Colors.OLIVE, msg.wrench_number, Colors.NORMAL
                                        );

            IRC.Instance.SendMain(message);
            IRC.Instance.SendAnnounce(message);
        }
Esempio n. 5
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")}");
            }
        }
Esempio n. 6
0
        private void OnWelcome(uint appID, IPacketGCMsg packetMsg)
        {
            uint version = 0;

            switch (appID)
            {
            case 730:
                version = new ClientGCMsgProtobuf <SteamKit2.GC.CSGO.Internal.CMsgClientWelcome>(packetMsg).Body.version;
                break;

            case 440:
                version = new ClientGCMsgProtobuf <SteamKit2.GC.TF2.Internal.CMsgClientWelcome>(packetMsg).Body.version;
                break;

            default:
                version = new ClientGCMsgProtobuf <CMsgClientWelcome>(packetMsg).Body.version;
                break;
            }

            var info = GetSessionInfo(appID);

            string message = string.Format("{0}{1}{2} new GC session", Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL);

            if (info.Version == 0 || info.Version == version)
            {
                message += string.Format(" {0}(version {1})", Colors.DARKGRAY, version);
            }
            else
            {
                message += string.Format(" {0}(version changed from {1} to {2})", Colors.DARKGRAY, info.Version, version);

                IRC.Instance.SendMain(message);
            }

            IRC.Instance.SendAnnounce(message);

            info.Version = version;
            info.Status  = GCConnectionStatus.GCConnectionStatus_HAVE_SESSION;
        }
        private 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, "{0} update: {1}{2}{3} -{4} {5}",
                                                        appType,
                                                        Colors.BLUE, appName, Colors.NORMAL,
                                                        Colors.DARKBLUE, SteamDB.GetAppURL(app, "history"));
            }

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

            foreach (var package in important)
            {
                IRC.Instance.AnnounceImportantPackageUpdate(package, "Package update: {0}{1}{2} -{3} {4}", Colors.BLUE, Steam.GetPackageName(package), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(package, "history"));
            }
        }
        private void OnWelcome(uint appID, IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgClientWelcome>(packetMsg).Body;

            var info = GetSessionInfo(appID);

            string message = string.Format("{0}{1}{2} new GC session", Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL);

            if (info.Version == 0 || info.Version == msg.version)
            {
                message += string.Format(" {0}(version {1})", Colors.DARKGRAY, msg.version);
            }
            else
            {
                message += string.Format(" {0}(version changed from {1} to {2})", Colors.DARKGRAY, info.Version, msg.version);

                IRC.Instance.SendMain(message);
            }

            IRC.Instance.SendAnnounce(message);

            info.Version = msg.version;
            info.Status  = GCConnectionStatus.GCConnectionStatus_HAVE_SESSION;
        }
Esempio n. 9
0
        private bool ProcessKey(string keyName, string displayName, string value, bool isJSON = false)
        {
            if (keyName.Length > 90)
            {
                Log.WriteError("App Processor", "Key {0} for AppID {1} is too long, not inserting info.", keyName, AppID);

                return(false);
            }

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

            if (!CurrentData.ContainsKey(keyName))
            {
                uint key = GetKeyNameID(keyName);

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

                    DbConnection.Execute("INSERT INTO `KeyNames` (`Name`, `Type`, `DisplayName`) VALUES(@Name, @Type, @DisplayName)", new { Name = keyName, DisplayName = displayName, Type = type });

                    key = GetKeyNameID(keyName);

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

                        return(false);
                    }

                    IRC.Instance.SendOps("New app keyname: {0}{1} {2}(ID: {3}) ({4}) - {5}", Colors.BLUE, keyName, Colors.LIGHTGRAY, key, displayName, SteamDB.GetAppURL(AppID, "history"));
                }

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

                if ((keyName == "extended_developer" || keyName == "extended_publisher") && value == "Valve")
                {
                    IRC.Instance.SendOps("New {0}=Valve app: {1}{2}{3} -{4} {5}",
                                         displayName,
                                         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("App Processor", "Duplicate key {0} in AppID {1}", keyName, AppID);

                return(false);
            }

            data.Processed = true;

            CurrentData[keyName] = data;

            if (!data.Value.Equals(value))
            {
                DbConnection.Execute("UPDATE `AppsInfo` SET `Value` = @Value WHERE `AppID` = @AppID AND `Key` = @Key", new { AppID, Key = data.Key, Value = value });
                MakeHistory("modified_key", data.Key, data.Value, value);

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

                return(true);
            }

            return(false);
        }
Esempio n. 10
0
        private static async Task ProcessFeed(Uri feedUrl)
        {
            var feed = await LoadRSS(feedUrl);

            if (feed == null)
            {
                return;
            }

            if (feed.Items.Count == 0)
            {
                Log.WriteError(nameof(RSS), $"Did not find any items in {feedUrl}");
                return;
            }

            await using var db = await Database.GetConnectionAsync();

            var items = (await db.QueryAsync <GenericFeedItem>("SELECT `Link` FROM `RSS` WHERE `Link` IN @Ids", new { Ids = feed.Items.Select(x => x.Link) })).ToDictionary(x => x.Link, _ => (byte)1);

            var newItems = feed.Items.Where(item => !items.ContainsKey(item.Link));

            foreach (var item in newItems)
            {
                Log.WriteInfo(nameof(RSS), $"[{feed.Title}] {item.Title}: {item.Link}");

                IRC.Instance.SendAnnounce($"{Colors.BLUE}{feed.Title}{Colors.NORMAL}: {item.Title} -{Colors.DARKBLUE} {item.Link}");

                await db.ExecuteAsync("INSERT INTO `RSS` (`Link`, `Title`) VALUES(@Link, @Title)", new { item.Link, item.Title });

                _ = TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type = "RSS",
                    item.Title,
                    Url = item.Link,
                }));

                if (!Settings.IsMillhaven)
                {
                    continue;
                }

                uint appID = 0;

                if (feed.Title == "Steam RSS News Feed")
                {
                    if (item.Title.StartsWith("Dota 2 Update", StringComparison.Ordinal))
                    {
                        appID = 570;
                    }
                    else if (item.Title == "Team Fortress 2 Update Released")
                    {
                        appID = 440;

                        // tf2 changelog cleanup
                        item.Content = item.Content.Replace("<br/>", "\n");
                        item.Content = item.Content.Replace("<ul style=\"padding-bottom: 0px; margin-bottom: 0px;\">", "\n");
                        item.Content = item.Content.Replace("<ul style=\"padding-bottom: 0px; margin-bottom: 0px;\" >", "\n");
                        item.Content = item.Content.Replace("</ul>", "\n");
                        item.Content = item.Content.Replace("<li>", "* ");
                    }
                    else if (item.Title == "Left 4 Dead 2 - Update")
                    {
                        appID = 550;
                    }
                    else if (item.Title == "Left 4 Dead - Update")
                    {
                        appID = 500;
                    }
                    else if (item.Title == "Portal 2 - Update")
                    {
                        appID = 620;
                    }
                }
                else if (feed.Title.Contains("Counter-Strike: Global Offensive") && item.Title.StartsWith("Release Notes", StringComparison.Ordinal))
                {
                    appID = 730;

                    // csgo changelog cleanup
                    item.Content = item.Content.Replace("</p>", "\n");
                    item.Content = new Regex("<p>\\[\\s*(.+)\\s*\\]", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "## $1");
                    item.Content = item.Content.Replace("<p>", "");
                }

                if (appID > 0)
                {
                    var build = (await db.QueryAsync <Build>(
                                     "SELECT `Builds`.`BuildID`, `Builds`.`ChangeID`, `Builds`.`AppID`, `Changelists`.`Date`, LENGTH(`Official`) as `Official` FROM `Builds` " +
                                     "LEFT JOIN `Patchnotes` ON `Patchnotes`.`BuildID` = `Builds`.`BuildID` " +
                                     "JOIN `Apps` ON `Apps`.`AppID` = `Builds`.`AppID` " +
                                     "JOIN `Changelists` ON `Builds`.`ChangeID` = `Changelists`.`ChangeID` " +
                                     "WHERE `Builds`.`AppID` = @AppID ORDER BY `Builds`.`BuildID` DESC LIMIT 1",
                                     new { appID }
                                     )).SingleOrDefault();

                    if (build == null)
                    {
                        continue;
                    }

                    if (DateTime.UtcNow > build.Date.AddMinutes(60))
                    {
                        Log.WriteDebug(nameof(RSS), $"Got {appID} update patch notes, but there is no build within last 10 minutes. {item.Link}");
                        IRC.Instance.SendOps($"{Colors.GREEN}[Patch notes]{Colors.NORMAL} Got {appID} update patch notes, but there is no build within last 10 minutes. {item.Link}");
                        continue;
                    }

                    if (build.Official > 0)
                    {
                        Log.WriteDebug(nameof(RSS), $"Got {appID} update patch notes, but official patch notes is already filled. {item.Link}");
                        IRC.Instance.SendOps($"{Colors.GREEN}[Patch notes]{Colors.NORMAL} Got {appID} update patch notes, but official patch notes is already filled. {item.Link}");
                        continue;
                    }

                    // breaks
                    item.Content = new Regex(@"<br( \/)?>\r?\n?", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "\n");

                    // dashes (CS:GO mainly)
                    item.Content = new Regex("^(?:-|&#(?:8208|8209|8210|8211|8212|8213);|–|—) ?", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "* ");

                    item.Content = WebUtility.HtmlDecode(item.Content);

                    Log.WriteDebug(nameof(RSS), $"Inserting {build.AppID} patchnotes for build {build.BuildID}:\n{item.Content}");

                    var accountId = Steam.Instance.Client?.SteamID?.AccountID ?? 0;

                    await db.ExecuteAsync(
                        "INSERT INTO `Patchnotes` (`BuildID`, `AppID`, `ChangeID`, `Date`, `Official`, `OfficialURL`) " +
                        "VALUES (@BuildID, @AppID, @ChangeID, @Date, @Content, @Link) ON DUPLICATE KEY UPDATE `Official` = VALUES(`Official`), `OfficialURL` = VALUES(`OfficialURL`), `LastEditor` = @AccountID",
                        new
                    {
                        build.BuildID,
                        build.AppID,
                        build.ChangeID,
                        Date = build.Date.AddSeconds(1).ToString("yyyy-MM-dd HH:mm:ss"),
                        item.Content,
                        item.Link,
                        accountId
                    }
                        );

                    IRC.Instance.SendAnnounce($"\u2699 Official patch notes:{Colors.BLUE} {Steam.GetAppName(build.AppID)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPatchnotesUrl(build.BuildID)}");
                }
            }
        }
Esempio n. 11
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (command.Message.Length == 0)
            {
                command.Reply("Usage:{0} players <appid or partial game name>", Colors.OLIVE);

                return;
            }

            uint   appID;
            string name;

            if (!uint.TryParse(command.Message, out appID))
            {
                name = command.Message;

                using (var webClient = new WebClient())
                {
                    webClient.QueryString.Add("x-algolia-application-id", "94HE6YATEI");
                    webClient.QueryString.Add("x-algolia-api-key", "2414d3366df67739fe6e73dad3f51a43");
                    webClient.QueryString.Add("hitsPerPage", "1");
                    webClient.QueryString.Add("attributesToHighlight", "null");
                    webClient.QueryString.Add("attributesToSnippet", "null");
                    webClient.QueryString.Add("attributesToRetrieve", "[\"objectID\"]");
                    webClient.QueryString.Add("facetFilters", "[[\"appType:Game\",\"appType:Application\"]]");
                    webClient.QueryString.Add("advancedSyntax", "true");
                    webClient.QueryString.Add("query", name);

                    var data = await webClient.DownloadStringTaskAsync("https://94he6yatei-dsn.algolia.net/1/indexes/steamdb/");

                    dynamic json = JsonConvert.DeserializeObject(data);

                    if (json.hits != null && json.hits.Count > 0)
                    {
                        appID = json.hits[0].objectID;
                    }
                }

                if (appID == 0)
                {
                    if (!Utils.ConvertUserInputToSQLSearch(ref name))
                    {
                        command.Reply("Your request is invalid or too short.");

                        return;
                    }

                    using (var db = Database.GetConnection())
                    {
                        appID = db.ExecuteScalar <uint>("SELECT `AppID` FROM `Apps` LEFT JOIN `AppsTypes` ON `Apps`.`AppType` = `AppsTypes`.`AppType` WHERE (`AppsTypes`.`Name` IN ('game', 'application', 'video', 'hardware') AND (`Apps`.`StoreName` LIKE @Name OR `Apps`.`Name` LIKE @Name)) OR (`AppsTypes`.`Name` = 'unknown' AND `Apps`.`LastKnownName` LIKE @Name) ORDER BY `LastUpdated` DESC LIMIT 1", new { Name = name });
                    }
                }

                if (appID == 0)
                {
                    command.Reply("Nothing was found matching your request.");

                    return;
                }
            }

            var callback = await Steam.Instance.UserStats.GetNumberOfCurrentPlayers(appID);

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

            string appType, type = "playing";

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

            if (callback.Result != EResult.OK)
            {
                command.Reply("Unable to request player count for {0}{1}{2}: {3}{4}", Colors.BLUE, name, Colors.NORMAL, Colors.RED, callback.Result);

                return;
            }

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

            case "Legacy Media":
            case "Video":
                type = "watching";
                break;

            case "Guide":
                type = "reading";
                break;

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

            command.Reply(
                "People {0} {1}{2}{3} right now: {4}{5:N0}{6} -{7} {8}",
                type,
                Colors.BLUE, name, Colors.NORMAL,
                Colors.OLIVE, callback.NumPlayers, Colors.NORMAL,
                Colors.DARKBLUE, SteamDB.GetAppURL(appID, "graphs")
                );
        }
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply("Usage:{0} app <appid or partial game name>", Colors.OLIVE);

                return;
            }

            string name;

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

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

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

            SteamApps.PICSRequest request;

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

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

            var callback = job.Results.FirstOrDefault(x => x.Apps.ContainsKey(appID));

            if (callback == null)
            {
                command.Reply("Unknown AppID: {0}{1}{2}", 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.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString());
            }
            else
            {
                name = Steam.GetAppName(info.ID);
            }

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

            command.Reply("{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                          Colors.BLUE, name, Colors.NORMAL,
                          Colors.DARKBLUE, SteamDB.GetAppURL(info.ID), Colors.NORMAL,
                          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));
            }
        }
Esempio n. 13
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (command.Message.Length == 0)
            {
                command.Reply("Usage:{0} app <appid or partial game name>", Colors.OLIVE);

                return;
            }

            string name;

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

                if (!Utils.ConvertUserInputToSQLSearch(ref name))
                {
                    command.Reply("Your request is invalid or too short.");

                    return;
                }

                using (var db = Database.GetConnection())
                {
                    appID = db.ExecuteScalar <uint>("SELECT `AppID` FROM `Apps` WHERE `Apps`.`StoreName` LIKE @Name OR `Apps`.`Name` LIKE @Name OR `Apps`.`LastKnownName` LIKE @Name ORDER BY `LastUpdated` DESC LIMIT 1", new { Name = name });
                }

                if (appID == 0)
                {
                    command.Reply("Nothing was found matching your request.");

                    return;
                }
            }

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

            SteamApps.PICSRequest request;

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

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

            var callback = job.Results.FirstOrDefault(x => x.Apps.ContainsKey(appID));

            if (callback == null)
            {
                command.Reply("Unknown AppID: {0}{1}{2}", 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.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString());
            }
            else
            {
                name = Steam.GetAppName(info.ID);
            }

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

            command.Reply("{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                          Colors.BLUE, name, Colors.NORMAL,
                          Colors.DARKBLUE, SteamDB.GetAppURL(info.ID), Colors.NORMAL,
                          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));
            }
        }
        private static void HandleAppToken(uint id, ulong accessToken)
        {
            if (!AppTokens.ContainsKey(id))
            {
                AppTokens.Add(id, accessToken);

                IRC.Instance.SendOps($"{Colors.GREEN}[Tokens]{Colors.NORMAL} Added a new app token that the bot got itself:{Colors.BLUE} {id} {Colors.NORMAL}({Steam.GetAppName(id)})");

                Log.WriteInfo(nameof(PICSTokens), "New token for appid {0}", id);

                using var db = Database.Get();
                db.Execute("INSERT INTO `PICSTokens` (`AppID`, `Token`) VALUES(@AppID, @Token)",
                           new PICSToken {
                    AppID = id, Token = accessToken
                }
                           );
            }
            else if (AppTokens[id] != accessToken)
            {
                IRC.Instance.SendOps($"{Colors.GREEN}[Tokens]{Colors.NORMAL} Bot got an app token that mismatches the one in database: {AppTokens[id]} != {accessToken}");
            }
        }
Esempio n. 15
0
        private static async Task ProcessFeed(Uri feed)
        {
            var rssItems = LoadRSS(feed, out var feedTitle);

            if (rssItems == null)
            {
                return;
            }

            if (rssItems.Count == 0)
            {
                Log.WriteError("RSS", "Did not find any items in {0}", feed);
                return;
            }

            using (var db = await Database.GetConnectionAsync())
            {
                var items = (await db.QueryAsync <GenericFeedItem>("SELECT `Link` FROM `RSS` WHERE `Link` IN @Ids", new { Ids = rssItems.Select(x => x.Link) })).ToDictionary(x => x.Link, x => (byte)1);

                var newItems = rssItems.Where(item => !items.ContainsKey(item.Link));

                foreach (var item in newItems)
                {
                    Log.WriteInfo("RSS", "[{0}] {1}: {2}", feedTitle, item.Title, item.Link);

                    IRC.Instance.SendMain("{0}{1}{2}: {3} -{4} {5}", Colors.BLUE, feedTitle, Colors.NORMAL, item.Title, Colors.DARKBLUE, item.Link);

                    await db.ExecuteAsync("INSERT INTO `RSS` (`Link`, `Title`) VALUES(@Link, @Title)", new { item.Link, item.Title });

                    if (!Settings.Current.CanQueryStore)
                    {
                        continue;
                    }

                    uint appID = 0;

                    if (feedTitle == "Steam RSS News Feed")
                    {
                        if (item.Title.StartsWith("Dota 2 Update", StringComparison.Ordinal))
                        {
                            appID = 570;
                        }
                        else if (item.Title == "Team Fortress 2 Update Released")
                        {
                            appID = 440;
                        }
                        else if (item.Title == "Left 4 Dead 2 - Update")
                        {
                            appID = 550;
                        }
                        else if (item.Title == "Left 4 Dead - Update")
                        {
                            appID = 500;
                        }
                        else if (item.Title == "Portal 2 - Update")
                        {
                            appID = 620;
                        }
                    }
                    else if (feedTitle.Contains("Counter-Strike: Global Offensive") && item.Title.StartsWith("Release Notes", StringComparison.Ordinal))
                    {
                        appID = 730;

                        // csgo changelog cleanup
                        item.Content = item.Content.Replace("</p>", "\n");
                        item.Content = new Regex("<p>\\[\\s*(.+)\\s*\\]", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "## $1");
                        item.Content = item.Content.Replace("<p>", "");
                    }

                    if (appID > 0)
                    {
                        var build = (await db.QueryAsync <Build>(
                                         "SELECT `Builds`.`BuildID`, `Builds`.`ChangeID`, `Builds`.`AppID`, `Changelists`.`Date`, LENGTH(`Official`) as `Official` FROM `Builds` " +
                                         "LEFT JOIN `Patchnotes` ON `Patchnotes`.`BuildID` = `Builds`.`BuildID` " +
                                         "JOIN `Apps` ON `Apps`.`AppID` = `Builds`.`AppID` " +
                                         "JOIN `Changelists` ON `Builds`.`ChangeID` = `Changelists`.`ChangeID` " +
                                         "WHERE `Builds`.`AppID` = @AppID ORDER BY `Builds`.`BuildID` DESC LIMIT 1",
                                         new { appID }
                                         )).SingleOrDefault();

                        if (build == null)
                        {
                            continue;
                        }

                        if (DateTime.UtcNow > build.Date.AddMinutes(60))
                        {
                            Log.WriteDebug("RSS", "Got {0} update patch notes, but there is no build within last 10 minutes. {1}", appID, item.Link);
                            IRC.Instance.SendOps($"{Colors.GREEN}[Patch notes]{Colors.NORMAL} Got {appID} update patch notes, but there is no build within last 10 minutes. {item.Link}");
                            continue;
                        }

                        if (build.Official > 0)
                        {
                            Log.WriteDebug("RSS", "Got {0} update patch notes, but official patch notes is already filled. {1}", appID, item.Link);
                            IRC.Instance.SendOps($"{Colors.GREEN}[Patch notes]{Colors.NORMAL} Got {appID} update patch notes, but official patch notes is already filled. {item.Link}");
                            continue;
                        }

                        // breaks
                        item.Content = new Regex(@"<br( \/)?>\r?\n?", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "\n");

                        // dashes (CS:GO mainly)
                        item.Content = new Regex("^&#(8208|8209|8210|8211|8212|8213); ?", RegexOptions.Multiline | RegexOptions.CultureInvariant).Replace(item.Content, "* ");

                        // add source
                        item.Content = string.Format("Via [{0}]({1}):\n\n{2}", appID == 730 ? "CS:GO Blog" : "the Steam Store", item.Link, item.Content);

                        Log.WriteDebug("RSS", "Inserting {0} patchnotes for build {1}:\n{2}", build.AppID, build.BuildID, item.Content);

                        await db.ExecuteAsync(
                            "INSERT INTO `Patchnotes` (`BuildID`, `AppID`, `ChangeID`, `Date`, `Official`) " +
                            "VALUES (@BuildID, @AppID, @ChangeID, @Date, @Content) ON DUPLICATE KEY UPDATE `Official` = VALUES(`Official`), `LastEditor` = @AccountID",
                            new
                        {
                            build.BuildID,
                            build.AppID,
                            build.ChangeID,
                            Date = build.Date.AddSeconds(1).ToString("yyyy-MM-dd HH:mm:ss"),
                            item.Content,
                            Steam.Instance.Client.SteamID.AccountID
                        }
                            );

                        IRC.Instance.SendMain($"\u2699 Official patch notes:{Colors.BLUE} {Steam.GetAppName(build.AppID)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPatchnotesURL(build.BuildID)}");
                    }
                }
            }
        }
        public static void DownloadFilesFromDepot(DepotProcessor.ManifestJob job, DepotManifest depotManifest)
        {
            var files        = depotManifest.Files.Where(x => IsFileNameMatching(job.DepotID, x.FileName)).ToList();
            var filesUpdated = false;

            Log.WriteDebug("FileDownloader", "Will download {0} files from depot {1}", files.Count(), job.DepotID);

            foreach (var file in files)
            {
                string directory    = Path.Combine(Application.Path, FILES_DIRECTORY, job.DepotID.ToString(), Path.GetDirectoryName(file.FileName));
                string finalPath    = Path.Combine(directory, Path.GetFileName(file.FileName));
                string downloadPath = Path.GetTempFileName();

                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                else if (File.Exists(finalPath))
                {
                    using (var fs = File.Open(finalPath, FileMode.Open))
                    {
                        using (var sha = new SHA1Managed())
                        {
                            if (file.FileHash.SequenceEqual(sha.ComputeHash(fs)))
                            {
                                Log.WriteDebug("FileDownloader", "{0} already matches the file we have", file.FileName);

                                continue;
                            }
                        }
                    }
                }

                Log.WriteInfo("FileDownloader", "Downloading {0} ({1} bytes, {2} chunks)", file.FileName, file.TotalSize, file.Chunks.Count);

                uint   count = 0;
                byte[] checksum;
                string lastError = "or checksum failed";

                using (var fs = File.Open(downloadPath, FileMode.OpenOrCreate))
                {
                    fs.SetLength((long)file.TotalSize);

                    var lockObject = new object();

                    // TODO: We *could* verify each chunk and only download needed ones
                    Parallel.ForEach(file.Chunks, (chunk, state) =>
                    {
                        var downloaded = false;

                        for (var i = 0; i <= 5; i++)
                        {
                            try
                            {
                                var chunkData = CDNClient.DownloadDepotChunk(job.DepotID, chunk, job.Server, job.CDNToken, job.DepotKey);

                                lock (lockObject)
                                {
                                    fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
                                    fs.Write(chunkData.Data, 0, chunkData.Data.Length);

                                    Log.WriteDebug("FileDownloader", "Downloaded {0} ({1}/{2})", file.FileName, ++count, file.Chunks.Count);
                                }

                                downloaded = true;

                                break;
                            }
                            catch (Exception e)
                            {
                                lastError = e.Message;
                            }
                        }

                        if (!downloaded)
                        {
                            state.Stop();
                        }
                    });

                    fs.Seek(0, SeekOrigin.Begin);

                    using (var sha = new SHA1Managed())
                    {
                        checksum = sha.ComputeHash(fs);
                    }
                }

                if (file.Chunks.Count == 0 || file.FileHash.SequenceEqual(checksum))
                {
                    Log.WriteInfo("FileDownloader", "Downloaded {0} from {1}", file.FileName, Steam.GetAppName(job.ParentAppID));

                    if (File.Exists(finalPath))
                    {
                        File.Delete(finalPath);
                    }

                    File.Move(downloadPath, finalPath);

                    filesUpdated = true;
                }
                else
                {
                    IRC.Instance.SendOps("{0}[{1}]{2} Failed to download {3}: Only {4} out of {5} chunks downloaded ({6})",
                                         Colors.OLIVE, Steam.GetAppName(job.ParentAppID), Colors.NORMAL, file.FileName, count, file.Chunks.Count, lastError);

                    Log.WriteError("FileDownloader", "Failed to download {0}: Only {1} out of {2} chunks downloaded from {3} ({4})",
                                   file.FileName, count, file.Chunks.Count, job.Server, lastError);

                    File.Delete(downloadPath);
                }
            }

            if (filesUpdated)
            {
                var updateScript = Path.Combine(Application.Path, "files", "update.sh");

                if (File.Exists(updateScript))
                {
                    // YOLO
                    Process.Start(updateScript, job.DepotID.ToString());
                }
            }
        }
Esempio n. 17
0
        private void OnSystemMessage(uint appID, IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgSystemBroadcast>(packetMsg).Body;

            IRC.Instance.SendMain("{0}{1}{2} system message:{3} {4}", Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL, Colors.OLIVE, msg.message);
        }
Esempio n. 18
0
        private void OnVersionUpdate(uint appID, IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgGCClientVersionUpdated>(packetMsg).Body;

            var info = GetSessionInfo(appID);

            IRC.Instance.SendMain("{0}{1}{2} client version changed:{3} {4} {5}(from {6})", Colors.BLUE, Steam.GetAppName(appID), Colors.NORMAL, Colors.BLUE, msg.client_version, Colors.DARKGRAY, info.Version);

            info.Version = msg.client_version;
        }
Esempio n. 19
0
        private async Task DownloadDepots(IEnumerable <ManifestJob> depots)
        {
            Log.WriteDebug("Depot Downloader", "Will process {0} depots ({1} depot locks left)", depots.Count(), DepotLocks.Count);

            var  processTasks       = new List <Task <EResult> >();
            bool anyFilesDownloaded = false;

            foreach (var depot in depots)
            {
                depot.DepotKey = await GetDepotDecryptionKey(depot.DepotID, depot.ParentAppID);

                if (depot.DepotKey == null)
                {
                    RemoveLock(depot.DepotID);

                    continue;
                }

                var cdnToken = await GetCDNAuthToken(depot.DepotID);

                if (cdnToken == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteDebug("Depot Downloader", "Got a depot key for depot {0} but no cdn auth token", depot.DepotID);

                    continue;
                }

                depot.CDNToken = cdnToken.Token;
                depot.Server   = cdnToken.Server;

                DepotManifest depotManifest = null;
                string        lastError     = string.Empty;

                for (var i = 0; i <= 5; i++)
                {
                    try
                    {
                        depotManifest = CDNClient.DownloadManifest(depot.DepotID, depot.ManifestID, depot.Server, depot.CDNToken, depot.DepotKey);

                        break;
                    }
                    catch (Exception e)
                    {
                        Log.WriteWarn("Depot Downloader", "{0} Manifest download failed: {1} - {2}", depot.DepotID, e.GetType(), e.Message);

                        lastError = e.Message;
                    }

                    // TODO: get new auth key if auth fails

                    if (depotManifest == null)
                    {
                        await Task.Delay(Utils.ExponentionalBackoff(i));
                    }
                }

                if (depotManifest == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteError("Depot Processor", "Failed to download depot manifest for depot {0} ({1}: {2})", depot.DepotID, depot.Server, lastError);

                    if (FileDownloader.IsImportantDepot(depot.DepotID))
                    {
                        IRC.Instance.SendOps("{0}[{1}]{2} Failed to download depot {3} manifest ({4}: {5})",
                                             Colors.OLIVE, Steam.GetAppName(depot.ParentAppID), Colors.NORMAL, depot.DepotID, depot.Server, lastError);
                    }

                    continue;
                }

                var task = TaskManager.Run(() =>
                {
                    using (var db = Database.GetConnection())
                    {
                        return(ProcessDepotAfterDownload(db, depot, depotManifest));
                    }
                });

                processTasks.Add(task);

                if (FileDownloader.IsImportantDepot(depot.DepotID))
                {
                    task = TaskManager.Run(() =>
                    {
                        var result = FileDownloader.DownloadFilesFromDepot(depot, depotManifest);

                        if (result == EResult.OK)
                        {
                            anyFilesDownloaded = true;
                        }

                        return(result);
                    }, TaskCreationOptions.LongRunning);

                    TaskManager.RegisterErrorHandler(task);

                    processTasks.Add(task);
                }
            }

            if (SaveLocalConfig)
            {
                SaveLocalConfig = false;

                LocalConfig.Save();
            }

            await Task.WhenAll(processTasks);

            // TODO: use ContinueWith on tasks
            if (!anyFilesDownloaded)
            {
                Log.WriteDebug("Depot Downloader", "Tasks awaited for {0} depot downloads", depots.Count());

                foreach (var depot in depots)
                {
                    RemoveLock(depot.DepotID);
                }

                return;
            }

            var canUpdate = processTasks.All(x => x.Result == EResult.OK || x.Result == EResult.Ignored) && File.Exists(UpdateScript);

#if true
            Log.WriteDebug("Depot Downloader", "Tasks awaited for {0} depot downloads (will run script: {1})", depots.Count(), canUpdate);
#endif
            bool lockTaken = false;

            try
            {
                UpdateScriptLock.Enter(ref lockTaken);

                foreach (var depot in depots)
                {
                    // TODO: this only needs to run if any downloaded files changed
                    if (canUpdate && FileDownloader.IsImportantDepot(depot.DepotID))
                    {
                        RunUpdateScript(string.Format("{0} no-git", depot.DepotID));
                    }

                    RemoveLock(depot.DepotID);
                }

                if (canUpdate)
                {
                    RunUpdateScript("0");
                }
            }
            finally
            {
                if (lockTaken)
                {
                    UpdateScriptLock.Exit();
                }
            }
        }
        private async Task DownloadDepots(uint appID, List <ManifestJob> depots)
        {
            Log.WriteDebug("Depot Downloader", "Will process {0} depots ({1} depot locks left)", depots.Count(), DepotLocks.Count);

            var  processTasks       = new List <Task <EResult> >();
            bool anyFilesDownloaded = false;

            foreach (var depot in depots)
            {
                var instance = depot.Anonymous ? Steam.Anonymous.Apps : Steam.Instance.Apps;

                depot.DepotKey = await GetDepotDecryptionKey(instance, depot.DepotID, appID);

                if (depot.DepotKey == null)
                {
                    RemoveLock(depot.DepotID);

                    continue;
                }

                var cdnToken = await GetCDNAuthToken(instance, appID, depot.DepotID);

                if (cdnToken == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteDebug("Depot Downloader", "Got a depot key for depot {0} but no cdn auth token", depot.DepotID);

                    continue;
                }

                depot.CDNToken = cdnToken.Token;
                depot.Server   = cdnToken.Server;

                DepotManifest depotManifest = null;
                string        lastError     = string.Empty;

                for (var i = 0; i <= 5; i++)
                {
                    try
                    {
                        depotManifest = CDNClient.DownloadManifest(depot.DepotID, depot.ManifestID, depot.Server, depot.CDNToken, depot.DepotKey);

                        break;
                    }
                    catch (Exception e)
                    {
                        Log.WriteWarn("Depot Downloader", "[{0}] Manifest download failed: {1} - {2}", depot.DepotID, e.GetType(), e.Message);

                        lastError = e.Message;
                    }

                    // TODO: get new auth key if auth fails

                    if (depotManifest == null)
                    {
                        await Task.Delay(Utils.ExponentionalBackoff(i));
                    }
                }

                if (depotManifest == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteError("Depot Processor", "Failed to download depot manifest for app {0} depot {1} ({2}: {3})", appID, depot.DepotID, depot.Server, lastError);

                    if (FileDownloader.IsImportantDepot(depot.DepotID))
                    {
                        IRC.Instance.SendOps("{0}[{1}]{2} Failed to download app {3} depot {4} manifest ({5}: {6})",
                                             Colors.OLIVE, Steam.GetAppName(appID), Colors.NORMAL, appID, depot.DepotID, depot.Server, lastError);
                    }

                    continue;
                }

                var task = TaskManager.Run(() =>
                {
                    using (var db = Database.GetConnection())
                    {
                        using (var transaction = db.BeginTransaction())
                        {
                            var result = ProcessDepotAfterDownload(db, depot, depotManifest);
                            transaction.Commit();
                            return(result);
                        }
                    }
                });

                processTasks.Add(task);

                if (FileDownloader.IsImportantDepot(depot.DepotID))
                {
                    task = TaskManager.Run(() =>
                    {
                        var result = FileDownloader.DownloadFilesFromDepot(appID, depot, depotManifest);

                        if (result == EResult.OK)
                        {
                            anyFilesDownloaded = true;
                        }

                        return(result);
                    }, TaskCreationOptions.LongRunning);

                    TaskManager.RegisterErrorHandler(task);

                    processTasks.Add(task);
                }
            }

            if (SaveLocalConfig)
            {
                SaveLocalConfig = false;

                LocalConfig.Save();
            }

            await Task.WhenAll(processTasks);

            Log.WriteDebug("Depot Downloader", "{0} depot downloads finished", depots.Count());

            // TODO: use ContinueWith on tasks
            if (!anyFilesDownloaded)
            {
                foreach (var depot in depots)
                {
                    RemoveLock(depot.DepotID);
                }

                return;
            }

            if (!File.Exists(UpdateScript))
            {
                return;
            }

            bool lockTaken = false;

            try
            {
                UpdateScriptLock.Enter(ref lockTaken);

                foreach (var depot in depots)
                {
                    if (depot.Result == EResult.OK)
                    {
                        RunUpdateScript(string.Format("{0} no-git", depot.DepotID));
                    }

                    RemoveLock(depot.DepotID);
                }

                // Only commit changes if all depots downloaded
                if (processTasks.All(x => x.Result == EResult.OK || x.Result == EResult.Ignored))
                {
                    if (!RunUpdateScript(appID))
                    {
                        RunUpdateScript("0");
                    }
                }
                else
                {
                    Log.WriteDebug("Depot Processor", "Reprocessing the app {0} because some files failed to download", appID);

                    IRC.Instance.SendOps("{0}[{1}]{2} Reprocessing the app due to download failures",
                                         Colors.OLIVE, Steam.GetAppName(appID), Colors.NORMAL
                                         );

                    JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appID, null));
                }
            }
            finally
            {
                if (lockTaken)
                {
                    UpdateScriptLock.Exit();
                }
            }
        }
Esempio n. 21
0
        public override async Task OnCommand(CommandArguments command)
        {
            await Task.Yield();

            if (command.CommandType != ECommandType.IRC || !IRC.IsRecipientChannel(command.Recipient))
            {
                command.Reply("This command is only available in channels.");

                return;
            }

            var channel = command.Recipient;

            var  s     = command.Message.Split(' ');
            var  count = s.Length;
            uint id;

            if (count > 0)
            {
                switch (s[0])
                {
                case "reload":
                    Application.ReloadImportant(command);
                    PICSTokens.Reload(command);
                    FileDownloader.ReloadFileList();

                    return;

                case "add":
                    if (count < 3)
                    {
                        break;
                    }

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                        List <string> channels;
                        var           exists = Application.ImportantApps.TryGetValue(id, out channels);

                        if (exists && channels.Contains(channel))
                        {
                            command.Reply("App {0}{1}{2} ({3}) is already important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }
                        else
                        {
                            if (exists)
                            {
                                Application.ImportantApps[id].Add(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Add(id, new List <string> {
                                    channel
                                });
                            }

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("INSERT INTO `ImportantApps` (`AppID`, `Channel`) VALUES (@AppID, @Channel)", new { AppID = id, Channel = channel });
                            }

                            command.Reply("Marked app {0}{1}{2} ({3}) as important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }

                        return;

                    case "sub":
                        if (Application.ImportantSubs.ContainsKey(id))
                        {
                            command.Reply("Package {0}{1}{2} ({3}) is already important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }
                        else
                        {
                            Application.ImportantSubs.Add(id, 1);

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("INSERT INTO `ImportantSubs` (`SubID`) VALUES (@SubID)", new { SubID = id });
                            }

                            command.Reply("Marked package {0}{1}{2} ({3}) as important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }

                        return;
                    }

                    break;

                case "remove":
                    if (count < 3)
                    {
                        break;
                    }

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                        List <string> channels;

                        if (!Application.ImportantApps.TryGetValue(id, out channels) || !channels.Contains(channel))
                        {
                            command.Reply("App {0}{1}{2} ({3}) is not important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }
                        else
                        {
                            if (channels.Count > 1)
                            {
                                Application.ImportantApps[id].Remove(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Remove(id);
                            }

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("DELETE FROM `ImportantApps` WHERE `AppID` = @AppID AND `Channel` = @Channel", new { AppID = id, Channel = channel });
                            }

                            command.Reply("Removed app {0}{1}{2} ({3}) from the important list in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }

                        return;

                    case "sub":
                        if (!Application.ImportantSubs.ContainsKey(id))
                        {
                            command.Reply("Package {0}{1}{2} ({3}) is not important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }
                        else
                        {
                            Application.ImportantSubs.Remove(id);

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("DELETE FROM `ImportantSubs` WHERE `SubID` = @SubID", new { SubID = id });
                            }

                            command.Reply("Removed package {0}{1}{2} ({3}) from the important list.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }

                        return;
                    }

                    break;
                }
            }

            command.Reply("Usage:{0} important reload {1}or{2} important <add/remove> <app/sub> <id>", Colors.OLIVE, Colors.NORMAL, Colors.OLIVE);
        }
Esempio n. 22
0
        public override void OnCommand(CommandArguments command)
        {
            if (command.CommandType != ECommandType.IRC || !IRC.IsRecipientChannel(command.Recipient))
            {
                CommandHandler.ReplyToCommand(command, "This command is only available in channels.");

                return;
            }

            var channel = command.Recipient;

            var s     = command.Message.Split(' ');
            var count = s.Length;

            if (count > 0)
            {
                switch (s[0])
                {
                case "reload":
                {
                    Application.ReloadImportant(command);
                    PICSTokens.Reload(command);
                    FileDownloader.ReloadFileList();

                    return;
                }

                case "add":
                {
                    if (count < 3)
                    {
                        break;
                    }

                    uint id;

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                    {
                        List <string> channels;
                        var           exists = Application.ImportantApps.TryGetValue(id, out channels);

                        if (exists && channels.Contains(channel))
                        {
                            CommandHandler.ReplyToCommand(command, "App {0}{1}{2} ({3}) is already important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }
                        else
                        {
                            if (exists)
                            {
                                Application.ImportantApps[id].Add(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Add(id, new List <string> {
                                            channel
                                        });
                            }

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("INSERT INTO `ImportantApps` (`AppID`, `Channel`) VALUES (@AppID, @Channel)", new { AppID = id, Channel = channel });
                            }

                            CommandHandler.ReplyToCommand(command, "Marked app {0}{1}{2} ({3}) as important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }

                        return;
                    }

                    case "sub":
                    {
                        if (Application.ImportantSubs.ContainsKey(id))
                        {
                            CommandHandler.ReplyToCommand(command, "Package {0}{1}{2} ({3}) is already important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }
                        else
                        {
                            Application.ImportantSubs.Add(id, 1);

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("INSERT INTO `ImportantSubs` (`SubID`) VALUES (@SubID)", new { SubID = id });
                            }

                            CommandHandler.ReplyToCommand(command, "Marked package {0}{1}{2} ({3}) as important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }

                        return;
                    }
                    }

                    break;
                }

                case "remove":
                {
                    if (count < 3)
                    {
                        break;
                    }

                    uint id;

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                    {
                        List <string> channels;

                        if (!Application.ImportantApps.TryGetValue(id, out channels) || !channels.Contains(channel))
                        {
                            CommandHandler.ReplyToCommand(command, "App {0}{1}{2} ({3}) is not important in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }
                        else
                        {
                            if (channels.Count > 1)
                            {
                                Application.ImportantApps[id].Remove(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Remove(id);
                            }

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("DELETE FROM `ImportantApps` WHERE `AppID` = @AppID AND `Channel` = @Channel", new { AppID = id, Channel = channel });
                            }

                            CommandHandler.ReplyToCommand(command, "Removed app {0}{1}{2} ({3}) from the important list in {4}{5}{6}.", Colors.BLUE, id, Colors.NORMAL, Steam.GetAppName(id), Colors.BLUE, channel, Colors.NORMAL);
                        }

                        return;
                    }

                    case "sub":
                    {
                        if (!Application.ImportantSubs.ContainsKey(id))
                        {
                            CommandHandler.ReplyToCommand(command, "Package {0}{1}{2} ({3}) is not important.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }
                        else
                        {
                            Application.ImportantSubs.Remove(id);

                            using (var db = Database.GetConnection())
                            {
                                db.Execute("DELETE FROM `ImportantSubs` WHERE `SubID` = @SubID", new { SubID = id });
                            }

                            CommandHandler.ReplyToCommand(command, "Removed package {0}{1}{2} ({3}) from the important list.", Colors.BLUE, id, Colors.NORMAL, Steam.GetPackageName(id));
                        }

                        return;
                    }
                    }

                    break;
                }

                case "queue":
                {
                    if (count < 3)
                    {
                        break;
                    }

                    uint id;

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                    {
                        string name;

                        using (var db = Database.GetConnection())
                        {
                            name = db.ExecuteScalar <string>("SELECT `Name` FROM `Apps` WHERE `AppID` = @AppID", new { AppID = id });
                        }

                        if (!string.IsNullOrEmpty(name))
                        {
                            StoreQueue.AddAppToQueue(id);

                            CommandHandler.ReplyToCommand(command, "App {0}{1}{2} ({3}) has been added to the store update queue.", Colors.BLUE, id, Colors.NORMAL, Utils.RemoveControlCharacters(name));

                            return;
                        }

                        CommandHandler.ReplyToCommand(command, "This app is not in the database.");

                        return;
                    }

                    case "sub":
                    {
                        if (id == 0)
                        {
                            CommandHandler.ReplyToCommand(command, "Sub 0 can not be queued.");

                            return;
                        }

                        string name;

                        using (var db = Database.GetConnection())
                        {
                            name = db.ExecuteScalar <string>("SELECT `Name` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = id });
                        }

                        if (!string.IsNullOrEmpty(name))
                        {
                            StoreQueue.AddPackageToQueue(id);

                            CommandHandler.ReplyToCommand(command, "Package {0}{1}{2} ({3}) has been added to the store update queue.", Colors.BLUE, id, Colors.NORMAL, Utils.RemoveControlCharacters(name));

                            return;
                        }

                        CommandHandler.ReplyToCommand(command, "This package is not in the database.");

                        return;
                    }
                    }

                    break;
                }
                }
            }

            CommandHandler.ReplyToCommand(command, "Usage:{0} important reload {1}or{2} important <add/remove/queue> <app/sub> <id>", Colors.OLIVE, Colors.NORMAL, Colors.OLIVE);
        }
Esempio n. 23
0
        public static bool HandleToken(uint id, ulong accessToken)
        {
            if (!SecretTokens.ContainsKey(id))
            {
                SecretTokens.Add(id, accessToken);

                IRC.Instance.SendOps($"{Colors.GREEN}[Tokens]{Colors.NORMAL} Added a new token that the bot got itself:{Colors.BLUE} {id} {Colors.NORMAL}({Steam.GetAppName(id)})");

                Log.WriteInfo("PICSTokens", "New token for appid {0}", id);

                using (var db = Database.Get())
                {
                    db.Execute("INSERT INTO `PICSTokens` (`AppID`, `Token`) VALUES(@AppID, @Token)",
                               new { AppID = id, Token = accessToken }
                               );
                }

                return(true);
            }
            else if (SecretTokens[id] != accessToken)
            {
                IRC.Instance.SendOps($"{Colors.GREEN}[Tokens]{Colors.NORMAL} Bot got a token that mismatches the one in database: {SecretTokens[id]} != {accessToken}");
            }

            return(false);
        }
Esempio n. 24
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")}"
                );
        }
Esempio n. 25
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (command.CommandType != ECommandType.IRC || !IRC.IsRecipientChannel(command.Recipient))
            {
                command.Reply("This command is only available in channels.");

                return;
            }

            var channel = command.Recipient;

            var s     = command.Message.Split(' ');
            var count = s.Length;

            if (count > 0)
            {
                uint id;
                switch (s[0])
                {
                case "reload":
                    await Application.ReloadImportant(command);

                    PICSTokens.Reload(command);

                    return;

                case "add":
                    if (count < 3)
                    {
                        break;
                    }

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                        var exists = Application.ImportantApps.TryGetValue(id, out var channels);

                        if (exists && channels.Contains(channel))
                        {
                            command.Reply($"App {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetAppName(id)}) is already important in {Colors.BLUE}{channel}{Colors.NORMAL}.");
                        }
                        else
                        {
                            if (exists)
                            {
                                Application.ImportantApps[id].Add(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Add(id, new List <string> {
                                    channel
                                });
                            }

                            await using (var db = await Database.GetConnectionAsync())
                            {
                                await db.ExecuteAsync("INSERT INTO `ImportantApps` (`AppID`, `Channel`) VALUES (@AppID, @Channel)", new { AppID = id, Channel = channel });
                            }

                            command.Reply($"Marked app {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetAppName(id)}) as important in {Colors.BLUE}{channel}{Colors.NORMAL}.");
                        }

                        return;

                    case "sub":
                        if (Application.ImportantSubs.ContainsKey(id))
                        {
                            command.Reply($"Package {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetPackageName(id)}) is already important.");
                        }
                        else
                        {
                            Application.ImportantSubs.Add(id, 1);

                            await using (var db = await Database.GetConnectionAsync())
                            {
                                await db.ExecuteAsync("INSERT INTO `ImportantSubs` (`SubID`) VALUES (@SubID)", new { SubID = id });
                            }

                            command.Reply($"Marked package {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetPackageName(id)}) as important.");
                        }

                        return;
                    }

                    break;

                case "remove":
                    if (count < 3)
                    {
                        break;
                    }

                    if (!uint.TryParse(s[2], out id))
                    {
                        break;
                    }

                    switch (s[1])
                    {
                    case "app":
                        if (!Application.ImportantApps.TryGetValue(id, out var channels) || !channels.Contains(channel))
                        {
                            command.Reply($"App {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetAppName(id)}) is not important in {Colors.BLUE}{channel}{Colors.NORMAL}.");
                        }
                        else
                        {
                            if (channels.Count > 1)
                            {
                                Application.ImportantApps[id].Remove(channel);
                            }
                            else
                            {
                                Application.ImportantApps.Remove(id);
                            }

                            await using (var db = await Database.GetConnectionAsync())
                            {
                                await db.ExecuteAsync("DELETE FROM `ImportantApps` WHERE `AppID` = @AppID AND `Channel` = @Channel", new { AppID = id, Channel = channel });
                            }

                            command.Reply($"Removed app {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetAppName(id)}) from the important list in {Colors.BLUE}{channel}{Colors.NORMAL}.");
                        }

                        return;

                    case "sub":
                        if (!Application.ImportantSubs.ContainsKey(id))
                        {
                            command.Reply($"Package {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetPackageName(id)}) is not important.");
                        }
                        else
                        {
                            Application.ImportantSubs.Remove(id);

                            await using (var db = await Database.GetConnectionAsync())
                            {
                                await db.ExecuteAsync("DELETE FROM `ImportantSubs` WHERE `SubID` = @SubID", new { SubID = id });
                            }

                            command.Reply($"Removed package {Colors.BLUE}{id}{Colors.NORMAL} ({Steam.GetPackageName(id)}) from the important list.");
                        }

                        return;
                    }

                    break;
                }
            }

            command.Reply($"Usage:{Colors.OLIVE} important reload {Colors.NORMAL}or{Colors.OLIVE} important <add/remove> <app/sub> <id>");
        }
        private async Task <bool> ProcessKey(string keyName, string displayName, string value, KeyValue newKv = null)
        {
            if (keyName.Length > 90)
            {
                Log.WriteError("App Processor", "Key {0} for AppID {1} is too long, not inserting info.", keyName, AppID);

                return(false);
            }

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

            if (!CurrentData.ContainsKey(keyName))
            {
                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("App Processor", "Failed to create key {0} for AppID {1}, not inserting info.", keyName, AppID);

                        return(false);
                    }

                    IRC.Instance.SendOps("New app keyname: {0}{1} {2}(ID: {3}) ({4}) - {5}", Colors.BLUE, keyName, Colors.LIGHTGRAY, 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 {0}=Valve app: {1}{2}{3} -{4} {5}",
                                         displayName,
                                         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("App Processor", "Duplicate key {0} in AppID {1}", keyName, 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);
        }
Esempio n. 27
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));
            }
        }
Esempio n. 28
0
        public static void HandleToken(uint id, ulong accessToken)
        {
            if (!SecretTokens.ContainsKey(id))
            {
                SecretTokens.Add(id, accessToken);

                IRC.Instance.SendOps("[TOKENS] Added a new token that the bot got itself: {0} ({1})", id, Steam.GetAppName(id));

                Log.WriteInfo("PICSTokens", "New token for appid {0}", id);

                using (var db = Database.GetConnection())
                {
                    db.Execute("INSERT INTO `PICSTokens` (`AppID`, `Token`, `CommunityID`) VALUES(@AppID, @Token, @CommunityID)",
                               new { AppID = id, Token = accessToken, CommunityID = Steam.Instance.Client.SteamID.ConvertToUInt64() }
                               );
                }
            }
            else if (SecretTokens[id] != accessToken)
            {
                IRC.Instance.SendOps("[TOKENS] Yo xPaw, bot got a token that mismatches the one in database: {0} != {1}", SecretTokens[id], accessToken);
            }
        }
Esempio n. 29
0
        private async Task DownloadDepots(uint appID, List <ManifestJob> depots)
        {
            Log.WriteDebug("Depot Downloader", "Will process {0} depots ({1} depot locks left)", depots.Count(), DepotLocks.Count);

            var  processTasks       = new List <Task <EResult> >();
            bool anyFilesDownloaded = false;

            foreach (var depot in depots)
            {
                var instance = depot.Anonymous ? Steam.Anonymous.Apps : Steam.Instance.Apps;

                depot.DepotKey = await GetDepotDecryptionKey(instance, depot.DepotID, appID);

                if (depot.DepotKey == null)
                {
                    RemoveLock(depot.DepotID);

                    continue;
                }

                var cdnToken = await GetCDNAuthToken(instance, appID, depot.DepotID);

                if (cdnToken == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteDebug("Depot Downloader", "Got a depot key for depot {0} but no cdn auth token", depot.DepotID);

                    continue;
                }

                depot.CDNToken = cdnToken.Token;
                depot.Server   = GetContentServer();

                DepotManifest depotManifest = null;
                string        lastError     = string.Empty;

                for (var i = 0; i <= 5; i++)
                {
                    try
                    {
                        depotManifest = await CDNClient.DownloadManifestAsync(depot.DepotID, depot.ManifestID, depot.Server, depot.CDNToken, depot.DepotKey);

                        break;
                    }
                    catch (Exception e)
                    {
                        lastError = e.Message;

                        Log.WriteError("Depot Processor", "Failed to download depot manifest for app {0} depot {1} ({2}: {3}) (#{4})", appID, depot.DepotID, depot.Server, lastError, i);
                    }

                    // TODO: get new auth key if auth fails
                    depot.Server = GetContentServer();

                    if (depotManifest == null)
                    {
                        await Task.Delay(Utils.ExponentionalBackoff(i));
                    }
                }

                if (depotManifest == null)
                {
                    LocalConfig.CDNAuthTokens.TryRemove(depot.DepotID, out _);

                    RemoveLock(depot.DepotID);

                    if (FileDownloader.IsImportantDepot(depot.DepotID))
                    {
                        IRC.Instance.SendOps("{0}[{1}]{2} Failed to download manifest ({3})",
                                             Colors.OLIVE, depot.DepotName, Colors.NORMAL, lastError);
                    }

                    if (!Settings.IsFullRun)
                    {
                        JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appID, null));
                    }

                    continue;
                }

                var task = ProcessDepotAfterDownload(depot, depotManifest);

                processTasks.Add(task);

                if (!FileDownloader.IsImportantDepot(depot.DepotID))
                {
                    continue;
                }

                task = TaskManager.Run(async() =>
                {
                    var result = EResult.Fail;

                    try
                    {
                        result = await FileDownloader.DownloadFilesFromDepot(depot, depotManifest);

                        if (result == EResult.OK)
                        {
                            anyFilesDownloaded = true;
                        }
                    }
                    catch (Exception e)
                    {
                        ErrorReporter.Notify($"Depot Processor {depot.DepotID}", e);
                    }

                    return(result);
                }).Unwrap();

                processTasks.Add(task);
            }

            if (SaveLocalConfig)
            {
                SaveLocalConfig = false;

                LocalConfig.Save();
            }

            await Task.WhenAll(processTasks).ConfigureAwait(false);

            Log.WriteDebug("Depot Downloader", "{0} depot downloads finished", depots.Count());

            // TODO: use ContinueWith on tasks
            if (!anyFilesDownloaded)
            {
                foreach (var depot in depots)
                {
                    RemoveLock(depot.DepotID);
                }

                return;
            }

            if (!File.Exists(UpdateScript))
            {
                return;
            }

            lock (UpdateScriptLock)
            {
                foreach (var depot in depots)
                {
                    if (depot.Result == EResult.OK)
                    {
                        RunUpdateScript(string.Format("{0} no-git", depot.DepotID));
                    }
                    else if (depot.Result != EResult.Ignored)
                    {
                        Log.WriteWarn("Depot Processor", "Dropping stored token for {0} due to download failures", depot.DepotID);

                        LocalConfig.CDNAuthTokens.TryRemove(depot.DepotID, out _);

                        using (var db = Database.Get())
                        {
                            // Mark this depot for redownload
                            db.Execute("UPDATE `Depots` SET `LastManifestID` = 0 WHERE `DepotID` = @DepotID", new { depot.DepotID });
                        }
                    }

                    RemoveLock(depot.DepotID);
                }

                // Only commit changes if all depots downloaded
                if (processTasks.All(x => x.Result == EResult.OK || x.Result == EResult.Ignored))
                {
                    if (!RunUpdateScript(appID, depots.First().BuildID))
                    {
                        RunUpdateScript("0");
                    }
                }
                else
                {
                    Log.WriteDebug("Depot Processor", "Reprocessing the app {0} because some files failed to download", appID);

                    IRC.Instance.SendOps("{0}[{1}]{2} Reprocessing the app due to download failures",
                                         Colors.OLIVE, Steam.GetAppName(appID), Colors.NORMAL
                                         );

                    JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appID, null));
                }
            }
        }
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply("Usage:{0} players <appid or partial game name>", Colors.OLIVE);

                return;
            }

            string name;

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

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

            var callback = await Steam.Instance.UserStats.GetNumberOfCurrentPlayers(appID);

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

            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 type = "playing";

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

            case "Legacy Media":
            case "Series":
            case "Video":
                type = "watching";
                break;

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

            case "Guide":
                type = "reading";
                break;

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

            command.Reply(
                "{0}{1:N0}{2} {3} {4}{5}{6} -{7} {8}",
                Colors.OLIVE, callback.NumPlayers, Colors.NORMAL,
                type,
                Colors.BLUE, name, Colors.NORMAL,
                Colors.DARKBLUE, SteamDB.GetAppURL(appID, "graphs")
                );
        }