diff --git a/src/core.zig b/src/core.zig index 4054696..e44ee14 100644 --- a/src/core.zig +++ b/src/core.zig @@ -1,4 +1,5 @@ const Intents = @import("./structures/types.zig").Intents; +const Snowflake = @import("./structures/snowflake.zig").Snowflake; const GatewayBotInfo = @import("internal.zig").GatewayBotInfo; const IdentifyProperties = @import("internal.zig").IdentifyProperties; const ShardDetails = @import("internal.zig").ShardDetails; @@ -13,13 +14,13 @@ const debug = @import("internal.zig").debug; pub const discord_epoch = 1420070400000; /// Calculate and return the shard ID for a given guild ID -pub inline fn calculateShardId(guild_id: u64, shards: ?usize) u64 { - return (guild_id >> 22) % shards orelse 1; +pub inline fn calculateShardId(guild_id: Snowflake, shards: ?usize) u64 { + return (guild_id.into() >> 22) % shards orelse 1; } /// Convert a timestamp to a snowflake. -pub inline fn snowflakeToTimestamp(id: u64) u64 { - return (id >> 22) + discord_epoch; +pub inline fn snowflakeToTimestamp(id: Snowflake) u64 { + return (id.into() >> 22) + discord_epoch; } const Self = @This(); diff --git a/src/discord.zig b/src/discord.zig index edd5adc..ae4affd 100644 --- a/src/discord.zig +++ b/src/discord.zig @@ -53,7 +53,7 @@ pub fn start(self: *Self, settings: struct { // check status idk if (res.status != http.Status.ok) { - @panic("we are cooked\n"); + @panic("we are cooked\n"); // check your token dumbass } const parsed = try json.parseFromSlice(GatewayBotInfo, self.allocator, body, .{}); diff --git a/src/internal.zig b/src/internal.zig index 9f3c14c..467255b 100644 --- a/src/internal.zig +++ b/src/internal.zig @@ -262,9 +262,10 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // auto_moderation_rule_update: null = null, // TODO: implement // auto_moderation_rule_delete: null = null, // TODO: implement // auto_moderation_action_execution: null = null, - // TODO: implement // channel_create: null = null, - // TODO: implement // channel_update: null = null, - // TODO: implement // channel_delete: null = null, + channel_create: ?*const fn (save: T, chan: Types.Channel) anyerror!void = undefined, + channel_update: ?*const fn (save: T, chan: Types.Channel) anyerror!void = undefined, + /// this isn't send when the channel is not relevant to you + channel_delete: ?*const fn (save: T, chan: Types.Channel) anyerror!void = undefined, // TODO: implement // channel_pins_update: null = null, // TODO: implement // thread_create: null = null, // TODO: implement // thread_update: null = null, @@ -273,21 +274,24 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // thread_member_update: null = null, // TODO: implement // thread_members_update: null = null, // TODO: implement // guild_audit_log_entry_create: null = null, - // TODO: implement // guild_create: null = null, - // TODO: implement // guild_update: null = null, - // TODO: implement // guild_delete: null = null, - // TODO: implement // guild_ban_add: null = null, - // TODO: implement // guild_ban_remove: null = null, + guild_create: ?*const fn (save: T, guild: Types.Guild) anyerror!void = undefined, + guild_create_unavailable: ?*const fn (save: T, guild: Types.UnavailableGuild) anyerror!void = undefined, + guild_update: ?*const fn (save: T, guild: Types.Guild) anyerror!void = undefined, + /// this is not necessarily sent upon deletion of a guild + /// but from when a user is *removed* therefrom + guild_delete: ?*const fn (save: T, guild: Types.UnavailableGuild) anyerror!void = undefined, + guild_ban_add: ?*const fn (save: T, gba: Types.GuildBanAddRemove) anyerror!void = undefined, + guild_ban_remove: ?*const fn (save: T, gbr: Types.GuildBanAddRemove) anyerror!void = undefined, // TODO: implement // guild_emojis_update: null = null, // TODO: implement // guild_stickers_update: null = null, // TODO: implement // guild_integrations_update: null = null, - // TODO: implement // guild_member_add: null = null, - // TODO: implement // guild_member_remove: null = null, - // TODO: implement // guild_member_update: null = null, + guild_member_add: ?*const fn (save: T, guild_id: Types.GuildMemberAdd) anyerror!void = undefined, + guild_member_update: ?*const fn (save: T, fields: Types.GuildMemberUpdate) anyerror!void = undefined, + guild_member_remove: ?*const fn (save: T, user: Types.GuildMemberRemove) anyerror!void = undefined, // TODO: implement // guild_members_chunk: null = null, - // TODO: implement // guild_role_create: null = null, - // TODO: implement // guild_role_update: null = null, - // TODO: implement // guild_role_delete: null = null, + guild_role_create: ?*const fn (save: T, role: Types.GuildRoleCreate) anyerror!void = undefined, + guild_role_delete: ?*const fn (save: T, role: Types.GuildRoleDelete) anyerror!void = undefined, + guild_role_update: ?*const fn (save: T, role: Types.GuildRoleUpdate) anyerror!void = undefined, // TODO: implement // guild_scheduled_event_create: null = null, // TODO: implement // guild_scheduled_event_update: null = null, // TODO: implement // guild_scheduled_event_delete: null = null, @@ -297,13 +301,13 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // integration_update: null = null, // TODO: implement // integration_delete: null = null, // TODO: implement // interaction_create: null = null, - // TODO: implement // invite_create: null = null, - // TODO: implement // invite_delete: null = null, + invite_create: ?*const fn (save: T, data: Types.InviteCreate) anyerror!void = undefined, + invite_delete: ?*const fn (save: T, data: Types.InviteDelete) anyerror!void = undefined, message_create: ?*const fn (save: T, message: Types.Message) anyerror!void = undefined, message_update: ?*const fn (save: T, message: Types.Message) anyerror!void = undefined, message_delete: ?*const fn (save: T, log: Types.MessageDelete) anyerror!void = undefined, message_delete_bulk: ?*const fn (save: T, log: Types.MessageDeleteBulk) anyerror!void = undefined, - // TODO: message_reaction_add: ?*const fn (save: T, log: Discord.MessageReactionAdd) anyerror!void = undefined, + message_reaction_add: ?*const fn (save: T, log: Types.MessageReactionAdd) anyerror!void = undefined, // TODO: implement // message_reaction_remove: null = null, // TODO: implement // message_reaction_remove_all: null = null, // TODO: implement // message_reaction_remove_emoji: null = null, @@ -311,8 +315,9 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // stage_instance_create: null = null, // TODO: implement // stage_instance_update: null = null, // TODO: implement // stage_instance_delete: null = null, - // TODO: implement // typing_start: null = null, - // TODO: implement // user_update: null = null, + typing_start: ?*const fn (save: T, data: Types.TypingStart) anyerror!void = undefined, + /// remember this is only sent when you change your profile yourself/your bot does + user_update: ?*const fn (save: T, user: Types.User) anyerror!void = undefined, // TODO: implement // voice_channel_effect_send: null = null, // TODO: implement // voice_state_update: null = null, // TODO: implement // voice_server_update: null = null, diff --git a/src/shard.zig b/src/shard.zig index 3fc7f6c..fa6fc3e 100644 --- a/src/shard.zig +++ b/src/shard.zig @@ -206,7 +206,7 @@ const ReadMessageError = mem.Allocator.Error || zlib.Error || json.ParseError(js fn readMessage(self: *Self, _: anytype) !void { try self.client.readTimeout(0); - while (try self.client.read()) |msg| { + while (try self.client.read()) |msg| { // check your intents, dumbass defer self.client.done(msg); try self.packets.appendSlice(msg.data); @@ -247,7 +247,6 @@ fn readMessage(self: *Self, _: anytype) !void { // PARSE NEW URL IN READY self.heart = Heart{ - // TODO: fix bug .heartbeatInterval = helloPayload.heartbeat_interval, .lastBeat = 0, }; @@ -255,10 +254,10 @@ fn readMessage(self: *Self, _: anytype) !void { if (self.resumable()) { try self.resume_(); return; - } else { - try self.identify(self.details.properties); } + try self.identify(self.details.properties); + var prng = std.Random.DefaultPrng.init(0); const jitter = std.Random.float(prng.random(), f64); self.heart.lastBeat = std.time.milliTimestamp(); @@ -379,31 +378,168 @@ pub fn send(self: *Self, _: bool, data: anytype) SendError!void { } pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void { + // std.debug.print("event: {s}\n", .{name}); + if (mem.eql(u8, name, "READY")) { const ready = try zjson.parse(GatewayPayload(Types.Ready), self.allocator, payload); - try self.handler.ready.?(self, ready.value.d.?); + if (self.handler.ready) |event| try event(self, ready.value.d.?); } + + if (mem.eql(u8, name, "CHANNEL_CREATE")) { + const chan = try zjson.parse(GatewayPayload(Types.Channel), self.allocator, payload); + + if (self.handler.channel_create) |event| try event(self, chan.value.d.?); + } + + if (mem.eql(u8, name, "CHANNEL_UPDATE")) { + const chan = try zjson.parse(GatewayPayload(Types.Channel), self.allocator, payload); + + if (self.handler.channel_update) |event| try event(self, chan.value.d.?); + } + + if (mem.eql(u8, name, "CHANNEL_DELETE")) { + const chan = try zjson.parse(GatewayPayload(Types.Channel), self.allocator, payload); + + if (self.handler.channel_delete) |event| try event(self, chan.value.d.?); + } + + if (mem.eql(u8, name, "INVITE_CREATE")) { + const data = try zjson.parse(GatewayPayload(Types.InviteCreate), self.allocator, payload); + + if (self.handler.invite_create) |event| try event(self, data.value.d.?); + } + + if (mem.eql(u8, name, "INVITE_DELETE")) { + const data = try zjson.parse(GatewayPayload(Types.InviteDelete), self.allocator, payload); + + if (self.handler.invite_delete) |event| try event(self, data.value.d.?); + } + if (mem.eql(u8, name, "MESSAGE_CREATE")) { const message = try zjson.parse(GatewayPayload(Types.Message), self.allocator, payload); - try self.handler.message_create.?(self, message.value.d.?); + if (self.handler.message_create) |event| try event(self, message.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_DELETE")) { const data = try zjson.parse(GatewayPayload(Types.MessageDelete), self.allocator, payload); - try self.handler.message_delete.?(self, data.value.d.?); + if (self.handler.message_delete) |event| try event(self, data.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_UPDATE")) { const message = try zjson.parse(GatewayPayload(Types.Message), self.allocator, payload); - try self.handler.message_update.?(self, message.value.d.?); + if (self.handler.message_update) |event| try event(self, message.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_DELETE_BULK")) { const data = try zjson.parse(GatewayPayload(Types.MessageDeleteBulk), self.allocator, payload); - try self.handler.message_delete_bulk.?(self, data.value.d.?); + if (self.handler.message_delete_bulk) |event| try event(self, data.value.d.?); } - if (self.handler.any) |anyEvent| try anyEvent(self, payload); + if (mem.eql(u8, name, "MESSAGE_REACTION_ADD")) { + const data = try zjson.parse(GatewayPayload(Types.MessageReactionAdd), self.allocator, payload); + + if (self.handler.message_reaction_add) |event| try event(self, data.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_CREATE")) { + const isAvailable = + try zjson.parse(GatewayPayload(struct { unavailable: ?bool }), self.allocator, payload); + + if (isAvailable.value.d.?.unavailable == true) { + const guild = try zjson.parse(GatewayPayload(Types.Guild), self.allocator, payload); + + if (self.handler.guild_create) |event| try event(self, guild.value.d.?); + return; + } + + const guild = try zjson.parse(GatewayPayload(Types.UnavailableGuild), self.allocator, payload); + + if (self.handler.guild_create_unavailable) |event| try event(self, guild.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_UPDATE")) { + const guild = try zjson.parse(GatewayPayload(Types.Guild), self.allocator, payload); + + if (self.handler.guild_update) |event| try event(self, guild.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_DELETE")) { + const guild = try zjson.parse(GatewayPayload(Types.UnavailableGuild), self.allocator, payload); + + if (self.handler.guild_delete) |event| try event(self, guild.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_MEMBER_ADD")) { + const guild_id = try zjson.parse(GatewayPayload(Types.GuildMemberAdd), self.allocator, payload); + + if (self.handler.guild_member_add) |event| try event(self, guild_id.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_MEMBER_UPDATE")) { + const fields = try zjson.parse(GatewayPayload(Types.GuildMemberUpdate), self.allocator, payload); + + if (self.handler.guild_member_update) |event| try event(self, fields.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_MEMBER_REMOVE")) { + const user = try zjson.parse(GatewayPayload(Types.GuildMemberRemove), self.allocator, payload); + + if (self.handler.guild_member_remove) |event| try event(self, user.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_ROLE_CREATE")) { + const role = try zjson.parse(GatewayPayload(Types.GuildRoleCreate), self.allocator, payload); + + if (self.handler.guild_role_create) |event| try event(self, role.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_ROLE_UPDATE")) { + const role = try zjson.parse(GatewayPayload(Types.GuildRoleUpdate), self.allocator, payload); + + if (self.handler.guild_role_update) |event| try event(self, role.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_ROLE_DELETE")) { + const role_id = try zjson.parse(GatewayPayload(Types.GuildRoleDelete), self.allocator, payload); + + if (self.handler.guild_role_delete) |event| try event(self, role_id.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_DELETE")) { + const guild = try zjson.parse(GatewayPayload(Types.UnavailableGuild), self.allocator, payload); + + if (self.handler.guild_delete) |event| try event(self, guild.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_BAN_ADD")) { + const gba = try zjson.parse(GatewayPayload(Types.GuildBanAddRemove), self.allocator, payload); + + if (self.handler.guild_ban_add) |event| try event(self, gba.value.d.?); + } + + if (mem.eql(u8, name, "GUILD_BAN_REMOVE")) { + const gbr = try zjson.parse(GatewayPayload(Types.GuildBanAddRemove), self.allocator, payload); + + if (self.handler.guild_ban_remove) |event| try event(self, gbr.value.d.?); + } + + if (mem.eql(u8, name, "TYPING_START")) { + const data = try zjson.parse(GatewayPayload(Types.TypingStart), self.allocator, payload); + + if (self.handler.typing_start) |event| try event(self, data.value.d.?); + } + + if (mem.eql(u8, name, "USER_UPDATE")) { + const user = try zjson.parse(GatewayPayload(Types.User), self.allocator, payload); + + if (self.handler.user_update) |event| try event(self, user.value.d.?); + } + + if (self.handler.any) |anyEvent| + try anyEvent(self, payload); } diff --git a/src/structures/application.zig b/src/structures/application.zig index a3fd400..2c8006f 100644 --- a/src/structures/application.zig +++ b/src/structures/application.zig @@ -13,7 +13,7 @@ pub const Application = struct { /// The description of the app description: []const u8, /// An array of rpc origin urls, if rpc is enabled - rpc_origins: []?[]const u8, + rpc_origins: ?[][]const u8, /// The url of the app's terms of service terms_of_service_url: ?[]const u8, /// The url of the app's privacy policy @@ -45,7 +45,7 @@ pub const Application = struct { ///If this application is a game sold on , this field will be the hash of the image on store embeds cover_image: ?[]const u8, /// up to 5 tags describing the content and functionality of the application - tags: []?[]const u8, + tags: ?[][]const u8, /// settings for the application's default in-app authorization link, if enabled install_params: ?InstallParams, // Default scopes and permissions for each supported installation context. @@ -63,7 +63,7 @@ pub const Application = struct { /// Partial user object for the bot user associated with the app bot: ?Partial(User), /// Array of redirect URIs for the app - redirect_uris: []?[]const u8, + redirect_uris: ?[][]const u8, /// Interactions endpoint URL for the app interactions_endpoint_url: ?[]const u8, }; diff --git a/src/structures/auditlog.zig b/src/structures/auditlog.zig index 9c278c6..589c2a2 100644 --- a/src/structures/auditlog.zig +++ b/src/structures/auditlog.zig @@ -25,9 +25,9 @@ pub const AuditLog = struct { /// threads: []Channel, /// List of guild scheduled events found in the audit log - guild_scheduled_events: []?ScheduledEvent, + guild_scheduled_events: ?[]ScheduledEvent, /// List of auto moderation rules referenced in the audit log - auto_moderation_rules: []?AutoModerationRule, + auto_moderation_rules: ?[]AutoModerationRule, /// List of application commands referenced in the audit log application_commands: []ApplicationCommand, }; @@ -38,7 +38,7 @@ pub const AuditLogEntry = struct { target_id: ?Snowflake, /// Changes made to the `target_id` /// TODO: change this - changes: []?AuditLogChange(noreturn), + changes: ?[]AuditLogChange(noreturn), /// User or app that made the changes user_id: ?Snowflake, /// ID of the entry diff --git a/src/structures/automod.zig b/src/structures/automod.zig index d3c9f50..65ab2be 100644 --- a/src/structures/automod.zig +++ b/src/structures/automod.zig @@ -55,7 +55,7 @@ pub const AutoModerationRuleTriggerMetadata = struct { /// /// Can have up to 1000 elements in the array and each []const u8 can have up to 60 characters /// - keyword_filter: []?[]const u8, + keyword_filter: ?[][]const u8, /// /// Regular expression patterns which will be matched against content. /// @@ -71,7 +71,7 @@ pub const AutoModerationRuleTriggerMetadata = struct { /// @remarks /// Only present with {@link AutoModerationTriggerTypes.KeywordPreset};. /// - presets: []?AutoModerationRuleTriggerMetadataPresets, + presets: ?[]AutoModerationRuleTriggerMetadataPresets, /// /// The substrings which will exempt from triggering the preset trigger type. /// @@ -81,7 +81,7 @@ pub const AutoModerationRuleTriggerMetadata = struct { /// When used with {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MemberProfile}; there can have up to 100 elements in the array and each []const u8 can have up to 60 characters. /// When used with {@link AutoModerationTriggerTypes.KeywordPreset}; there can have up to 1000 elements in the array and each []const u8 can have up to 60 characters. /// - allow_list: []?[]const u8, + allow_list: ?[][]const u8, /// /// Total isize of mentions (role & user) allowed per message. /// diff --git a/src/structures/channel.zig b/src/structures/channel.zig index 2322f15..e0c4df5 100644 --- a/src/structures/channel.zig +++ b/src/structures/channel.zig @@ -15,13 +15,13 @@ const ForumLayout = @import("shared.zig").ForumLayout; /// https://discord.com/developers/docs/resources/channel#allowed-mentions-object pub const AllowedMentions = struct { /// An array of allowed mention types to parse from the content. - parse: []?AllowedMentionsTypes, + parse: ?[]AllowedMentionsTypes, /// For replies, whether to mention the author of the message being replied to (default false) replied_user: ?bool, /// Array of role_ids to mention (Max size of 100) - roles: []?[]const u8, + roles: ?[][]const u8, /// Array of user_ids to mention (Max size of 100) - users: []?[]const u8, + users: ?[][]const u8, }; /// https://discord.com/developers/docs/topics/gateway#typing-start @@ -49,7 +49,7 @@ pub const Channel = struct { /// Sorting position of the channel (channels with the same position are sorted by id) position: ?isize, /// Explicit permission overwrites for members and roles - permission_overwrites: []?Overwrite, + permission_overwrites: ?[]Overwrite, /// The name of the channel (1-100 characters) name: ?[]const u8, /// The channel topic (0-4096 characters for GUILD_FORUM channels, 0-1024 characters for all others) @@ -65,7 +65,7 @@ pub const Channel = struct { /// Amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission `manage_messages` or `manage_channel`, are unaffected rate_limit_per_user: ?isize, /// the recipients of the DM - recipients: []?User, + recipients: ?[]User, /// icon hash of the group DM icon: ?[]const u8, /// Id of the creator of the thread @@ -99,9 +99,9 @@ pub const Channel = struct { /// isize of messages ever sent in a thread, it's similar to `message_count` on message creation, but will not decrement the isize when a message is deleted total_message_sent: ?isize, /// The set of tags that can be used in a GUILD_FORUM channel - available_tags: []?ForumTag, + available_tags: ?[]ForumTag, /// The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel - applied_tags: []?[]const u8, + applied_tags: ?[][]const u8, /// the emoji to show in the add reaction button on a thread in a GUILD_FORUM channel default_reaction_emoji: ?DefaultReactionEmoji, /// the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. diff --git a/src/structures/command.zig b/src/structures/command.zig index 1634f71..f8735d9 100644 --- a/src/structures/command.zig +++ b/src/structures/command.zig @@ -25,7 +25,7 @@ pub const ApplicationCommand = struct { /// Localization object for `description` field. Values follow the same restrictions as `description` description_localizations: ?[]const u8, //?Localization, /// Parameters for the command, max of 25 - options: []?ApplicationCommandOption, + options: ?[]ApplicationCommandOption, /// Set of permissions represented as a bit set default_member_permissions: ?[]const u8, /// @@ -35,7 +35,7 @@ pub const ApplicationCommand = struct { /// This value is available only for globally-scoped commands /// Defaults to the application configured contexts /// - integration_types: []?ApplicationIntegrationType, + integration_types: ?[]ApplicationIntegrationType, /// /// Interaction context(s) where the command can be used /// @@ -43,7 +43,7 @@ pub const ApplicationCommand = struct { /// This value is available only for globally-scoped commands /// By default, all interaction context types included for new commands. /// - contexts: []?InteractionContextType, + contexts: ?[]InteractionContextType, /// /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. /// @@ -87,7 +87,7 @@ pub const CreateApplicationCommand = struct { /// Localization object for `description` field. Values follow the same restrictions as `description` description_localizations: []const u8, //?Localization, /// Parameters for the command, max of 25 - options: []?ApplicationCommandOption, + options: ?[]ApplicationCommandOption, /// Set of permissions represented as a bit set default_member_permissions: ?[]const u8, /// @@ -97,7 +97,7 @@ pub const CreateApplicationCommand = struct { /// This value is available only for globally-scoped commands /// Defaults to the application configured contexts /// - integration_types: []?ApplicationIntegrationType, + integration_types: ?[]ApplicationIntegrationType, /// /// Interaction context(s) where the command can be used /// @@ -105,7 +105,7 @@ pub const CreateApplicationCommand = struct { /// This value is available only for globally-scoped commands /// By default, all interaction context types included for new commands. /// - contexts: []?InteractionContextType, + contexts: ?[]InteractionContextType, /// /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. /// @@ -170,14 +170,14 @@ pub const ApplicationCommandOption = struct { /// /// If you provide an array of choices, they will be the ONLY accepted values for this option /// - choices: []?ApplicationCommandOptionChoice, + choices: ?[]ApplicationCommandOptionChoice, /// /// If the option is a subcommand or subcommand group type, these nested options will be the parameters /// /// @remarks /// Only valid in option of type {@link ApplicationCommandOptionTypes.SubCommand | SubCommand}; or {@link ApplicationCommandOptionTypes.SubCommandGroup | SubCommandGroup}; /// - options: []?ApplicationCommandOption, + options: ?[]ApplicationCommandOption, /// /// If autocomplete interactions are enabled for this option. /// @@ -193,7 +193,7 @@ pub const ApplicationCommandOption = struct { /// @remarks /// Only valid in option of type {@link ApplicationCommandOptionTypes.Channel | Channel}; /// - channel_types: []?ChannelTypes, + channel_types: ?[]ChannelTypes, /// /// The minimum permitted value /// diff --git a/src/structures/embed.zig b/src/structures/embed.zig index 59614ba..327c7e2 100644 --- a/src/structures/embed.zig +++ b/src/structures/embed.zig @@ -27,7 +27,7 @@ pub const Embed = struct { /// Author information author: ?EmbedAuthor, /// Fields information - fields: []?EmbedField, + fields: ?[]EmbedField, }; /// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure diff --git a/src/structures/emoji.zig b/src/structures/emoji.zig index f4337be..4195f6c 100644 --- a/src/structures/emoji.zig +++ b/src/structures/emoji.zig @@ -8,7 +8,7 @@ pub const Emoji = struct { /// Emoji id id: ?Snowflake, /// Roles allowed to use this emoji - roles: []?[]const u8, + roles: ?[][]const u8, /// User that created this emoji user: ?User, /// Whether this emoji must be wrapped in colons diff --git a/src/structures/events.zig b/src/structures/events.zig index 0a058c6..cd7a128 100644 --- a/src/structures/events.zig +++ b/src/structures/events.zig @@ -41,9 +41,9 @@ pub const GuildMembersChunk = struct { /// The total isize of expected chunks for this response chunk_count: isize, /// If passing an invalid id to `REQUEST_GUILD_MEMBERS`, it will be returned here - not_found: []?[]const u8, + not_found: ?[][]const u8, /// If passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned members will be here - presences: []?PresenceUpdate, + presences: ?[]PresenceUpdate, /// The nonce used in the Guild Members Request nonce: ?[]const u8, }; @@ -113,7 +113,7 @@ pub const MessageReactionAdd = struct { /// true if this is a super-reaction burst: bool, /// Colors used for super-reaction animation in "#rrggbb" format - burst_colors: []?[]const u8, + burst_colors: ?[][]const u8, /// The type of reaction type: ReactionType, }; @@ -306,9 +306,9 @@ pub const ThreadMembersUpdate = struct { /// The id of the guild guild_id: Snowflake, /// The users who were added to the thread - added_members: []?ThreadMember, + added_members: ?[]ThreadMember, /// The id of the users who were removed from the thread - removed_member_ids: []?[]const u8, + removed_member_ids: ?[][]const u8, /// the approximate isize of members in the thread, capped at 50 member_count: isize, }; @@ -496,7 +496,7 @@ pub const ModifyChannel = struct { /// The user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit user_limit: ?isize, /// Channel or category-specific permissions - permission_overwrites: []?Overwrite, + permission_overwrites: ?[]Overwrite, /// Id of the new parent category for a channel parent_id: ?Snowflake, /// Voice region id for the voice channel, automatic when set to null @@ -525,7 +525,7 @@ pub const ModifyChannel = struct { emoji_name: []const u8, }, /// The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel; limited to 5 - applied_tags: []?[]const u8, + applied_tags: ?[][]const u8, /// the emoji to show in the add reaction button on a thread in a GUILD_FORUM channel default_reaction_emoji: ?struct { /// The id of a guild's custom emoji @@ -548,7 +548,7 @@ pub const CreateGuildEmoji = struct { ///The 128x128 emoji image. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. If a URL is provided to the image parameter, eno will automatically convert it to a base64 []const u8 internally. image: []const u8, /// Roles allowed to use this emoji - roles: []?[]const u8, + roles: ?[][]const u8, }; /// https://discord.com/developers/docs/resources/emoji#modify-guild-emoji @@ -556,7 +556,7 @@ pub const ModifyGuildEmoji = struct { /// Name of the emoji name: ?[]const u8, /// Roles allowed to use this emoji - roles: []?[]const u8, + roles: ?[][]const u8, }; pub const CreateGuildChannel = struct { @@ -575,7 +575,7 @@ pub const CreateGuildChannel = struct { /// Sorting position of the channel position: ?isize, /// The channel's permission overwrites - permission_overwrites: []?Overwrite, + permission_overwrites: ?[]Overwrite, /// Id of the parent category for a channel parent_id: ?Snowflake, /// Whether the channel is nsfw @@ -617,7 +617,7 @@ pub const CreateMessage = struct { /// true if this is a TTS message tts: ?bool, /// Embedded `rich` content (up to 6000 characters) - embeds: []?Embed, + embeds: ?[]Embed, /// Allowed mentions for the message allowed_mentions: ?AllowedMentions, /// Include to make your message a reply @@ -645,7 +645,7 @@ pub const ModifyGuildWelcomeScreen = struct { /// Whether the welcome screen is enabled enabled: ?bool, /// Channels linked in the welcome screen and their display options - welcome_screen: []?WelcomeScreenChannel, + welcome_screen: ?[]WelcomeScreenChannel, /// The server description to show in the welcome screen description: ?[]const u8, }; @@ -696,22 +696,22 @@ pub const CreateForumPostWithMessage = struct { /// Message contents (up to 2000 characters) content: ?[]const u8, /// Embedded rich content (up to 6000 characters) - embeds: []?Embed, + embeds: ?[]Embed, /// Allowed mentions for the message - allowed_mentions: []?AllowedMentions, + allowed_mentions: ?[]AllowedMentions, /// Components to include with the message - components: []?[]MessageComponent, + components: ?[][]MessageComponent, /// IDs of up to 3 stickers in the server to send in the message - sticker_ids: []?[]const u8, + sticker_ids: ?[][]const u8, /// JSON-encoded body of non-file params, only for multipart/form-data requests. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files}; payload_json: ?[]const u8, /// Attachment objects with filename and description. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files}; - attachments: []?Attachment, + attachments: ?[]Attachment, /// Message flags combined as a bitfield, only SUPPRESS_EMBEDS can be set flags: ?MessageFlags, }, /// the IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel - applied_tags: []?[]const u8, + applied_tags: ?[][]const u8, }; pub const ArchivedThreads = struct { diff --git a/src/structures/gateway.zig b/src/structures/gateway.zig index e243489..023c1d8 100644 --- a/src/structures/gateway.zig +++ b/src/structures/gateway.zig @@ -74,7 +74,7 @@ pub const Activity = struct { /// Secrets for Rich Presence joining and spectating secrets: ?ActivitySecrets, /// The custom buttons shown in the Rich Presence (max 2) - buttons: []?ActivityButton, + buttons: ?[]ActivityButton, }; /// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-instance-object diff --git a/src/structures/guild.zig b/src/structures/guild.zig index 880c89e..7f746e5 100644 --- a/src/structures/guild.zig +++ b/src/structures/guild.zig @@ -101,15 +101,15 @@ pub const Guild = struct { /// When this guild was joined at joined_at: ?[]const u8, // States of members currently in voice channels; lacks the guild_id key - // voice_states: []?Omit(VoiceState, .{"guildId"}), + // voice_states: ?[]Omit(VoiceState, .{"guildId"}), /// Users in the guild - members: []?Member, + members: ?[]Member, /// Channels in the guild - channels: []?Channel, + channels: ?[]Channel, /// All active threads in the guild that the current user has permission to view - threads: []?Channel, + threads: ?[]Channel, /// Presences of the members in the guild, will only include non-offline members if the size is greater than large threshold - presences: []?Partial(PresenceUpdate), + presences: ?[]Partial(PresenceUpdate), /// Banner hash banner: ?[]const u8, ///The preferred locale of a Community guild; used in server discovery and notices from ; defaults to "en-US" @@ -119,9 +119,9 @@ pub const Guild = struct { /// The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object welcome_screen: ?WelcomeScreen, /// Stage instances in the guild - stage_instances: []?StageInstance, + stage_instances: ?[]StageInstance, /// Custom guild stickers - stickers: []?Sticker, + stickers: ?[]Sticker, ///The id of the channel where admins and moderators of Community guilds receive safety alerts from safety_alerts_channel_id: ?Snowflake, }; diff --git a/src/structures/interaction.zig b/src/structures/interaction.zig index 9b84798..4d90281 100644 --- a/src/structures/interaction.zig +++ b/src/structures/interaction.zig @@ -151,7 +151,7 @@ pub const InteractionData = struct { /// The components if its a Modal Submit interaction. components: ?[]MessageComponent, /// The values chosen by the user. - values: []?[]const u8, + values: ?[][]const u8, /// The Id of the invoked command id: Snowflake, /// The name of the invoked command @@ -179,7 +179,7 @@ pub const InteractionData = struct { attachments: Record(Attachment), }, /// The params + values from the user - options: []?InteractionDataOption, + options: ?[]InteractionDataOption, /// The target id if this is a context menu command. target_id: ?Snowflake, /// the id of the guild the command is registered to @@ -198,7 +198,7 @@ pub const InteractionDataOption = struct { integer: isize, }, /// Present if this option is a group or subcommand - options: []?InteractionDataOption, + options: ?[]InteractionDataOption, /// `true` if this option is the currently focused option for autocomplete focused: ?bool, }; diff --git a/src/structures/message.zig b/src/structures/message.zig index 96ec554..1c2ed43 100644 --- a/src/structures/message.zig +++ b/src/structures/message.zig @@ -245,12 +245,12 @@ pub const MessageSnapshot = struct { avatar_decoration_data: ?AvatarDecorationData, member: ?Partial(Member), }, - mention_roles: []?[]const u8, + mention_roles: ?[][]const u8, type: MessageTypes, flags: ?MessageFlags, - stickers: []?Sticker, + stickers: ?[]Sticker, components: ?[]MessageComponent, - sticker_items: []?StickerItem, + sticker_items: ?[]StickerItem, attachments: []Attachment, embeds: []Embed, }, diff --git a/src/structures/scheduled_event.zig b/src/structures/scheduled_event.zig index 2e4f11d..930e252 100644 --- a/src/structures/scheduled_event.zig +++ b/src/structures/scheduled_event.zig @@ -56,15 +56,15 @@ pub const ScheduledEventRecurrenceRule = struct { /// The spacing between the events, defined by `frequency`. For example, `frequency` of `Weekly` and an `interval` of `2` would be "every-other week" interval: isize, /// Set of specific days within a week for the event to recur on - by_weekday: []?ScheduledEventRecurrenceRuleWeekday, + by_weekday: ?[]ScheduledEventRecurrenceRuleWeekday, /// List of specific days within a specific week (1-5) to recur on - by_n_weekday: []?ScheduledEventRecurrenceRuleNWeekday, + by_n_weekday: ?[]ScheduledEventRecurrenceRuleNWeekday, /// Set of specific months to recur on - by_month: []?ScheduledEventRecurrenceRuleMonth, + by_month: ?[]ScheduledEventRecurrenceRuleMonth, /// Set of specific dates within a month to recur on - by_month_day: []?isize, + by_month_day: ?[]isize, /// Set of days within a year to recur on (1-364) - by_year_day: []?isize, + by_year_day: ?[]isize, /// The total amount of times that the event is allowed to recur before stopping count: ?isize, }; diff --git a/src/structures/thread.zig b/src/structures/thread.zig index 4cc1acd..ccf4f46 100644 --- a/src/structures/thread.zig +++ b/src/structures/thread.zig @@ -47,7 +47,7 @@ pub const ThreadListSync = struct { /// The id of the guild guild_id: Snowflake, /// The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channelIds that have no active threads as well, so you know to clear that data - channel_ids: []?[]const u8, + channel_ids: ?[][]const u8, /// All active threads in the given channels that the current user can access threads: []Channel, /// All thread member objects from the synced threads for the current user, indicating which threads the current user has been added to diff --git a/src/structures/user.zig b/src/structures/user.zig index 1eb447a..e67f66e 100644 --- a/src/structures/user.zig +++ b/src/structures/user.zig @@ -85,7 +85,7 @@ pub const Connection = struct { /// whether the connection is revoked revoked: ?bool, /// an array of partial server integrations - integrations: []?Partial(Integration), + integrations: ?[]Partial(Integration), /// whether the connection is verified verified: bool, /// whether friend sync is enabled for this connection diff --git a/src/test.zig b/src/test.zig index cd75a87..c76a44e 100644 --- a/src/test.zig +++ b/src/test.zig @@ -7,6 +7,8 @@ const Thread = std.Thread; const std = @import("std"); const fmt = std.fmt; +const INTENTS = 53608447; + fn ready(_: *Shard, payload: Discord.Ready) !void { std.debug.print("logged in as {s}\n", .{payload.user.username}); } @@ -27,17 +29,19 @@ fn message_create(session: *Shard, message: Discord.Message) fmt.AllocPrintError }; } +fn message_reaction_add(_: *Shard, _: Discord.MessageReactionAdd) !void {} +fn guild_create(_: *Shard, guild: Discord.Guild) !void { + std.debug.print("{any}\n", .{guild}); +} + pub fn main() !void { var tsa = std.heap.ThreadSafeAllocator{ .child_allocator = std.heap.c_allocator }; var handler = Discord.init(tsa.allocator()); try handler.start(.{ - .token = std.posix.getenv("DISCORD_TOKEN") orelse unreachable, - .intents = Intents.fromRaw(37379), - .run = .{ - .message_create = &message_create, - .ready = &ready, - }, + .token = std.posix.getenv("DISCORD_TOKEN").?, + .intents = Intents.fromRaw(INTENTS), + .run = .{ .message_create = &message_create, .ready = &ready, .message_reaction_add = &message_reaction_add }, .log = .yes, .options = .{}, }); diff --git a/vendor/zjson/json.zig b/vendor/zjson/json.zig index 3170137..f171a8f 100644 --- a/vendor/zjson/json.zig +++ b/vendor/zjson/json.zig @@ -42,6 +42,8 @@ pub const ParserError = error{ UnclosedBraces, /// for `ultimateParserAssert` UnconsumedInput, + /// unknown property + UnknownProperty, }; /// Parser a = String -> Either ParserError (String, a) @@ -945,10 +947,30 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er if (value.is(.null)) return null; return try parseInto(optionalInfo.child, allocator, value); // optional }, - .@"union" => { + .@"union" => |unionInfo| { if (std.meta.hasFn(T, "toJson")) { return try T.toJson(allocator, value); } + if (unionInfo.tag_type == null) + @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'"); + + var result: ?T = null; + const fieldname = switch (value) { + .string => |slice| slice, + else => @panic("can only cast strings"), + }; + + inline for (unionInfo.fields) |u_field| { + if (std.mem.eql(u8, u_field.name, fieldname)) { + if (u_field.type == void) { + result = @unionInit(T, u_field.name, {}); + } else { + @panic("unions may only contain empty values"); + } + } + } + + return result.?; }, .@"enum" => { if (std.meta.hasFn(T, "toJson"))