Files
2026-10-14 06:59:51 +02:00

44 lines
1.9 KiB
C#

using System.Collections.Concurrent;
namespace Campus_live_rewrite.api.server
{
/// <summary>
/// One gift of the same item from the same buyer to the same player per Window (2026-09-27
/// request). The gift button is easy to hit twice, and two taps both passed the "they already
/// have it" check before either box existed - consumables are never checked at all - so the
/// buyer paid twice. The reservation is taken atomically before anything is charged; a line
/// that ends up unbought gives it back.
/// </summary>
public static class GiftGuard
{
public static TimeSpan Window = TimeSpan.FromSeconds(60);
public const string Message = "You just gifted them that - wait a minute before sending it again.";
private static readonly ConcurrentDictionary<string, DateTime> _recent = new();
public static string Key(ulong buyer, ulong recipient, string item) => $"{buyer}>{recipient}:{item.ToLowerInvariant()}";
/// <summary>True when this gift may go ahead (and it is now reserved), false inside the window.</summary>
public static bool TryReserve(string key)
{
var now = DateTime.UtcNow;
if (_recent.Count > 5000)
foreach (var kv in _recent)
if (now - kv.Value >= Window) _recent.TryRemove(kv.Key, out _);
while (true)
{
if (_recent.TryAdd(key, now)) return true;
if (!_recent.TryGetValue(key, out var at)) continue;
if (now - at < Window) return false;
if (_recent.TryUpdate(key, now, at)) return true;
}
}
/// <summary>Give a reservation back (the gift was not bought after all).</summary>
public static void Release(string? key)
{
if (!string.IsNullOrEmpty(key)) _recent.TryRemove(key, out _);
}
}
}