Files
sbox-public/engine/Sandbox.Services/Api/Models/PackageFind.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

297 lines
6.0 KiB
C#

namespace Sandbox.Services;
public struct FindPackageQuery
{
public FindPackageQuery()
{
}
/// <summary>
/// Which asset types to find - or null/empty for all
/// </summary>
public string Type;
/// <summary>
/// How to sort the list
/// </summary>
public SortMode Sort;
/// <summary>
/// SteamId of querying user (should be authenticated!)
/// </summary>
public long SteamId;
/// <summary>
/// Get the favourites of this user
/// </summary>
public long FavouritesSteamId;
/// <summary>
/// Only return packages in this collection
/// </summary>
public string InCollection;
/// <summary>
/// True if we want the total
/// </summary>
public bool GetTotalCount;
/// <summary>
/// True if we want facets
/// </summary>
public bool GetFacets;
/// <summary>
/// primary asset should match this value
/// </summary>
public string PrimaryAsset;
/// <summary>
/// Text search in description, title, summary etc
/// </summary>
public string SearchString;
/// <summary>
/// References this package id
/// </summary>
public long WithReference;
/// <summary>
/// Org name
/// </summary>
public string Org;
/// <summary>
/// Must contain all tags
/// </summary>
public List<string> WithTag;
/// <summary>
/// In contest
/// </summary>
public string InContest;
/// <summary>
/// Content created for this game, specifically
/// </summary>
public string ForGame;
/// <summary>
/// Show hidden packages in this orgs
/// </summary>
public long[] OpenOrgs;
/// <summary>
/// If we want to find counter-strike maps, this would be set to the name of the counter-strike package
/// </summary>
public string TargetPackage;
/// <summary>
/// Only show packages that we haven't played
/// </summary>
public bool Unplayed;
/// <summary>
/// Facets like Category:Wall
/// </summary>
public Dictionary<string, string> Facets;
public enum SortMode
{
Updated,
Popular,
Friends,
Created,
Random,
Trending,
ThumbsUp,
ThumbsDown,
Favourites,
InCollections,
/// <summary>
/// Order by recently used
/// </summary>
Used,
RankDay,
RankWeek,
RankMonth,
Spawns,
SpawnsDay,
SpawnsWeek,
SpawnsMonth,
PlayersNow,
/// <summary>
/// No sorting
/// </summary>
None
}
public static FindPackageQuery Parse( string query )
{
if ( string.IsNullOrWhiteSpace( query ) )
return default;
var find = new FindPackageQuery
{
GetTotalCount = true,
GetFacets = true
};
var tokens = query.ToLowerInvariant().Split( ' ', StringSplitOptions.RemoveEmptyEntries );
var searchWords = new List<string>();
var tags = new HashSet<string>();
foreach ( var token in tokens )
{
// Skip pipe-separated tokens
if ( token.Contains( '|' ) ) continue;
// Handle tag prefixes
if ( token.StartsWith( '+' ) )
{
tags.Add( token[1..] );
continue;
}
// Skip excluded tags (not currently used in output)
if ( token.StartsWith( '-' ) ) continue;
// Handle key:value pairs
var colonIndex = token.IndexOf( ':' );
if ( colonIndex > 0 && colonIndex < token.Length - 1 )
{
var key = token[..colonIndex];
var value = token[(colonIndex + 1)..].Trim();
if ( !ProcessToken( ref find, key, value ) )
{
// Unknown tokens become facets
find.Facets ??= new();
find.Facets[key] = value;
}
}
else
{
// Regular search word
searchWords.Add( token );
}
}
// Finalize search string and tags
find.SearchString = string.Join( ' ', searchWords );
ProcessTags( ref find, tags );
return find;
}
private static bool ProcessToken( ref FindPackageQuery find, string key, string value )
{
switch ( key )
{
case "type":
find.Type = value;
return true;
case "sort":
if ( value == "favourite" )
{
find.FavouritesSteamId = find.SteamId;
find.Sort = SortMode.None;
}
else
{
find.Sort = ParseSortMode( value );
}
return true;
case "asset":
find.PrimaryAsset = value.ToLower();
return true;
case "contest":
find.InContest = value;
return true;
case "in":
find.InCollection = value;
return true;
case "target":
find.TargetPackage = value;
return true;
case "org":
find.Org = value;
return true;
case "is":
if ( value == "unplayed" )
find.Unplayed = true;
// Note: "owner" case was tracked but never used in original
return true;
case "api":
// API version was parsed but never used in original
return true;
default:
return false; // Unknown token, will be treated as facet
}
}
private static void ProcessTags( ref FindPackageQuery find, HashSet<string> tags )
{
if ( tags.Count == 0 ) return;
// Extract and process game tags
var gameTag = tags.FirstOrDefault( t => t.StartsWith( "game:" ) );
if ( gameTag != null )
{
var game = gameTag[5..];
find.ForGame = game == "any" ? null : game;
tags.Remove( gameTag );
}
// Set remaining tags
if ( tags.Count > 0 )
{
find.WithTag = tags.ToList();
}
}
private static SortMode ParseSortMode( string sort )
{
return sort switch
{
"live" or "referenced" or "referencing" or "user" or "used" or "played" => SortMode.Used,
"oldest" => SortMode.Created, // TODO: might need reverse order flag
"newest" => SortMode.Created,
"upvotes" => SortMode.ThumbsUp,
"downvotes" => SortMode.ThumbsDown,
"favourite" or "favorites" or "favourites" => SortMode.Favourites,
"collections" => SortMode.InCollections,
"friends" => SortMode.Friends,
"random" => SortMode.Random,
"popular" => SortMode.Popular,
"updated" => SortMode.Updated,
"trending" => SortMode.Trending,
"rankd" or "rankday" => SortMode.RankDay,
"rankw" or "rankweek" => SortMode.RankWeek,
"rankm" or "rankmonth" => SortMode.RankMonth,
"spawns" => SortMode.Spawns,
"spawnsday" => SortMode.SpawnsDay,
"spawnsweek" => SortMode.SpawnsWeek,
"spawnsmonth" => SortMode.SpawnsMonth,
"playersnow" => SortMode.PlayersNow,
_ => SortMode.Popular // Default
};
}
}