using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Floatplane.Api.Models;
/// Image with optional pre-scaled variants (pbs.floatplane.com, public).
public class ImageModel
{
public int Width { get; set; }
public int Height { get; set; }
public string Path { get; set; } = string.Empty;
public List ChildImages { get; set; } = new();
/// Largest variant whose width does not exceed , else the original.
public string BestPath(int maxWidth = 1200)
{
var candidates = ChildImages.Where(c => c.Width <= maxWidth && !string.IsNullOrEmpty(c.Path)).OrderByDescending(c => c.Width).ToList();
if (candidates.Count > 0)
{
return candidates[0].Path;
}
return Path;
}
}
/// Error envelope Floatplane returns for 4xx responses.
public class ApiErrorModel
{
public string? Id { get; set; }
public List Errors { get; set; } = new();
public string? Message { get; set; }
public bool Has(string name) => Errors.Any(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase));
}
public class ApiErrorItem
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Message { get; set; }
}
///
/// Floatplane sometimes sends a related object inline and sometimes only its id
/// (e.g. channel on a post, videoAttachments in list responses).
/// This converter accepts both: a string becomes { Id = string }.
///
/// Model type with an Id property.
public class IdOrObjectConverter : JsonConverter
where T : class, IHasId, new()
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (reader.TokenType == JsonTokenType.String)
{
return new T { Id = reader.GetString() ?? string.Empty, IsReferenceOnly = true };
}
// Avoid infinite recursion: deserialise with a copy of the options minus this converter.
var inner = new JsonSerializerOptions(options);
var self = inner.Converters.FirstOrDefault(c => c is IdOrObjectConverter);
if (self != null)
{
inner.Converters.Remove(self);
}
return JsonSerializer.Deserialize(ref reader, inner);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var inner = new JsonSerializerOptions(options);
var self = inner.Converters.FirstOrDefault(c => c is IdOrObjectConverter);
if (self != null)
{
inner.Converters.Remove(self);
}
JsonSerializer.Serialize(writer, value, inner);
}
}
public interface IHasId
{
string Id { get; set; }
/// True when only the id was present in the JSON.
bool IsReferenceOnly { get; set; }
}
/// Shared serializer options for Floatplane JSON.
public static class FloatplaneJson
{
public static JsonSerializerOptions Options { get; } = Create();
private static JsonSerializerOptions Create()
{
var o = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
o.Converters.Add(new IdOrObjectConverter());
o.Converters.Add(new IdOrObjectConverter());
o.Converters.Add(new IdOrObjectConverter());
o.Converters.Add(new JsonStringEnumConverter());
return o;
}
}