Upgrade API serialisation code to enable potential context-based serialisation
This commit is contained in:
		| @@ -1,6 +1,8 @@ | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| namespace PluralKit.API.Controllers | ||||
| { | ||||
|     [ApiController] | ||||
| @@ -16,12 +18,12 @@ namespace PluralKit.API.Controllers | ||||
|         } | ||||
|  | ||||
|         [HttpGet("{aid}")] | ||||
|         public async Task<ActionResult<PKSystem>> GetSystemByAccount(ulong aid) | ||||
|         public async Task<ActionResult<JObject>> GetSystemByAccount(ulong aid) | ||||
|         { | ||||
|             var system = await _data.GetSystemByAccount(aid); | ||||
|             if (system == null) return NotFound("Account not found."); | ||||
|              | ||||
|             return Ok(system); | ||||
|             return Ok(system.ToJson()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,9 @@ | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using PluralKit.Core; | ||||
|  | ||||
| namespace PluralKit.API.Controllers | ||||
| @@ -20,103 +23,67 @@ namespace PluralKit.API.Controllers | ||||
|         } | ||||
|  | ||||
|         [HttpGet("{hid}")] | ||||
|         public async Task<ActionResult<PKMember>> GetMember(string hid) | ||||
|         public async Task<ActionResult<JObject>> GetMember(string hid) | ||||
|         { | ||||
|             var member = await _data.GetMemberByHid(hid); | ||||
|             if (member == null) return NotFound("Member not found."); | ||||
|  | ||||
|             return Ok(member); | ||||
|             return Ok(member.ToJson()); | ||||
|         } | ||||
|  | ||||
|         [HttpPost] | ||||
|         [RequiresSystem] | ||||
|         public async Task<ActionResult<PKMember>> PostMember([FromBody] PKMember newMember) | ||||
|         public async Task<ActionResult<JObject>> PostMember([FromBody] JObject properties) | ||||
|         { | ||||
|             var system = _auth.CurrentSystem; | ||||
|  | ||||
|             if (newMember.Name == null) | ||||
|                 return BadRequest("Member name cannot be null."); | ||||
|             if (!properties.ContainsKey("name")) | ||||
|                 return BadRequest("Member name must be specified."); | ||||
|  | ||||
|             // Enforce per-system member limit | ||||
|             var memberCount = await _data.GetSystemMemberCount(system); | ||||
|             if (memberCount >= Limits.MaxMemberCount) | ||||
|                 return BadRequest($"Member limit reached ({memberCount} / {Limits.MaxMemberCount})."); | ||||
|  | ||||
|             // Explicit bounds checks | ||||
|             if (newMember.Name != null && newMember.Name.Length > Limits.MaxMemberNameLength) | ||||
|                 return BadRequest($"Member name too long ({newMember.Name.Length} > {Limits.MaxMemberNameLength}."); | ||||
|             if (newMember.DisplayName != null && newMember.DisplayName.Length > Limits.MaxMemberNameLength) | ||||
|                 return BadRequest($"Member display name too long ({newMember.DisplayName.Length} > {Limits.MaxMemberNameLength}."); | ||||
|             if (newMember.Pronouns != null && newMember.Pronouns.Length > Limits.MaxPronounsLength) | ||||
|                 return BadRequest($"Member pronouns too long ({newMember.Pronouns.Length} > {Limits.MaxPronounsLength}."); | ||||
|             if (newMember.Description != null && newMember.Description.Length > Limits.MaxDescriptionLength) | ||||
|                 return BadRequest($"Member descriptions too long ({newMember.Description.Length} > {Limits.MaxDescriptionLength}."); | ||||
|  | ||||
|             // Sanity bounds checks | ||||
|             if (newMember.AvatarUrl != null && newMember.AvatarUrl.Length > 1000) | ||||
|                 return BadRequest(); | ||||
|             if (newMember.ProxyTags?.Any(tag => tag.Prefix.Length > 1000 || tag.Suffix.Length > 1000) ?? false) | ||||
|                 return BadRequest(); | ||||
|  | ||||
|             var member = await _data.CreateMember(system, newMember.Name); | ||||
|  | ||||
|             member.Name = newMember.Name; | ||||
|             member.DisplayName = newMember.DisplayName; | ||||
|             member.Color = newMember.Color; | ||||
|             member.AvatarUrl = newMember.AvatarUrl; | ||||
|             member.Birthday = newMember.Birthday; | ||||
|             member.Pronouns = newMember.Pronouns; | ||||
|             member.Description = newMember.Description; | ||||
|             member.ProxyTags = newMember.ProxyTags; | ||||
|             member.KeepProxy = newMember.KeepProxy; | ||||
|             var member = await _data.CreateMember(system, properties.Value<string>("name")); | ||||
|             try | ||||
|             { | ||||
|                 member.Apply(properties); | ||||
|             } | ||||
|             catch (PKParseError e) | ||||
|             { | ||||
|                 return BadRequest(e.Message); | ||||
|             } | ||||
|              | ||||
|             await _data.SaveMember(member); | ||||
|  | ||||
|             return Ok(member); | ||||
|             return Ok(member.ToJson()); | ||||
|         } | ||||
|  | ||||
|         [HttpPatch("{hid}")] | ||||
|         [RequiresSystem] | ||||
|         public async Task<ActionResult<PKMember>> PatchMember(string hid, [FromBody] PKMember newMember) | ||||
|         public async Task<ActionResult<JObject>> PatchMember(string hid, [FromBody] JObject changes) | ||||
|         { | ||||
|             var member = await _data.GetMemberByHid(hid); | ||||
|             if (member == null) return NotFound("Member not found."); | ||||
|  | ||||
|             if (member.System != _auth.CurrentSystem.Id) return Unauthorized($"Member '{hid}' is not part of your system."); | ||||
|  | ||||
|             if (newMember.Name == null) | ||||
|                 return BadRequest("Member name can not be null."); | ||||
|  | ||||
|             // Explicit bounds checks | ||||
|             if (newMember.Name != null && newMember.Name.Length > Limits.MaxMemberNameLength) | ||||
|                 return BadRequest($"Member name too long ({newMember.Name.Length} > {Limits.MaxMemberNameLength}."); | ||||
|             if (newMember.DisplayName != null && newMember.DisplayName.Length > Limits.MaxMemberNameLength) | ||||
|                 return BadRequest($"Member display name too long ({newMember.DisplayName.Length} > {Limits.MaxMemberNameLength}."); | ||||
|             if (newMember.Pronouns != null && newMember.Pronouns.Length > Limits.MaxPronounsLength) | ||||
|                 return BadRequest($"Member pronouns too long ({newMember.Pronouns.Length} > {Limits.MaxPronounsLength}."); | ||||
|             if (newMember.Description != null && newMember.Description.Length > Limits.MaxDescriptionLength) | ||||
|                 return BadRequest($"Member descriptions too long ({newMember.Description.Length} > {Limits.MaxDescriptionLength}."); | ||||
|  | ||||
|             // Sanity bounds checks | ||||
|             if (newMember.ProxyTags?.Any(tag => (tag.Prefix?.Length ?? 0) > 1000 || (tag.Suffix?.Length ?? 0) > 1000) ?? false) | ||||
|                 return BadRequest(); | ||||
|  | ||||
|             member.Name = newMember.Name; | ||||
|             member.DisplayName = newMember.DisplayName.NullIfEmpty(); | ||||
|             member.Color = newMember.Color.NullIfEmpty(); | ||||
|             member.AvatarUrl = newMember.AvatarUrl.NullIfEmpty(); | ||||
|             member.Birthday = newMember.Birthday; | ||||
|             member.Pronouns = newMember.Pronouns.NullIfEmpty(); | ||||
|             member.Description = newMember.Description.NullIfEmpty(); | ||||
|             member.ProxyTags = newMember.ProxyTags; | ||||
|             member.KeepProxy = newMember.KeepProxy; | ||||
|             try | ||||
|             { | ||||
|                 member.Apply(changes); | ||||
|             } | ||||
|             catch (PKParseError e) | ||||
|             { | ||||
|                 return BadRequest(e.Message); | ||||
|             } | ||||
|              | ||||
|             await _data.SaveMember(member); | ||||
|  | ||||
|             return Ok(member); | ||||
|             return Ok(member.ToJson()); | ||||
|         } | ||||
|          | ||||
|         [HttpDelete("{hid}")] | ||||
|         [RequiresSystem] | ||||
|         public async Task<ActionResult<PKMember>> DeleteMember(string hid) | ||||
|         public async Task<ActionResult> DeleteMember(string hid) | ||||
|         { | ||||
|             var member = await _data.GetMemberByHid(hid); | ||||
|             if (member == null) return NotFound("Member not found."); | ||||
| @@ -124,7 +91,6 @@ namespace PluralKit.API.Controllers | ||||
|             if (member.System != _auth.CurrentSystem.Id) return Unauthorized($"Member '{hid}' is not part of your system."); | ||||
|              | ||||
|             await _data.DeleteMember(member); | ||||
|  | ||||
|             return Ok(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using NodaTime; | ||||
|  | ||||
| namespace PluralKit.API.Controllers | ||||
| @@ -13,8 +15,8 @@ namespace PluralKit.API.Controllers | ||||
|         [JsonProperty("sender")] public string Sender; | ||||
|         [JsonProperty("channel")] public string Channel; | ||||
|  | ||||
|         [JsonProperty("system")] public PKSystem System; | ||||
|         [JsonProperty("member")] public PKMember Member; | ||||
|         [JsonProperty("system")] public JObject System; | ||||
|         [JsonProperty("member")] public JObject Member; | ||||
|     } | ||||
|      | ||||
|     [ApiController] | ||||
| @@ -41,8 +43,8 @@ namespace PluralKit.API.Controllers | ||||
|                 Id = msg.Message.Mid.ToString(), | ||||
|                 Channel = msg.Message.Channel.ToString(), | ||||
|                 Sender = msg.Message.Sender.ToString(), | ||||
|                 Member = msg.Member, | ||||
|                 System = msg.System, | ||||
|                 Member = msg.Member.ToJson(), | ||||
|                 System = msg.System.ToJson(), | ||||
|                 Original = msg.Message.OriginalMid?.ToString() | ||||
|             }; | ||||
|         }  | ||||
|   | ||||
| @@ -4,6 +4,8 @@ using System.Threading.Tasks; | ||||
| using Dapper; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using NodaTime; | ||||
| using PluralKit.Core; | ||||
|  | ||||
| @@ -18,7 +20,7 @@ namespace PluralKit.API.Controllers | ||||
|     public struct FrontersReturn | ||||
|     { | ||||
|         [JsonProperty("timestamp")] public Instant Timestamp { get; set; } | ||||
|         [JsonProperty("members")] public IEnumerable<PKMember> Members { get; set; } | ||||
|         [JsonProperty("members")] public IEnumerable<JObject> Members { get; set; } | ||||
|     } | ||||
|  | ||||
|     public struct PostSwitchParams | ||||
| @@ -44,27 +46,27 @@ namespace PluralKit.API.Controllers | ||||
|  | ||||
|         [HttpGet] | ||||
|         [RequiresSystem] | ||||
|         public Task<ActionResult<PKSystem>> GetOwnSystem() | ||||
|         public Task<ActionResult<JObject>> GetOwnSystem() | ||||
|         { | ||||
|             return Task.FromResult<ActionResult<PKSystem>>(Ok(_auth.CurrentSystem)); | ||||
|             return Task.FromResult<ActionResult<JObject>>(Ok(_auth.CurrentSystem.ToJson())); | ||||
|         } | ||||
|  | ||||
|         [HttpGet("{hid}")] | ||||
|         public async Task<ActionResult<PKSystem>> GetSystem(string hid) | ||||
|         public async Task<ActionResult<JObject>> GetSystem(string hid) | ||||
|         { | ||||
|             var system = await _data.GetSystemByHid(hid); | ||||
|             if (system == null) return NotFound("System not found."); | ||||
|             return Ok(system); | ||||
|             return Ok(system.ToJson()); | ||||
|         } | ||||
|  | ||||
|         [HttpGet("{hid}/members")] | ||||
|         public async Task<ActionResult<IEnumerable<PKMember>>> GetMembers(string hid) | ||||
|         public async Task<ActionResult<IEnumerable<JObject>>> GetMembers(string hid) | ||||
|         { | ||||
|             var system = await _data.GetSystemByHid(hid); | ||||
|             if (system == null) return NotFound("System not found."); | ||||
|  | ||||
|             var members = await _data.GetSystemMembers(system); | ||||
|             return Ok(members); | ||||
|             return Ok(members.Select(m => m.ToJson())); | ||||
|         } | ||||
|  | ||||
|         [HttpGet("{hid}/switches")] | ||||
| @@ -102,32 +104,27 @@ namespace PluralKit.API.Controllers | ||||
|             return Ok(new FrontersReturn | ||||
|             { | ||||
|                 Timestamp = sw.Timestamp, | ||||
|                 Members = members | ||||
|                 Members = members.Select(m => m.ToJson()) | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [HttpPatch] | ||||
|         [RequiresSystem] | ||||
|         public async Task<ActionResult<PKSystem>> EditSystem([FromBody] PKSystem newSystem) | ||||
|         public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes) | ||||
|         { | ||||
|             var system = _auth.CurrentSystem; | ||||
|              | ||||
|             // Bounds checks | ||||
|             if (newSystem.Name != null && newSystem.Name.Length > Limits.MaxSystemNameLength) | ||||
|                 return BadRequest($"System name too long ({newSystem.Name.Length} > {Limits.MaxSystemNameLength}."); | ||||
|             if (newSystem.Tag != null && newSystem.Tag.Length > Limits.MaxSystemTagLength) | ||||
|                 return BadRequest($"System tag too long ({newSystem.Tag.Length} > {Limits.MaxSystemTagLength}."); | ||||
|             if (newSystem.Description != null && newSystem.Description.Length > Limits.MaxDescriptionLength) | ||||
|                 return BadRequest($"System description too long ({newSystem.Description.Length} > {Limits.MaxDescriptionLength}."); | ||||
|  | ||||
|             system.Name = newSystem.Name.NullIfEmpty(); | ||||
|             system.Description = newSystem.Description.NullIfEmpty(); | ||||
|             system.Tag = newSystem.Tag.NullIfEmpty(); | ||||
|             system.AvatarUrl = newSystem.AvatarUrl.NullIfEmpty(); | ||||
|             system.UiTz = newSystem.UiTz ?? "UTC"; | ||||
|              | ||||
|             try | ||||
|             { | ||||
|                 system.Apply(changes); | ||||
|             } | ||||
|             catch (PKParseError e) | ||||
|             { | ||||
|                 return BadRequest(e.Message); | ||||
|             } | ||||
|  | ||||
|             await _data.SaveSystem(system); | ||||
|             return Ok(system); | ||||
|             return Ok(system.ToJson()); | ||||
|         } | ||||
|  | ||||
|         [HttpPost("switches")] | ||||
|   | ||||
| @@ -22,8 +22,7 @@ namespace PluralKit.API | ||||
|             services.AddCors(); | ||||
|             services.AddControllers() | ||||
|                 .SetCompatibilityVersion(CompatibilityVersion.Latest) | ||||
|                 .AddNewtonsoftJson(); | ||||
|                 // .AddJsonOptions(opts => { opts.SerializerSettings.BuildSerializerSettings(); }); | ||||
|                 .AddNewtonsoftJson(); // sorry MS, this just does *more* | ||||
|  | ||||
|             services | ||||
|                 .AddTransient<IDataStore, PostgresDataStore>() | ||||
|   | ||||
| @@ -1,15 +1,23 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text.Json; | ||||
|  | ||||
| using Dapper.Contrib.Extensions; | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using NodaTime; | ||||
| using NodaTime.Text; | ||||
|  | ||||
| using PluralKit.Core; | ||||
|  | ||||
| namespace PluralKit | ||||
| { | ||||
|     public class PKParseError: Exception | ||||
|     { | ||||
|         public PKParseError(string message): base(message) { } | ||||
|     } | ||||
|      | ||||
|     public struct ProxyTag | ||||
|     { | ||||
|         public ProxyTag(string prefix, string suffix) | ||||
| @@ -52,16 +60,26 @@ namespace PluralKit | ||||
|         [JsonProperty("tz")] public string UiTz { get; set; } | ||||
|         [JsonIgnore] public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz); | ||||
|  | ||||
|         public void ToJson(System.Text.Json.Utf8JsonWriter w) | ||||
|         public JObject ToJson() | ||||
|         { | ||||
|             w.WriteStartObject(); | ||||
|             w.WriteString("id", Hid); | ||||
|             w.WriteString("description", Description); | ||||
|             w.WriteString("tag", Tag); | ||||
|             w.WriteString("avatar_url", AvatarUrl); | ||||
|             w.WriteString("created", Formats.TimestampExportFormat.Format(Created)); | ||||
|             w.WriteString("tz", UiTz); | ||||
|             w.WriteEndObject(); | ||||
|             var o = new JObject(); | ||||
|             o.Add("id", Hid); | ||||
|             o.Add("name", Name); | ||||
|             o.Add("description", Description); | ||||
|             o.Add("tag", Tag); | ||||
|             o.Add("avatar_url", AvatarUrl); | ||||
|             o.Add("created", Formats.TimestampExportFormat.Format(Created)); | ||||
|             o.Add("tz", UiTz); | ||||
|             return o; | ||||
|         } | ||||
|  | ||||
|         public void Apply(JObject o) | ||||
|         { | ||||
|             if (o.ContainsKey("name")) Name = o.Value<string>("name").NullIfEmpty().BoundsCheck(Limits.MaxSystemNameLength, "System name"); | ||||
|             if (o.ContainsKey("description")) Description = o.Value<string>("description").NullIfEmpty().BoundsCheck(Limits.MaxDescriptionLength, "System description"); | ||||
|             if (o.ContainsKey("tag")) Tag = o.Value<string>("tag").NullIfEmpty().BoundsCheck(Limits.MaxSystemTagLength, "System tag"); | ||||
|             if (o.ContainsKey("avatar_url")) AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty(); | ||||
|             if (o.ContainsKey("tz")) UiTz = o.Value<string>("tz") ?? "UTC"; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -81,20 +99,6 @@ namespace PluralKit | ||||
|         [JsonProperty("proxy_tags")] public ICollection<ProxyTag> ProxyTags { get; set; } | ||||
|         [JsonProperty("keep_proxy")] public bool KeepProxy { get; set; } | ||||
|         [JsonProperty("created")] public Instant Created { get; set; } | ||||
|          | ||||
|         // These are deprecated as fuck, and are kinda hacky | ||||
|         // Don't use, unless you're the API's serialization library | ||||
|         [JsonProperty("prefix")] [Obsolete("Use PKMember.ProxyTags")] public string Prefix | ||||
|         { | ||||
|             get => ProxyTags?.FirstOrDefault().Prefix; | ||||
|             set => ProxyTags = new[] {new ProxyTag(Prefix, value)}; | ||||
|         } | ||||
|  | ||||
|         [JsonProperty("suffix")] [Obsolete("Use PKMember.ProxyTags")] public string Suffix | ||||
|         { | ||||
|             get => ProxyTags?.FirstOrDefault().Suffix; | ||||
|             set => ProxyTags = new[] {new ProxyTag(Suffix, value)}; | ||||
|         } | ||||
|  | ||||
|         /// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" is hidden | ||||
|         [JsonIgnore] public string BirthdayString | ||||
| @@ -116,36 +120,64 @@ namespace PluralKit | ||||
|             return $"{guildDisplayName ?? DisplayName ?? Name} {systemTag}"; | ||||
|         } | ||||
|  | ||||
|         public void ToJson(Utf8JsonWriter w) | ||||
|         public JObject ToJson() | ||||
|         { | ||||
|             w.WriteStartObject(); | ||||
|             w.WriteString("id", Hid); | ||||
|             w.WriteString("name", Name); | ||||
|             w.WriteString("color", Color); | ||||
|             w.WriteString("display_name", DisplayName); | ||||
|             w.WriteString("birthday", Birthday.HasValue ? Formats.DateExportFormat.Format(Birthday.Value) : null); | ||||
|             w.WriteString("pronouns", Pronouns); | ||||
|             w.WriteString("description", Description); | ||||
|             w.WriteStartArray("proxy_tags"); | ||||
|             foreach (var tag in ProxyTags) | ||||
|             { | ||||
|                 w.WriteStartObject(); | ||||
|                 w.WriteString("prefix", tag.Prefix); | ||||
|                 w.WriteString("suffix", tag.Suffix); | ||||
|                 w.WriteEndObject(); | ||||
|             } | ||||
|             w.WriteEndArray(); | ||||
|             w.WriteBoolean("keep_proxy", KeepProxy); | ||||
|             w.WriteString("created", Formats.TimestampExportFormat.Format(Created)); | ||||
|             var o = new JObject(); | ||||
|             o.Add("id", Hid); | ||||
|             o.Add("name", Name); | ||||
|             o.Add("color", Color); | ||||
|             o.Add("display_name", DisplayName); | ||||
|             o.Add("birthday", Birthday.HasValue ? Formats.DateExportFormat.Format(Birthday.Value) : null); | ||||
|             o.Add("pronouns", Pronouns); | ||||
|             o.Add("description", Description); | ||||
|              | ||||
|             var tagArray = new JArray(); | ||||
|             foreach (var tag in ProxyTags)  | ||||
|                 tagArray.Add(new JObject {{"prefix", tag.Prefix}, {"suffix", tag.Suffix}}); | ||||
|             o.Add("proxy_tags", tagArray); | ||||
|  | ||||
|             o.Add("keep_proxy", KeepProxy); | ||||
|             o.Add("created", Formats.TimestampExportFormat.Format(Created)); | ||||
|  | ||||
|             if (ProxyTags.Count > 0) | ||||
|             { | ||||
|                 // Legacy compatibility only, TODO: remove at some point | ||||
|                 w.WriteString("prefix", ProxyTags?.FirstOrDefault().Prefix); | ||||
|                 w.WriteString("suffix", ProxyTags?.FirstOrDefault().Suffix); | ||||
|                 o.Add("prefix", ProxyTags?.FirstOrDefault().Prefix); | ||||
|                 o.Add("suffix", ProxyTags?.FirstOrDefault().Suffix); | ||||
|             } | ||||
|  | ||||
|             w.WriteEndObject(); | ||||
|             return o; | ||||
|         } | ||||
|  | ||||
|         public void Apply(JObject o) | ||||
|         { | ||||
|             if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null)  | ||||
|                 throw new PKParseError("Member name can not be set to null."); | ||||
|              | ||||
|             if (o.ContainsKey("name")) Name = o.Value<string>("name").BoundsCheck(Limits.MaxMemberNameLength, "Member name"); | ||||
|             if (o.ContainsKey("color")) Color = o.Value<string>("color").NullIfEmpty(); | ||||
|             if (o.ContainsKey("display_name")) DisplayName = o.Value<string>("display_name").NullIfEmpty().BoundsCheck(Limits.MaxMemberNameLength, "Member display name"); | ||||
|             if (o.ContainsKey("birthday")) | ||||
|             { | ||||
|                 var str = o.Value<string>("birthday").NullIfEmpty(); | ||||
|                 var res = Formats.DateExportFormat.Parse(str); | ||||
|                 if (res.Success) Birthday = res.Value; | ||||
|                 else if (str == null) Birthday = null; | ||||
|                 else throw new PKParseError("Could not parse member birthday."); | ||||
|             } | ||||
|  | ||||
|             if (o.ContainsKey("pronouns")) Pronouns = o.Value<string>("pronouns").NullIfEmpty().BoundsCheck(Limits.MaxPronounsLength, "Member pronouns"); | ||||
|             if (o.ContainsKey("description")) Description = o.Value<string>("description").NullIfEmpty().BoundsCheck(Limits.MaxDescriptionLength, "Member descriptoin"); | ||||
|             if (o.ContainsKey("keep_proxy")) KeepProxy = o.Value<bool>("keep_proxy"); | ||||
|  | ||||
|             if (o.ContainsKey("prefix") || o.ContainsKey("suffix") && !o.ContainsKey("proxy_tags")) | ||||
|                 ProxyTags = new[] {new ProxyTag(o.Value<string>("prefix"), o.Value<string>("suffix"))}; | ||||
|             else if (o.ContainsKey("proxy_tags")) | ||||
|             { | ||||
|                 ProxyTags = o.Value<JArray>("proxy_tags") | ||||
|                     .OfType<JObject>().Select(o => new ProxyTag(o.Value<string>("prefix"), o.Value<string>("suffix"))) | ||||
|                     .ToList(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -253,6 +253,13 @@ namespace PluralKit | ||||
|             if (input.Trim().Length == 0) return null; | ||||
|             return input; | ||||
|         } | ||||
|  | ||||
|         public static string BoundsCheck(this string input, int maxLength, string nameInError) | ||||
|         { | ||||
|             if (input != null && input.Length > maxLength) | ||||
|                 throw new PKParseError($"{nameInError} too long ({input.Length} > {maxLength})."); | ||||
|             return input; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static class Emojis { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user