New type definitions

This commit is contained in:
Kecskeméti László 2024-06-28 16:47:32 +02:00
parent 2b18bd8ae4
commit da9be4735e
12 changed files with 291 additions and 4 deletions

View File

@ -16,6 +16,156 @@ public class JsonTests
_testOutputHelper = testOutputHelper; _testOutputHelper = testOutputHelper;
} }
[Fact]
public void GuildCreateDeserialize()
{
string src = """
{
"op": 0,
"t": "GUILD_CREATE",
"s": 3,
"d":{
"id": "197038439483310086",
"name": "Discord Testers",
"icon": "f64c482b807da4f539cff778d174971c",
"description": "The official place to report Discord Bugs!",
"splash": null,
"discovery_splash": null,
"features": [
"ANIMATED_ICON",
"VERIFIED",
"NEWS",
"VANITY_URL",
"DISCOVERABLE",
"MORE_EMOJI",
"INVITE_SPLASH",
"BANNER",
"COMMUNITY"
],
"emojis": [],
"banner": "9b6439a7de04f1d26af92f84ac9e1e4a",
"owner_id": "73193882359173120",
"application_id": null,
"region": null,
"afk_channel_id": null,
"afk_timeout": 300,
"system_channel_id": null,
"widget_enabled": true,
"widget_channel_id": null,
"verification_level": 3,
"roles": [],
"default_message_notifications": 1,
"mfa_level": 1,
"explicit_content_filter": 2,
"max_presences": 40000,
"max_members": 250000,
"vanity_url_code": "discord-testers",
"premium_tier": 3,
"premium_subscription_count": 33,
"system_channel_flags": 0,
"preferred_locale": "en-US",
"rules_channel_id": "441688182833020939",
"public_updates_channel_id": "281283303326089216",
"safety_alerts_channel_id": "281283303326089216",
"joined_at": "2024-06-27T11:59:36Z",
"large": false,
"member_count": 69,
"voice_states": [
{
"channel_id": "157733188964188161",
"user_id": "80351110224678912",
"session_id": "90326bd25d71d39b9ef95b299e3872ff",
"deaf": false,
"mute": false,
"self_deaf": false,
"self_mute": true,
"suppress": false,
"request_to_speak_timestamp": "2021-03-31T18:45:31.297561+00:00"
}
],
"members":[
{
"user": {
"id": "80351110224678912",
"username": "Nelly",
"discriminator": "1337",
"avatar": "8342729096ea3675442027381ff50dfe",
"verified": true,
"email": "nelly@discord.com",
"flags": 64,
"banner": "06c16474723fe537c283b8efa61a30c8",
"accent_color": 16711680,
"premium_type": 1,
"public_flags": 64,
"avatar_decoration_data": {
"sku_id": "1144058844004233369",
"asset": "a_fed43ab12698df65902ba06727e20c0e"
}
},
"nick": "NOT API SUPPORT",
"avatar": null,
"roles": [],
"joined_at": "2015-04-26T06:26:56.936000+00:00",
"deaf": false,
"mute": false
}
],
"channels":[
{
"id": "41771983423143937",
"guild_id": "197038439483310086",
"name": "general",
"type": 0,
"position": 6,
"permission_overwrites": [],
"rate_limit_per_user": 2,
"nsfw": true,
"topic": "24/7 chat about how to gank Mike #2",
"last_message_id": "155117677105512449",
"parent_id": "399942396007890945",
"default_auto_archive_duration": 60
}
]
}
}
""";
GatewayPacket? gateway_packet = JsonSerializer.Deserialize(src, SourceGenerationContext.Default.GatewayPacket);
Assert.IsType<GuildCreatePacket>(gateway_packet);
Assert.IsType<GuildData>((gateway_packet as GuildCreatePacket)!.Data);
}
[Fact]
public void UnavailableGuildCreateDeserialize()
{
string src = """
{
"op": 0,
"t": "GUILD_CREATE",
"s": 3,
"d":{
"id": "922243411795390566",
"unavailable": true
}
}
""";
GatewayPacket? packet =
JsonSerializer.Deserialize<GatewayPacket>(src, SourceGenerationContext.Default.GatewayPacket);
Assert.IsType<GuildCreatePacket>(packet);
Assert.IsType<UnavailableGuildData>(((GuildCreatePacket)packet).Data);
Assert.True(packet is GuildCreatePacket
{
Data: UnavailableGuildData
{
Id: 922243411795390566,
Unavailable: true
}
});
}
[Fact] [Fact]
public void InvalidSessionDeserialize() public void InvalidSessionDeserialize()
{ {

View File

@ -0,0 +1,20 @@
namespace Discord.API;
public class GuildData : UnavailableGuildData
{
public override bool Unavailable => false;
public string? Name { get; init; }
public ulong? AfkChannelId { get; init; }
public int? AfkTimeout { get; init; }
public RoleData[]? Roles { get; init; }
public ulong? SystemChannelId { get; init; }
public uint? SystemChannelFlags { get; init; }
public string? Description { get; init; }
public int? NsfwLevel { get; init; }
public DateTime? JoinedAt { get; init; }
public bool? Large { get; init; }
public uint? MemberCount { get; init; }
public VoiceStateData[]? VoiceStates { get; init; }
public GuildMemberData[]? Members { get; init; }
public ChannelData[]? Channels { get; init; }
}

View File

@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Discord.API;
internal class GuildDataConverter : JsonConverter<UnavailableGuildData>
{
public override UnavailableGuildData? Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
JsonDocument json_doc = JsonDocument.ParseValue(ref reader);
if (!json_doc.RootElement.TryGetProperty("unavailable", out var unavailable_prop) ||
unavailable_prop.ValueKind == JsonValueKind.False)
{
return json_doc.Deserialize(SourceGenerationContext.Default.GuildData);
}
else
{
if (json_doc.RootElement.TryGetProperty("id", out var id_prop) &&
id_prop.ValueKind is JsonValueKind.String or JsonValueKind.Number)
{
ulong id = id_prop.ValueKind switch
{
JsonValueKind.Number => id_prop.GetUInt64(),
JsonValueKind.String => ulong.Parse(id_prop.GetString() ?? "")
};
return new UnavailableGuildData()
{
Id = id
};
}
else
{
throw new JsonException("Id property not found in guild data");
}
}
}
public override void Write(Utf8JsonWriter writer, UnavailableGuildData value, JsonSerializerOptions options)
{
if (value is GuildData)
{
writer.WriteRawValue(JsonSerializer.SerializeToUtf8Bytes(value, SourceGenerationContext.Default.GuildData));
}
else
{
writer.WriteStartObject();
writer.WriteBoolean("unavailable", true);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace Discord.API;
public class GuildMemberData
{
public UserData? User { get; init; }
public string? Nick { get; init; }
public ulong[]? Roles { get; init; }
public DateTime? JoinedAt { get; init; }
public bool? Deaf { get; init; }
public bool? Mute { get; init; }
//TODO: More fields
}

View File

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace Discord.API;
public class RoleData
{
[JsonRequired]
public ulong Id { get; init; }
public string? Name { get; init; }
public uint? Color { get; init; }
public bool? Hoist { get; init; }
public int? Position { get; init; }
public bool? Managed { get; init; }
public bool? Mentionable { get; init; }
}

View File

@ -2,8 +2,10 @@ using System.Text.Json.Serialization;
namespace Discord.API; namespace Discord.API;
public sealed class UnavailableGuildData [JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(GuildData))]
public class UnavailableGuildData
{ {
public required ulong Id { get; init; } public required ulong Id { get; init; }
public bool Unavailable { get; init; } public virtual bool Unavailable => true;
} }

View File

@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace Discord.API;
public class VoiceStateData
{
public ulong? GuildId { get; init; }
public ulong? ChannelId { get; init; }
[JsonRequired]
public required ulong UserId { get; init; }
public GuildMemberData? Member { get; init; }
public string? SessionId { get; init; }
public bool? Mute { get; init; }
public bool? Deaf { get; init; }
public bool? SelfMute { get; init; }
public bool? SelfDeaf { get; init; }
public bool? SelfStream { get; init; }
public bool? SelfVideo { get; init; }
public bool? Suppress { get; init; }
public DateTime? RequestToSpeakTimestamp { get; init; }
}

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>api</RootNamespace> <RootNamespace>Discord.API</RootNamespace>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -8,6 +8,7 @@ namespace Discord.API;
[JsonDerivedType(typeof(ChannelUpdatePacket))] [JsonDerivedType(typeof(ChannelUpdatePacket))]
[JsonDerivedType(typeof(ChannelDeletePacket))] [JsonDerivedType(typeof(ChannelDeletePacket))]
[JsonDerivedType(typeof(ReadyPacket))] [JsonDerivedType(typeof(ReadyPacket))]
[JsonDerivedType(typeof(GuildCreatePacket))]
internal abstract class DispatchPacket : GatewayPacket internal abstract class DispatchPacket : GatewayPacket
{ {
public override Opcode Op => Opcode.Dispatch; public override Opcode Op => Opcode.Dispatch;

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
namespace Discord.API;
internal class GuildCreatePacket : DispatchPacket
{
public override string Event => "GUILD_CREATE";
[JsonRequired]
[JsonPropertyName("d")]
public required UnavailableGuildData Data { get; init; }
}

View File

@ -52,6 +52,8 @@ internal class GatewayPacketConverter : JsonConverter<GatewayPacket>
SourceGenerationContext.Default.ChannelUpdatePacket), SourceGenerationContext.Default.ChannelUpdatePacket),
"CHANNEL_DELETE" => json_doc.Deserialize( "CHANNEL_DELETE" => json_doc.Deserialize(
SourceGenerationContext.Default.ChannelDeletePacket), SourceGenerationContext.Default.ChannelDeletePacket),
"GUILD_CREATE" => json_doc.Deserialize(
SourceGenerationContext.Default.GuildCreatePacket),
_ => throw new NotSupportedException($"Packet {event_name} is not supported in json deserialization") _ => throw new NotSupportedException($"Packet {event_name} is not supported in json deserialization")
}; };
default: default:

View File

@ -10,7 +10,7 @@ using System.Text.Json;
IgnoreReadOnlyProperties = false, IgnoreReadOnlyProperties = false,
IncludeFields = true, IncludeFields = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower, PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
Converters = [typeof(GatewayPacketConverter)], Converters = [typeof(GatewayPacketConverter), typeof(GuildDataConverter)],
NumberHandling = JsonNumberHandling.AllowReadingFromString NumberHandling = JsonNumberHandling.AllowReadingFromString
)] )]
[JsonSerializable(typeof(GatewayPacket))] [JsonSerializable(typeof(GatewayPacket))]