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); }
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); }
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")}"); } }
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; }
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); }
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)}"); } } }
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)); } }
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}"); } }
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()); } } }
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); }
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; }
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(); } } }
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); }
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); }
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); }
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")}" ); }
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); }
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)); } }
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); } }
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") ); }