Move time placeholder impl to TryGetCleanCdnUrl, drop divmod in favor of fixed options

This commit is contained in:
Una Kearney 2025-12-05 19:35:33 -05:00
parent 3a3f42d755
commit 776b562ce7
No known key found for this signature in database
2 changed files with 25 additions and 21 deletions

View file

@ -15,10 +15,6 @@ public static class AvatarUtils
private static readonly string DiscordMediaUrlReplacement =
"https://media.discordapp.net/attachments/$1/$2/$3.$4?width=256&height=256";
// Rewrite time "cachebuster" parameters for randomly generated/chosen avatars with custom URLs.
// Number match uses `[1-9][0-9]{0,6}` rather than `[0-9]+` to avoid needing to deal with special cases for zero and limit to reasonable numbers.
private static readonly Regex TimePlaceholder = new(@"\{time(?:/(?<divisor>[1-9][0-9]{0,6}))?(?:%(?<modulus>[1-9][0-9]{0,6}))?\}", RegexOptions.IgnoreCase);
public static string? TryRewriteCdnUrl(string? url)
{
if (url == null)
@ -29,24 +25,8 @@ public static class AvatarUtils
if (match.Groups["query"].Success)
newUrl += "&" + match.Groups["query"].Value;
newUrl = TimePlaceholder.Replace(newUrl, ProcessTimePlaceholder);
return newUrl;
}
private static string? ProcessTimePlaceholder(Match m) {
// Minutes are the maximum accuracy to avoid too much cache thrashing
// AND with the maximum positive value so it's always positive (as if this code will exist long enough for the 64-bit signed unix time to go negative...)
var time = (DateTimeOffset.UtcNow.ToUnixTimeSeconds()/60)&Int64.MaxValue;
if (m.Groups["divisor"].Success)
time /= Int32.Parse(m.Groups["divisor"].Value); // as above - guaranteed to not throw and be > 0
if (m.Groups["modulus"].Success)
time %= Int32.Parse(m.Groups["modulus"].Value);
return time.ToString();
}
public static bool IsDiscordCdnUrl(string? url) => url != null && DiscordCdnUrl.Match(url).Success;
}

View file

@ -4,6 +4,7 @@ namespace PluralKit.Core;
public static class MiscUtils
{
// discord mediaproxy URLs used to be stored directly in the database, so now we cleanup image urls before using them outside of proxying
private static readonly Regex MediaProxyUrl =
new(
@ -11,6 +12,10 @@ public static class MiscUtils
private static readonly string DiscordCdnReplacement = "https://cdn.discordapp.com/attachments/$1/$2/$3.$4";
// Rewrite time "cachebuster" parameters for randomly generated/chosen avatars with custom URLs.
private static readonly Regex TimePlaceholder = new(@"\{(time(?:stamp|_(?:1m|5m|30m|1h|6h|1d)))\}");
private const Int64 TimeAccuracy = 60;
public static bool TryMatchUri(string input, out Uri uri)
{
if (input.StartsWith('<') && input.EndsWith('>'))
@ -32,5 +37,24 @@ public static class MiscUtils
}
public static string? TryGetCleanCdnUrl(this string? url) =>
url == null ? null : MediaProxyUrl.Replace(url, DiscordCdnReplacement);
url == null ? null : TimePlaceholder.Replace(MediaProxyUrl.Replace(url, DiscordCdnReplacement), ProcessTimePlaceholder);
private static string? ProcessTimePlaceholder(Match m) {
// Limit maximum accuracy to avoid too much cache thrashing, multiply for standard-ish Unix time
// AND with the maximum positive value so it's always positive (as if this code will exist long enough for the 64-bit signed unix time to go negative...)
var time = ((DateTimeOffset.UtcNow.ToUnixTimeSeconds()/TimeAccuracy)*TimeAccuracy)&Int64.MaxValue;
switch (m.Groups[1].Value) {
case "timestamp": break;
case "time_1m": time /= 60; break;
case "time_5m": time /= 60*5; break;
case "time_30m": time /= 60*30; break;
case "time_1h": time /= 60*60; break;
case "time_6h": time /= 6*60*60; break;
case "time_1d": time /= 24*60*60; break;
}
return time.ToString();
}
}