private void OnLicenseListCallback(SteamApps.LicenseListCallback licenseList) { if (licenseList.Result != EResult.OK) { Log.WriteError("Steam", "Unable to get license list: {0}", licenseList.Result); return; } Log.WriteInfo("Steam", "{0} Licenses: {1}", licenseList.LicenseList.Count, string.Join(", ", licenseList.LicenseList.Select(lic => lic.PackageID))); var timeNow = DateTime.Now; foreach (var license in licenseList.LicenseList) { if (license.PackageID != 0 && !OwnedPackages.Contains(license.PackageID) && timeNow.Subtract(license.TimeCreated).TotalSeconds < 600) { IRC.SendMain("New license granted: {0}{1}{2} -{3} {4} {5}({6}, {7})", Colors.OLIVE, SteamProxy.GetPackageName(license.PackageID), Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetPackageURL(license.PackageID), Colors.NORMAL, license.LicenseType, license.PaymentMethod ); } OwnedPackages.Add(license.PackageID); } // TODO: Probably should handle deletions too, for OwnedPackages }
private void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback) { JobManager.TryRemoveJob(callback.JobID); var packageIDs = callback.GrantedPackages; var appIDs = callback.GrantedApps; Log.WriteDebug("FreeLicense", "Received free license: {0} ({1} apps: {2}, {3} packages: {4})", callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs)); if (appIDs.Any()) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty <uint>())); } if (packageIDs.Any()) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty <uint>(), packageIDs)); TaskManager.RunAsync(() => { RefreshPackageNames(); foreach (var subID in packageIDs) { IRC.Instance.SendAnnounce("New free license granted: {0}{1}{2} -{3} {4}", Colors.BLUE, Steam.GetPackageName(subID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(subID) ); } }); } }
private static void OnNumberOfPlayers(SteamUserStats.NumberOfPlayersCallback callback) { JobAction job; if (!JobManager.TryRemoveJob(callback.JobID, out job) || !job.IsCommand) { return; } var request = job.CommandRequest; if (callback.Result != EResult.OK) { CommandHandler.ReplyToCommand(request.Command, "Unable to request player count: {0}{1}", Colors.RED, callback.Result); } else if (request.Target == 0) { CommandHandler.ReplyToCommand( request.Command, "{0}{1:N0}{2} people praising lord Gaben right now, influence:{3} {4}", Colors.OLIVE, callback.NumPlayers, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(753, "graphs") ); } else { CommandHandler.ReplyToCommand( request.Command, "People playing {0}{1}{2} right now: {3}{4:N0}{5} -{6} {7}", Colors.BLUE, Steam.GetAppName(request.Target), Colors.NORMAL, Colors.OLIVE, callback.NumPlayers, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(request.Target, "graphs") ); } }
public override async Task OnCommand(CommandArguments command) { uint subID; if (command.Message.Length == 0 || !uint.TryParse(command.Message, out subID)) { command.Reply("Usage:{0} sub <subid>", Colors.OLIVE); return; } var job = await Steam.Instance.Apps.PICSGetProductInfo(null, subID, false, false); var callback = job.Results.FirstOrDefault(x => x.Packages.ContainsKey(subID)); if (callback == null) { command.Reply("Unknown SubID: {0}{1}{2}", Colors.BLUE, subID, LicenseList.OwnedSubs.ContainsKey(subID) ? SteamDB.StringCheckmark : string.Empty); return; } var info = callback.Packages[subID]; info.KeyValues.SaveToFile(Path.Combine(Application.Path, "sub", string.Format("{0}.vdf", info.ID)), false); command.Reply("{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}", Colors.BLUE, Steam.GetPackageName(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetRawPackageURL(info.ID), Colors.NORMAL, info.MissingToken ? SteamDB.StringNeedToken : string.Empty, LicenseList.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty ); }
public override async Task OnCommand(CommandArguments command) { BlogPost post; await using (var db = await Database.GetConnectionAsync()) { post = (await db.QueryAsync <BlogPost>("SELECT `ID`, `Slug`, `Title` FROM `Blog` WHERE `IsHidden` = 0 ORDER BY `Time` DESC LIMIT 1")).SingleOrDefault(); } if (post.ID == 0) { command.Reply("No blog post found."); return; } command.Reply( command.Message.Length > 0 ? "Blog post:{0} {1}{2} -{3} {4}" : "Latest blog post:{0} {1}{2} -{3} {4}", Colors.BLUE, post.Title, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetBlogUrl(post.Slug.Length > 0 ? post.Slug : post.ID.ToString()) ); }
private void PrintImportants(SteamApps.PICSChangesCallback callback) { // Apps var important = callback.AppChanges.Keys.Intersect(Application.ImportantApps.Keys); string appType; string appName; foreach (var app in important) { appName = Steam.GetAppName(app, out 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 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")); if (Settings.Current.CanQueryStore) { Steam.Instance.UnifiedMessages.SendMessage("ChatRoom.SendChatMessage#1", new CChatRoom_SendChatMessage_Request { chat_group_id = 1147, chat_id = 10208600, message = $"[Changelist {callback.CurrentChangeNumber}] {appType} update: {appName}\n{SteamDB.GetAppURL(app, "history")}?utm_source=Steam&utm_medium=Steam&utm_campaign=SteamDB%20Group%20Chat" }); } } // 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 static void PrintImportants(SteamApps.PICSChangesCallback callback) { // Apps var important = callback.AppChanges.Keys.Intersect(Application.ImportantApps.Keys); foreach (var app in important) { var appName = Steam.GetAppName(app, out var appType); IRC.Instance.AnnounceImportantAppUpdate(app, $"{appType} update: {Colors.BLUE}{appName}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetAppUrl(app, "history")}"); if (Settings.Current.CanQueryStore) { Steam.Instance.UnifiedMessages.SendMessage("ChatRoom.SendChatMessage#1", new CChatRoom_SendChatMessage_Request { chat_group_id = 1147, chat_id = 10208600, message = $"{appType} update: {appName}\n<{SteamDB.GetAppUrl(app, "history")}?changeid={callback.CurrentChangeNumber}>" }); } } // Packages important = callback.PackageChanges.Keys.Intersect(Application.ImportantSubs.Keys); foreach (var package in important) { IRC.Instance.SendMain($"Package update: {Colors.BLUE}{Steam.GetPackageName(package)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPackageUrl(package, "history")}"); } }
public override void OnCommand(CommandArguments command) { BlogPost post; using (var db = Database.GetConnection()) { if (command.Message.Length > 0) { post = db.Query <BlogPost>("SELECT `ID`, `Slug`, `Title` FROM `Blog` WHERE `IsHidden` = 0 AND (`Slug` = @Slug OR `ID` = @Slug) LIMIT 1", new { Slug = command.Message }).SingleOrDefault(); } else { post = db.Query <BlogPost>("SELECT `ID`, `Slug`, `Title` FROM `Blog` WHERE `IsHidden` = 0 ORDER BY `Time` DESC LIMIT 1").SingleOrDefault(); } } if (post.ID == 0) { CommandHandler.ReplyToCommand(command, "No blog post found."); return; } CommandHandler.ReplyToCommand( command, command.Message.Length > 0 ? "Blog post:{0} {1}{2} -{3} {4}" : "Latest blog post:{0} {1}{2} -{3} {4}", Colors.BLUE, post.Title, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetBlogURL(post.Slug.Length > 0 ? post.Slug : post.ID.ToString()) ); }
public override async Task OnCommand(CommandArguments command) { if (command.Message.Length == 0 || !uint.TryParse(command.Message, out var subID)) { command.Reply("Usage:{0} sub <subid>", Colors.OLIVE); return; } var tokenTask = Steam.Instance.Apps.PICSGetAccessTokens(null, subID); tokenTask.Timeout = TimeSpan.FromSeconds(10); var tokenCallback = await tokenTask; SteamApps.PICSRequest request; if (tokenCallback.PackageTokens.ContainsKey(subID)) { request = PICSTokens.NewPackageRequest(subID, tokenCallback.PackageTokens[subID]); } else { request = PICSTokens.NewPackageRequest(subID); } var infoTask = Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty <SteamApps.PICSRequest>(), new List <SteamApps.PICSRequest> { request }); infoTask.Timeout = TimeSpan.FromSeconds(10); var job = await infoTask; var callback = job.Results.FirstOrDefault(x => x.Packages.ContainsKey(subID)); if (callback == null) { command.Reply("Unknown SubID: {0}{1}{2}", Colors.BLUE, subID, LicenseList.OwnedSubs.ContainsKey(subID) ? SteamDB.StringCheckmark : string.Empty); return; } var info = callback.Packages[subID]; info.KeyValues.SaveToFile(Path.Combine(Application.Path, "sub", string.Format("{0}.vdf", info.ID)), false); command.Reply("{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}", Colors.BLUE, Steam.GetPackageName(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageUrl(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetRawPackageUrl(info.ID), Colors.NORMAL, info.MissingToken ? SteamDB.StringNeedToken : string.Empty, LicenseList.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty ); }
public override async Task OnCommand(CommandArguments command) { if (string.IsNullOrWhiteSpace(command.Message)) { command.Reply($"Usage:{Colors.OLIVE} app <appid or partial game name>"); return; } if (!uint.TryParse(command.Message, out var appID)) { appID = await TrySearchAppId(command); if (appID == 0) { return; } } var info = await GetAppData(appID); if (info == null) { command.Reply($"Unknown AppID: {Colors.BLUE}{appID}{(LicenseList.OwnedApps.ContainsKey(appID) ? SteamDB.StringCheckmark : string.Empty)}"); return; } string name; if (info.KeyValues["common"]["name"].Value != null) { name = Utils.LimitStringLength(Utils.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString())); } else { name = Steam.GetAppName(info.ID); } var filename = $"{Utils.ByteArrayToString(info.SHAHash)}.vdf"; info.KeyValues.SaveToFile(Path.Combine(Application.Path, "app", filename), false); command.Reply($"{Colors.BLUE}{name}{Colors.NORMAL} -{Colors.DARKBLUE} <{SteamDB.GetAppUrl(info.ID)}>{Colors.NORMAL} - Dump:{Colors.DARKBLUE} <{SteamDB.GetRawAppUrl(filename)}>{Colors.NORMAL}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty)}"); if (command.Recipient == Settings.Current.IRC.Channel.Ops && !LicenseList.OwnedApps.ContainsKey(info.ID)) { JobManager.AddJob(() => Steam.Instance.Apps.RequestFreeLicense(info.ID)); } }
private void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback) { JobManager.TryRemoveJob(callback.JobID); var packageIDs = callback.GrantedPackages; var appIDs = callback.GrantedApps; Log.WriteDebug(nameof(FreeLicense), "Received free license: {0} ({1} apps: {2}, {3} packages: {4})", callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs)); if (appIDs.Count > 0) { JobManager.AddJob( () => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty <uint>()), new PICSTokens.RequestedTokens { Apps = appIDs.ToList() }); foreach (var appid in appIDs) { LocalConfig.Current.FreeLicensesToRequest.Remove(appid); } LocalConfig.Save(); } if (packageIDs.Count > 0) { JobManager.AddJob( () => Steam.Instance.Apps.PICSGetAccessTokens(Enumerable.Empty <uint>(), packageIDs), new PICSTokens.RequestedTokens { Packages = packageIDs.ToList() }); TaskManager.RunAsync(async() => { await RefreshPackageNames(); foreach (var subID in packageIDs) { IRC.Instance.SendAnnounce("New free license granted: {0}{1}{2} -{3} {4}", Colors.BLUE, Steam.GetPackageName(subID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageUrl(subID) ); } }); } }
public override async void OnCommand(CommandArguments command) { uint subID; if (command.Message.Length == 0 || !uint.TryParse(command.Message, out subID)) { CommandHandler.ReplyToCommand(command, "Usage:{0} sub <subid>", Colors.OLIVE); return; } var count = PICSProductInfo.ProcessedSubs.Count; if (count > 100) { CommandHandler.ReplyToCommand(command, "There are currently {0} packages awaiting to be processed, try again later.", count); return; } var job = await Steam.Instance.Apps.PICSGetProductInfo(null, subID, false, false); var callback = job.Results.First(x => !x.ResponsePending); if (!callback.Packages.ContainsKey(subID)) { CommandHandler.ReplyToCommand(command, "Unknown SubID: {0}{1}{2}", Colors.BLUE, subID, LicenseList.OwnedSubs.ContainsKey(subID) ? SteamDB.StringCheckmark : string.Empty); return; } var info = callback.Packages[subID]; info.KeyValues.SaveToFile(Path.Combine(Application.Path, "sub", string.Format("{0}.vdf", info.ID)), false); CommandHandler.ReplyToCommand(command, "{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}", Colors.BLUE, Steam.GetPackageName(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(info.ID), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetRawPackageURL(info.ID), Colors.NORMAL, info.MissingToken ? SteamDB.StringNeedToken : string.Empty, LicenseList.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty ); }
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")}"); } }
public void OnNumberOfPlayers(SteamUserStats.NumberOfPlayersCallback callback, JobID jobID) { var request = IRCRequests.Find(r => r.JobID == jobID); if (request == null) { return; } IRCRequests.Remove(request); if (callback.Result != EResult.OK) { CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2}: Unable to request player count: {3}", Colors.OLIVE, request.Command.Nickname, Colors.NORMAL, callback.Result); } else if (request.Target == 0) { CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2}: {3}{4:N0}{5} people praising lord Gaben right now, influence:{6} {7}", Colors.OLIVE, request.Command.Nickname, Colors.NORMAL, Colors.OLIVE, callback.NumPlayers, Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetGraphURL(0)); } else { string graphUrl = string.Empty; string name = GetAppName(request.Target); using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `AppID` FROM `ImportantApps` WHERE (`Graph` = 1 OR `MaxPlayers` > 0) AND `AppID` = @AppID", new MySqlParameter("AppID", request.Target))) { if (Reader.Read()) { graphUrl = string.Format("{0} -{1} {2}", Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetGraphURL(request.Target)); } } CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2}: People playing {3}{4}{5} right now: {6}{7:N0}{8} -{9} {10}{11}", Colors.OLIVE, request.Command.Nickname, Colors.NORMAL, Colors.OLIVE, name, Colors.NORMAL, Colors.GREEN, callback.NumPlayers, Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetAppURL(request.Target), graphUrl); } }
public void OnChatMessage(SteamFriends.ChatMsgCallback callback) { if (callback.ChatMsgType != EChatEntryType.ChatMsg || callback.Message[0] != '!' || callback.Message.Contains('\n')) { return; } var i = callback.Message.IndexOf(' '); var inputCommand = i == -1 ? callback.Message : callback.Message.Substring(0, i); Action <CommandHandler.CommandArguments> callbackFunction; if (CommandHandler.Commands.TryGetValue(inputCommand, out callbackFunction)) { var input = i == -1 ? string.Empty : callback.Message.Substring(i).Trim(); var command = new CommandHandler.CommandArguments { SenderID = callback.ChatterID, ChatRoomID = callback.ChatRoomID, Nickname = Steam.Instance.Friends.GetFriendPersonaName(callback.ChatterID), Message = input }; if (SteamDB.IsBusy()) { CommandHandler.ReplyToCommand(command, "{0}{1}{2}: The bot is currently busy.", Colors.OLIVE, command.Nickname, Colors.NORMAL); return; } Log.WriteInfo("Steam", "Handling command {0} for user {1} in chatroom {2}", inputCommand, callback.ChatterID, callback.ChatRoomID); callbackFunction(command); } }
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)); } }
public override async Task OnCommand(CommandArguments command) { if (command.Message.Length == 0) { command.Reply("Usage:{0} pubfile <pubfileid>", Colors.OLIVE); return; } if (!ulong.TryParse(command.Message, out var pubFileId)) { command.Reply("Invalid Published File ID"); return; } var pubFileRequest = new CPublishedFile_GetDetails_Request { includeadditionalpreviews = true, includechildren = true, includetags = true, includekvtags = true, includevotes = true, includeforsaledata = true, includemetadata = true, }; pubFileRequest.publishedfileids.Add(pubFileId); var task = PublishedFiles.SendMessage(api => api.GetDetails(pubFileRequest)); task.Timeout = TimeSpan.FromSeconds(10); var callback = await task; var response = callback.GetDeserializedResponse <CPublishedFile_GetDetails_Response>(); var details = response.publishedfiledetails.FirstOrDefault(); if (details == null) { command.Reply("Unable to make service request for published file info: the server returned no info"); return; } var result = (EResult)details.result; if (result != EResult.OK) { command.Reply("Unable to get published file info: {0}{1}", Colors.RED, result); return; } var json = JsonConvert.SerializeObject(details, Formatting.Indented); File.WriteAllText(Path.Combine(Application.Path, "ugc", string.Format("{0}.json", details.publishedfileid)), json, Encoding.UTF8); command.Reply("{0}, Title: {1}{2}{3}, Creator: {4}{5}{6}, App: {7}{8}{9}{10}, File UGC: {11}{12}{13}, Preview UGC: {14}{15}{16} -{17} {18}", (EWorkshopFileType)details.file_type, Colors.BLUE, string.IsNullOrWhiteSpace(details.title) ? "[no title]" : details.title, Colors.NORMAL, Colors.BLUE, new SteamID(details.creator).Render(true), Colors.NORMAL, Colors.BLUE, details.creator_appid, details.creator_appid == details.consumer_appid ? "" : string.Format(" (consumer {0})", details.consumer_appid), Colors.NORMAL, Colors.BLUE, details.hcontent_file, Colors.NORMAL, Colors.BLUE, details.hcontent_preview, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetUGCURL(details.publishedfileid) ); command.Notice("{0} - https://steamcommunity.com/sharedfiles/filedetails/?id={1}", details.file_url, details.publishedfileid); }
private async Task SendChangelistsToIRC(SteamApps.PICSChangesCallback callback) { if (DateTime.Now > ChangelistBurstTime) { ChangelistBurstTime = DateTime.Now.AddMinutes(5); ChangelistBurstCount = 0; } // Group apps and package changes by changelist number var changelists = new Dictionary <uint, IrcChangelistGroup>(); foreach (var app in callback.AppChanges.Values) { if (!changelists.ContainsKey(app.ChangeNumber)) { changelists[app.ChangeNumber] = new IrcChangelistGroup(); } changelists[app.ChangeNumber].Apps.Add(app.ID); } foreach (var package in callback.PackageChanges.Values) { if (!changelists.ContainsKey(package.ChangeNumber)) { changelists[package.ChangeNumber] = new IrcChangelistGroup(); } changelists[package.ChangeNumber].Packages.Add(package.ID); } foreach (var(changeNumber, changeList) in changelists.OrderBy(x => x.Key)) { var appCount = changeList.Apps.Count; var packageCount = changeList.Packages.Count; var message = $"Changelist {Colors.BLUE}{changeNumber}{Colors.NORMAL} {Colors.DARKGRAY}({appCount:N0} apps and {packageCount:N0} packages)"; var changesCount = appCount + packageCount; if (changesCount >= 50) { IRC.Instance.SendMain($"Big {message}{Colors.DARKBLUE} {SteamDB.GetChangelistUrl(changeNumber)}"); } if (ChangelistBurstCount++ >= CHANGELIST_BURST_MIN || changesCount > 300) { if (appCount > 0) { message += $" (Apps: {string.Join(", ", changeList.Apps)})"; } if (packageCount > 0) { message += $" (Packages: {string.Join(", ", changeList.Packages)})"; } IRC.Instance.SendAnnounce($"{Colors.RED}»{Colors.NORMAL} {message}"); continue; } IRC.Instance.SendAnnounce($"{Colors.RED}»{Colors.NORMAL} {message}"); if (appCount > 0) { Dictionary <uint, App> apps; await using (var db = await Database.GetConnectionAsync()) { apps = (await db.QueryAsync <App>("SELECT `AppID`, `Name`, `LastKnownName` FROM `Apps` WHERE `AppID` IN @Ids", new { Ids = changeList.Apps })).ToDictionary(x => x.AppID, x => x); } foreach (var appId in changeList.Apps) { apps.TryGetValue(appId, out var data); IRC.Instance.SendAnnounce($" App: {Colors.BLUE}{appId}{Colors.NORMAL} - {Steam.FormatAppName(appId, data)}"); } } if (packageCount > 0) { Dictionary <uint, Package> packages; await using (var db = await Database.GetConnectionAsync()) { packages = (await db.QueryAsync <Package>("SELECT `SubID`, `Name`, `LastKnownName` FROM `Subs` WHERE `SubID` IN @Ids", new { Ids = changeList.Packages })).ToDictionary(x => x.SubID, x => x); } foreach (var packageId in changeList.Packages) { packages.TryGetValue(packageId, out var data); IRC.Instance.SendAnnounce($" Package: {Colors.BLUE}{packageId}{Colors.NORMAL} - {Steam.FormatPackageName(packageId, data)}"); } } } }
private async Task ProcessKey(string keyName, string displayName, string value, bool isJSON = false) { if (keyName.Length > 90) { Log.WriteError(nameof(SubProcessor), $"Key {keyName} for SubID {SubID} is too long, not inserting info."); return; } // All keys in PICS are supposed to be lower case. // But currently some keys in packages are not lowercased, // this lowercases everything to make sure nothing breaks in future keyName = keyName.ToLowerInvariant().Trim(); if (!CurrentData.ContainsKey(keyName)) { CurrentData[keyName] = new PICSInfo { Processed = true, }; var key = KeyNameCache.GetSubKeyID(keyName); if (key == 0) { var type = isJSON ? 86 : 0; // 86 is a hardcoded const for the website key = await KeyNameCache.CreateSubKey(keyName, displayName, type); if (key == 0) { // We can't insert anything because key wasn't created Log.WriteError(nameof(SubProcessor), $"Failed to create key {keyName} for SubID {SubID}, not inserting info."); return; } IRC.Instance.SendOps($"New package keyname: {Colors.BLUE}{keyName} {Colors.LIGHTGRAY}(ID: {key}) ({displayName}) - {SteamDB.GetPackageUrl(SubID, "history")}"); } await DbConnection.ExecuteAsync("INSERT INTO `SubsInfo` (`SubID`, `Key`, `Value`) VALUES (@SubID, @Key, @Value)", new { SubID, Key = key, Value = value }); await MakeHistory("created_key", key, string.Empty, value); return; } var data = CurrentData[keyName]; if (data.Processed) { Log.WriteWarn(nameof(SubProcessor), $"Duplicate key {keyName} in SubID {SubID}"); return; } data.Processed = true; CurrentData[keyName] = data; if (data.Value == value) { return; } await DbConnection.ExecuteAsync("UPDATE `SubsInfo` SET `Value` = @Value WHERE `SubID` = @SubID AND `Key` = @Key", new { SubID, data.Key, Value = value }); await MakeHistory("modified_key", data.Key, data.Value, value); }
private void SendChangelistsToIRC(SteamApps.PICSChangesCallback callback) { // Group apps and package changes by changelist, this will seperate into individual changelists var appGrouping = callback.AppChanges.Values.GroupBy(a => a.ChangeNumber); var packageGrouping = callback.PackageChanges.Values.GroupBy(p => p.ChangeNumber); // Join apps and packages back together based on changelist number var changeLists = Utils.FullOuterJoin(appGrouping, packageGrouping, a => a.Key, p => p.Key, (a, p, key) => new { ChangeNumber = key, Apps = a.ToList(), Packages = p.ToList(), }, new EmptyGrouping <uint, SteamApps.PICSChangesCallback.PICSChangeData>(), new EmptyGrouping <uint, SteamApps.PICSChangesCallback.PICSChangeData>()) .OrderBy(c => c.ChangeNumber); foreach (var changeList in changeLists) { var appCount = changeList.Apps.Count; var packageCount = changeList.Packages.Count; string Message = string.Format("Changelist {0}{1}{2} {3}({4:N0} apps and {5:N0} packages){6} -{7} {8}", Colors.BLUE, changeList.ChangeNumber, Colors.NORMAL, Colors.DARKGRAY, appCount, packageCount, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetChangelistURL(changeList.ChangeNumber) ); var changesCount = appCount + packageCount; if (changesCount >= 50) { IRC.Instance.SendMain(Message); } IRC.Instance.SendAnnounce("{0}»{1} {2}", Colors.RED, Colors.NORMAL, Message); // If this changelist is very big, freenode will hate us forever if we decide to print all that stuff if (changesCount > 300) { IRC.Instance.SendAnnounce("{0} This changelist is too big to be printed in IRC, please view it online", Colors.RED); continue; } if (appCount > 0) { Dictionary <uint, App> apps; App data; using (var db = Database.GetConnection()) { apps = db.Query <App>("SELECT `AppID`, `Name`, `LastKnownName` FROM `Apps` WHERE `AppID` IN @Ids", new { Ids = changeList.Apps.Select(x => x.ID) }).ToDictionary(x => x.AppID, x => x); } foreach (var app in changeList.Apps) { apps.TryGetValue(app.ID, out data); IRC.Instance.SendAnnounce(" App: {0}{1}{2} - {3}{4}", Colors.BLUE, app.ID, Colors.NORMAL, Steam.FormatAppName(app.ID, data), app.NeedsToken ? SteamDB.StringNeedToken : string.Empty ); } } if (packageCount > 0) { Dictionary <uint, Package> packages; Package data; using (var db = Database.GetConnection()) { packages = db.Query <Package>("SELECT `SubID`, `Name`, `LastKnownName` FROM `Subs` WHERE `SubID` IN @Ids", new { Ids = changeList.Packages.Select(x => x.ID) }).ToDictionary(x => x.SubID, x => x); } foreach (var package in changeList.Packages) { packages.TryGetValue(package.ID, out data); IRC.Instance.SendAnnounce(" Package: {0}{1}{2} - {3}{4}", Colors.BLUE, package.ID, Colors.NORMAL, Steam.FormatPackageName(package.ID, data), package.NeedsToken ? SteamDB.StringNeedToken : string.Empty ); } } } }
private void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback) { JobManager.TryRemoveJob(callback.JobID); var packageIDs = callback.GrantedPackages; var appIDs = callback.GrantedApps; Log.WriteDebug(nameof(FreeLicense), $"Received free license: {callback.Result} ({appIDs.Count} apps: {string.Join(", ", appIDs)}, {packageIDs.Count} packages: {string.Join(", ", packageIDs)})"); if (appIDs.Count > 0) { JobManager.AddJob( () => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty <uint>()), new PICSTokens.RequestedTokens { Apps = appIDs.ToList() }); var removed = false; foreach (var appid in appIDs) { if (FreeLicensesToRequest.Remove(appid)) { removed = true; } } if (removed) { TaskManager.Run(Save); } } if (packageIDs.Count > 0) { JobManager.AddJob( () => Steam.Instance.Apps.PICSGetAccessTokens(Enumerable.Empty <uint>(), packageIDs), new PICSTokens.RequestedTokens { Packages = packageIDs.ToList() }); TaskManager.Run(async() => { await RefreshPackageNames(); foreach (var subID in packageIDs) { IRC.Instance.SendAnnounce($"New free license granted: {Colors.BLUE}{Steam.GetPackageName(subID)}{Colors.NORMAL} -{Colors.DARKBLUE} {SteamDB.GetPackageUrl(subID)}"); } }); } }
public void Process(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo) { ChangeNumber = productInfo.ChangeNumber; if (Settings.IsFullRun) { Log.WriteDebug("App Processor", "AppID: {0}", AppID); DbConnection.Execute("INSERT INTO `Changelists` (`ChangeID`) VALUES (@ChangeNumber) ON DUPLICATE KEY UPDATE `Date` = `Date`", new { productInfo.ChangeNumber }); DbConnection.Execute("INSERT INTO `ChangelistsApps` (`ChangeID`, `AppID`) VALUES (@ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = `AppID`", new { AppID, productInfo.ChangeNumber }); } var app = DbConnection.Query <App>("SELECT `Name`, `AppType` FROM `Apps` WHERE `AppID` = @AppID LIMIT 1", new { AppID }).SingleOrDefault(); var newAppName = productInfo.KeyValues["common"]["name"].AsString(); if (newAppName != null) { int newAppType = -1; string currentType = productInfo.KeyValues["common"]["type"].AsString().ToLower(); using (var reader = DbConnection.ExecuteReader("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType })) { if (reader.Read()) { newAppType = reader.GetInt32(reader.GetOrdinal("AppType")); } } if (newAppType == -1) { DbConnection.Execute("INSERT INTO `AppsTypes` (`Name`, `DisplayName`) VALUES(@Name, @DisplayName)", new { Name = currentType, DisplayName = productInfo.KeyValues["common"]["type"].AsString() }); // We don't need to lower display name Log.WriteInfo("App Processor", "Creating new apptype \"{0}\" (AppID {1})", currentType, AppID); IRC.Instance.SendOps("New app type: {0}{1}{2} - {3}", Colors.BLUE, currentType, Colors.NORMAL, SteamDB.GetAppURL(AppID, "history")); newAppType = DbConnection.ExecuteScalar <int>("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType }); } if (string.IsNullOrEmpty(app.Name) || app.Name.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal)) { DbConnection.Execute("INSERT INTO `Apps` (`AppID`, `AppType`, `Name`, `LastKnownName`) VALUES (@AppID, @Type, @AppName, @AppName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`), `LastKnownName` = VALUES(`LastKnownName`), `AppType` = VALUES(`AppType`)", new { AppID, Type = newAppType, AppName = newAppName } ); MakeHistory("created_app"); MakeHistory("created_info", SteamDB.DATABASE_NAME_TYPE, string.Empty, newAppName); // TODO: Testy testy if (!Settings.IsFullRun && Settings.Current.ChatRooms.Count > 0) { Steam.Instance.Friends.SendChatRoomMessage(Settings.Current.ChatRooms[0], EChatEntryType.ChatMsg, string.Format( "New {0} was published: {1}\nSteamDB: {2}\nSteam: https://store.steampowered.com/app/{3}/", currentType, newAppName, SteamDB.GetAppURL(AppID), AppID ) ); } if ((newAppType > 9 && newAppType != 13) || Triggers.Any(newAppName.Contains)) { IRC.Instance.SendOps("New {0}: {1}{2}{3} -{4} {5}", currentType, Colors.BLUE, newAppName, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history")); } } else if (!app.Name.Equals(newAppName)) { DbConnection.Execute("UPDATE `Apps` SET `Name` = @AppName, `LastKnownName` = @AppName WHERE `AppID` = @AppID", new { AppID, AppName = newAppName }); MakeHistory("modified_info", SteamDB.DATABASE_NAME_TYPE, app.Name, newAppName); } if (app.AppType == 0 || app.AppType != newAppType) { DbConnection.Execute("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID", new { AppID, Type = newAppType }); if (app.AppType == 0) { MakeHistory("created_info", SteamDB.DATABASE_APPTYPE, string.Empty, newAppType.ToString()); } else { MakeHistory("modified_info", SteamDB.DATABASE_APPTYPE, app.AppType.ToString(), newAppType.ToString()); } } } foreach (var section in productInfo.KeyValues.Children) { string sectionName = section.Name.ToLower(); if (sectionName == "appid" || sectionName == "public_only") { continue; } if (sectionName == "change_number") // Carefully handle change_number { sectionName = "root_change_number"; // TODO: Remove this key, move it to Apps table itself ProcessKey(sectionName, "change_number", productInfo.ChangeNumber.ToString()); //section.AsString()); } else if (sectionName == "common" || sectionName == "extended") { string keyName; foreach (KeyValue keyvalue in section.Children) { keyName = string.Format("{0}_{1}", sectionName, keyvalue.Name); if (keyName.Equals("common_type") || keyName.Equals("common_gameid") || keyName.Equals("common_name") || keyName.Equals("extended_order")) { // Ignore common keys that are either duplicated or serve no real purpose continue; } if (keyvalue.Children.Count > 0) { ProcessKey(keyName, keyvalue.Name, Utils.JsonifyKeyValue(keyvalue), true); } else if (!string.IsNullOrEmpty(keyvalue.Value)) { ProcessKey(keyName, keyvalue.Name, keyvalue.Value); } } } else { sectionName = string.Format("root_{0}", sectionName); if (ProcessKey(sectionName, sectionName, Utils.JsonifyKeyValue(section), true) && sectionName.Equals("root_depots")) { DbConnection.Execute("UPDATE `Apps` SET `LastDepotUpdate` = CURRENT_TIMESTAMP() WHERE `AppID` = @AppID", new { AppID }); } } } foreach (var data in CurrentData.Values) { if (!data.Processed && !data.KeyName.StartsWith("website", StringComparison.Ordinal)) { DbConnection.Execute("DELETE FROM `AppsInfo` WHERE `AppID` = @AppID AND `Key` = @Key", new { AppID, data.Key }); MakeHistory("removed_key", data.Key, data.Value); if (newAppName != null && data.Key.Equals("common_section_type") && data.Value.Equals("ownersonly")) { IRC.Instance.SendMain("Removed ownersonly from: {0}{1}{2} -{3} {4}", Colors.BLUE, app.Name, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history")); } } } if (newAppName == null) { if (string.IsNullOrEmpty(app.Name)) // We don't have the app in our database yet { DbConnection.Execute("INSERT INTO `Apps` (`AppID`, `Name`) VALUES (@AppID, @AppName) ON DUPLICATE KEY UPDATE `AppType` = `AppType`", new { AppID, AppName = string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID) }); } else if (!app.Name.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal)) // We do have the app, replace it with default name { IRC.Instance.SendMain("App deleted: {0}{1}{2} -{3} {4}", Colors.BLUE, app.Name, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history")); DbConnection.Execute("UPDATE `Apps` SET `Name` = @AppName, `AppType` = 0 WHERE `AppID` = @AppID", new { AppID, AppName = string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID) }); MakeHistory("deleted_app", 0, app.Name); } } if (productInfo.KeyValues["depots"] != null) { Steam.Instance.DepotProcessor.Process(AppID, ChangeNumber, productInfo.KeyValues["depots"]); } }
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)}"); } } }
private async Task ProcessKey(string keyName, string displayName, string value, bool isJSON = false) { if (keyName.Length > 90) { Log.WriteError("Sub Processor", "Key {0} for SubID {1} is too long, not inserting info.", keyName, SubID); return; } // All keys in PICS are supposed to be lower case. // But currently some keys in packages are not lowercased, // this lowercases everything to make sure nothing breaks in future keyName = keyName.ToLower().Trim(); if (!CurrentData.ContainsKey(keyName)) { var key = await GetKeyNameID(keyName); if (key == 0) { var type = isJSON ? 86 : 0; // 86 is a hardcoded const for the website await DbConnection.ExecuteAsync("INSERT INTO `KeyNamesSubs` (`Name`, `Type`, `DisplayName`) VALUES(@Name, @Type, @DisplayName)", new { Name = keyName, DisplayName = displayName, Type = type }); key = await GetKeyNameID(keyName); if (key == 0) { // We can't insert anything because key wasn't created Log.WriteError("Sub Processor", "Failed to create key {0} for SubID {1}, not inserting info.", keyName, SubID); return; } IRC.Instance.SendOps("New package keyname: {0}{1} {2}(ID: {3}) ({4}) - {5}", Colors.BLUE, keyName, Colors.LIGHTGRAY, key, displayName, SteamDB.GetPackageURL(SubID, "history")); } await DbConnection.ExecuteAsync("INSERT INTO `SubsInfo` (`SubID`, `Key`, `Value`) VALUES (@SubID, @Key, @Value)", new { SubID, Key = key, Value = value }); await MakeHistory("created_key", key, string.Empty, value); return; } var data = CurrentData[keyName]; if (data.Processed) { Log.WriteWarn("Sub Processor", "Duplicate key {0} in SubID {1}", keyName, SubID); return; } data.Processed = true; CurrentData[keyName] = data; if (data.Value.Equals(value)) { return; } await DbConnection.ExecuteAsync("UPDATE `SubsInfo` SET `Value` = @Value WHERE `SubID` = @SubID AND `Key` = @Key", new { SubID, data.Key, Value = value }); await MakeHistory("modified_key", data.Key, data.Value, value); }
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 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)); } }
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 void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback) { JobManager.TryRemoveJob(callback.JobID); var packageIDs = callback.GrantedPackages; var appIDs = callback.GrantedApps; Log.WriteDebug("FreeLicense", "Received free license: {0} ({1} apps: {2}, {3} packages: {4})", callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs)); if (appIDs.Count > 0) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty <uint>())); } if (packageIDs.Count > 0) { JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty <uint>(), packageIDs)); // We don't want to block our main thread with web requests TaskManager.Run(() => { string data = null; try { var response = WebAuth.PerformRequest("GET", "https://store.steampowered.com/account/licenses/"); using (var responseStream = response.GetResponseStream()) { using (var reader = new StreamReader(responseStream)) { data = reader.ReadToEnd(); } } } catch (WebException e) { Log.WriteError("FreeLicense", "Failed to fetch account details page: {0}", e.Message); } using (var db = Database.GetConnection()) { foreach (var package in packageIDs) { var packageData = db.Query <Package>("SELECT `SubID`, `Name`, `LastKnownName` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = package }).FirstOrDefault(); if (!string.IsNullOrEmpty(data)) { // Tell me all about using regex var match = Regex.Match(data, string.Format("RemoveFreeLicense\\( ?{0}, ?'(.+)' ?\\)", package)); if (match.Success) { var grantedName = Encoding.UTF8.GetString(Convert.FromBase64String(match.Groups[1].Value)); // Update last known name if we can if (packageData.SubID > 0 && (string.IsNullOrEmpty(packageData.LastKnownName) || packageData.LastKnownName.StartsWith("Steam Sub ", StringComparison.Ordinal))) { db.Execute("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID = package, Name = grantedName }); db.Execute(SubProcessor.GetHistoryQuery(), new PICSHistory { ID = package, Key = SteamDB.DATABASE_NAME_TYPE, OldValue = "free on demand; account page", NewValue = grantedName, Action = "created_info" } ); // Add a app comment on each app in this package var comment = string.Format("This app is in a free on demand package called <b>{0}</b>", SecurityElement.Escape(grantedName)); var apps = db.Query <PackageApp>("SELECT `AppID` FROM `SubsApps` WHERE `SubID` = @SubID", new { SubID = package }).ToList(); var types = db.Query <App>("SELECT `AppID` FROM `Apps` WHERE `AppType` > 0 AND `AppID` IN @Ids", new { Ids = apps.Select(x => x.AppID) }).ToDictionary(x => x.AppID, x => true); var key = db.ExecuteScalar <uint>("SELECT `ID` FROM `KeyNames` WHERE `Name` = 'website_comment'"); foreach (var app in apps) { if (types.ContainsKey(app.AppID)) { continue; } db.Execute("INSERT INTO `AppsInfo` VALUES (@AppID, @Key, @Value) ON DUPLICATE KEY UPDATE `Key` = `Key`", new { app.AppID, Key = key, value = comment }); } } packageData.LastKnownName = grantedName; } } IRC.Instance.SendMain("New free license granted: {0}{1}{2} -{3} {4}", Colors.BLUE, Steam.FormatPackageName(package, packageData), Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetPackageURL(package) ); } } }); } }
private void PrintLinux() { string appType; var name = Steam.GetAppName(AppID, out appType); if (appType != "Game" && appType != "Application") { return; } IRC.Instance.SendSteamLUG(string.Format( "[oslist][{0}] {1} now lists Linux{2} - {3} - https://store.steampowered.com/app/{4}/", appType, name, LinkExpander.GetFormattedPrices(AppID), SteamDB.GetAppURL(AppID, "history"), AppID )); }