Utils/Flickr.cs
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;

namespace CryptidHunt;

public static partial class Flickr
{
	public static readonly string[] LocalFallbacks =
	{
		"ui/fallbacks/pig.jpg",
		"ui/fallbacks/jerry.jpg",
		"ui/fallbacks/leha.png",
		"ui/fallbacks/seedy.jpg",
		"ui/fallbacks/snowball1.jpg",
		"ui/fallbacks/snowball2.png",
	};

	public static readonly string[] TagStopList =
	{
		"nsfw",
		"erotic",
		"sex",
		"nude",
		"naked",
		"porn",
		"gore",
		"dick",
		"penis",
		"pussy",
		"webcam",
		"masturb", // includes "masturbation"
		"fetish",
		"kink",
		"mature",
		"erection",
		"bdsm",
		"boobs",
		"tits"
	};

	public static readonly int ResponsePrefixLength = "jsonFlickrFeed(".Length;
	public static readonly int ResponsePostfixLength = ")".Length;

	/// <summary>
	/// Time of "user patience" in seconds
	/// </summary>
	public static readonly float ImageLoadTimeout = 5.0f;

	class FlickPublicFeed
	{
		public class Item
		{
			[JsonPropertyName( "media" )]
			public Dictionary<string, string> Media { get; set; }
			[JsonPropertyName( "tags" )]
			public string Tags { get; set; }
		}

		[JsonPropertyName( "items" )]
		public Item[] Items { get; set; }
	}

	private static async Task<string> GetUrl( string fear )
	{
		fear = Regex.Replace( fear.Trim(), @"\s+", " " );
		// rndtrash: feeds accept a comma separated list of tags. Comment the next line if the results are actually worse.
		fear = fear.Replace( " ", "," );
		var safeFear = HttpUtility.UrlEncode( fear );

		string jsonString;
		try
		{
			jsonString = await Http.RequestStringAsync( $"https://www.flickr.com/services/feeds/photos_public.gne?format=json&tags={safeFear}" );
		}
		catch ( Exception ex )
		{
			Log.Error( $"Got an exception while reaching out to Flickr API! ({ex})" );
			return null;
		}
		jsonString = jsonString.Substring( ResponsePrefixLength, jsonString.Length - ResponsePrefixLength - ResponsePostfixLength );

		FlickPublicFeed feed;
		try
		{
			feed = Json.Deserialize<FlickPublicFeed>( jsonString );
		}
		catch ( Exception ex )
		{
			Log.Error( $"Got an exception while parsing Flickr JSON! ({ex}, the JSON in question: {jsonString})" );
			return null;
		}

		var feedItems = feed.Items.Where( item => !TagStopList.Any( tag => item.Tags.Contains( tag ) ) ).ToList();
		if ( feedItems.Count == 0 )
		{
			// Oh. It's all porn. Bailing out!
			return null;
		}

		var item = Random.Shared.FromList( feedItems );
		var url = item.Media.FirstOrDefault().Value;
		Log.Info( $"Chose one with the following tags: {item.Tags} ({url})" );
		return url;
	}

	private static async Task<Texture> GetFallback() => await Texture.LoadFromFileSystemAsync(
		LocalFallbacks[Random.Shared.Int( 0, LocalFallbacks.Length - 1 )], FileSystem.Mounted );

	public static async Task<Texture> Get( string fear )
	{
		var url = await GetUrl( fear );
		if ( url == null ) // got an empty url
			return await GetFallback();

		var image = Texture.Load( url );
		TimeSince imageStartedLoading = 0;
		while ( !image.IsLoaded )
		{
			await Task.Delay( 10 );

			if ( imageStartedLoading >= ImageLoadTimeout )
			{
				// fuck it
				image.Dispose();
				return await GetFallback();
			}
		}

		return image;
	}

	[ConCmd( "debug_get_fear" )]
	public static void DebugGetFear( string fear )
	{
		Log.Info( $"{GetUrl( fear ).Result}" );
	}
}