Skip to content

Commit

Permalink
Improved package filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
Citrinate committed Oct 18, 2023
1 parent d190594 commit 6fb51dd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 21 deletions.
72 changes: 54 additions & 18 deletions FreePackages/Handlers/PackageFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ internal async Task UpdateUserData() {
}

if (FilterConfig.ImportStoreFilters) {
// TODO: don't merge these
FilterConfig.IgnoredAppIDs.UnionWith(userData.IgnoredApps.Where(x => x.Value == 0).Select(x => x.Key));
FilterConfig.IgnoredTags.UnionWith(userData.ExcludedTags.Select(x => x.TagID));
FilterConfig.IgnoredContentDescriptors.UnionWith(userData.ExcludedContentDescriptorIDs);
Expand All @@ -64,15 +65,6 @@ internal async Task UpdateUserData() {
internal static bool IsFreeApp(SteamApps.PICSProductInfoCallback.PICSProductInfo app) {
KeyValue kv = app.KeyValues;

string? releaseState = kv["common"]["releasestate"].AsString();
if (releaseState != "released") {
// App not released yet
// Note: There's another seemingly relevant field: kv["common"]["steam_release_date"]
// steam_release_date is not checked because an app can be "released", still have a future release date, and still be redeemed
// Example: https://steamdb.info/changelist/20505012/
return false;
}

if (kv["extended"]["isfreeapp"].AsBoolean()) {
return true;
}
Expand All @@ -91,7 +83,22 @@ internal static bool IsFreeApp(SteamApps.PICSProductInfoCallback.PICSProductInfo
return false;
}

internal bool IsRedeemableApp(SteamApps.PICSProductInfoCallback.PICSProductInfo app) {
internal static bool IsAvailableApp(SteamApps.PICSProductInfoCallback.PICSProductInfo app) {
KeyValue kv = app.KeyValues;

string? releaseState = kv["common"]["releasestate"].AsString();
if (releaseState != "released") {
// App not released yet
// Note: There's another seemingly relevant field: kv["common"]["steam_release_date"]
// steam_release_date is not checked because an app can be "released", still have a future release date, and still be redeemed
// Example: https://steamdb.info/changelist/20505012/
return false;
}

return true;
}

internal bool IsRedeemableApp(SteamApps.PICSProductInfoCallback.PICSProductInfo app, bool ignoreOwnsCheck = false) {
if (UserData == null) {
throw new InvalidOperationException(nameof(UserData));
}
Expand All @@ -104,12 +111,12 @@ internal bool IsRedeemableApp(SteamApps.PICSProductInfoCallback.PICSProductInfo
throw new InvalidOperationException(nameof(Country));
}

// It's impossible to tell for certain if an app is redeemable with the information we have here
// It's impossible to tell for certain if an app is redeemable by this account with the information we have here
// For an app to be redeemable it needs a package that's also redeemable, but we can't see which packages grant an app
// Some examples: Deactivated demo: https://steamdb.info/app/1316010
// App isn't region locked but with package that is: https://steamdb.info/app/2147450

if (OwnedAppIDs.Contains(app.ID)) {
if (!ignoreOwnsCheck && OwnedAppIDs.Contains(app.ID)) {
// Already own this app
return false;
}
Expand Down Expand Up @@ -229,32 +236,56 @@ internal static bool IsFreePackage(SteamApps.PICSProductInfoCallback.PICSProduct
KeyValue kv = package.KeyValues;

var billingType = (EBillingType) kv["billingtype"].AsInteger();
if (billingType != EBillingType.FreeOnDemand
&& billingType != EBillingType.NoCost
) {
return false;
if (billingType == EBillingType.FreeOnDemand || billingType == EBillingType.NoCost) {
return true;
}

var appIDs = kv["appids"].Children.Select(x => x.AsUnsignedInteger());
if (appIDs.Count() == 0) {
return false;
}

internal static bool IsAvailablePackage(SteamApps.PICSProductInfoCallback.PICSProductInfo package) {
KeyValue kv = package.KeyValues;

if (kv["appids"].Children.Count == 0) {
// Package has no apps
return false;
}

if ((EPackageStatus) kv["status"].AsInteger() != EPackageStatus.Available) {
// Package is unavailable
return false;
}

if ((ELicenseType) kv["licensetype"].AsInteger() != ELicenseType.SinglePurchase) {
// Wrong license type
return false;
}

var expiryTime = kv["extended"]["expirytime"].AsUnsignedLong();
var now = DateUtils.DateTimeToUnixTime(DateTime.UtcNow);
if (expiryTime > 0 && expiryTime < now) {
// Package was only available for a limited time and is no longer available
return false;
}

if (kv["extended"]["deactivated_demo"].AsBoolean()) {
// Demo package has been disabled
return false;
}

return true;
}

internal static bool IsAvailablePackageContents(SteamApps.PICSProductInfoCallback.PICSProductInfo package, IEnumerable<SteamApps.PICSProductInfoCallback.PICSProductInfo> apps) {
KeyValue kv = package.KeyValues;

if (kv["appids"].Children.Count != apps.Count()) {
// Could not find all of the apps for this package
return false;
}

if (apps.Any(app => !IsAvailableApp(app))) {
// At least one of the apps in this package isn't available
return false;
}

Expand Down Expand Up @@ -312,6 +343,11 @@ internal bool IsRedeemablePackage(SteamApps.PICSProductInfoCallback.PICSProductI
}
}

if (apps.Any(app => !IsRedeemableApp(app, ignoreOwnsCheck: true))) {
// At least one of the apps in this package isn't redeemable
return false;
}

return true;
}

Expand Down
10 changes: 8 additions & 2 deletions FreePackages/Handlers/PackageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ internal async static Task HandleChanges() {
private async static Task HandleProductInfo(List<SteamApps.PICSProductInfoCallback> productInfo) {
// Figure out which apps are free and add any wanted apps to the queue
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo app in productInfo.SelectMany(static result => result.Apps.Values)) {
if (!PackageFilter.IsFreeApp(app)) {
if (!PackageFilter.IsFreeApp(app) || !PackageFilter.IsAvailableApp(app)) {
Handlers.Values.ToList().ForEach(x => x.BotCache.RemoveChange(appID: app.ID));

continue;
Expand All @@ -168,7 +168,7 @@ private async static Task HandleProductInfo(List<SteamApps.PICSProductInfoCallba
// Need to get the product info of the apps that are contained in each free package in order to apply filters
// This first loop gets a list of these apps and also filters out any non-free packages
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo package in packages) {
if (!PackageFilter.IsFreePackage(package)) {
if (!PackageFilter.IsFreePackage(package) || !PackageFilter.IsAvailablePackage(package)) {
Handlers.Values.ToList().ForEach(x => x.BotCache.RemoveChange(packageID: package.ID));

continue;
Expand All @@ -189,6 +189,12 @@ private async static Task HandleProductInfo(List<SteamApps.PICSProductInfoCallba
KeyValue kv = package.KeyValues;
var childAppIDs = kv["appids"].Children.Select(x => x.AsUnsignedInteger()).ToHashSet<uint>();
var childApps = relatedApps.Where(x => childAppIDs.Contains(x.ID));

if (!PackageFilter.IsAvailablePackageContents(package, childApps)) {
Handlers.Values.ToList().ForEach(x => x.BotCache.RemoveChange(packageID: package.ID));

continue;
}

Handlers.Values.ToList().ForEach(x => x.HandleFreePackage(package, childApps));
}
Expand Down
2 changes: 1 addition & 1 deletion FreePackages/Handlers/PackageQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private async Task<EResult> ClaimFreeApp(uint appID) {
// We could be rate limited, but the app could also be invalid beacause it has no available licenses. It's necessary to assume invalid so we don't get into an infinite loop.
// Examples: https://steamdb.info/app/2401570/ on Oct 2, 2023, Attempting to download demo through Steam client gives error "no licenses"
// Free games that still have store pages but display "At the request of the publisher, ___ is unlisted on the Steam store and will not appear in search.": https://store.steampowered.com/app/376570/WildStar/
Bot.ArchiLogger.LogGenericInfo(string.Format("ID: app/{0} | Status: {1}", appID, EResult.Invalid));
Bot.ArchiLogger.LogGenericInfo(string.Format("ID: app/{0} | Status: Unknown", appID));

return EResult.Invalid;
}
Expand Down

0 comments on commit 6fb51dd

Please sign in to comment.