From c8c1e2a2c13fbdaa8bc0d7423be8a6a6a339bb9e Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 4 Dec 2024 20:28:36 -0500 Subject: [PATCH] rework the whole library --- build.zig | 14 +- build.zig.zon | 8 - src/{sharder.zig => core.zig} | 18 +- src/discord.zig | 8 +- src/internal.zig | 78 +- src/parser.zig | 91 - src/shard.zig | 250 +- src/shared.zig | 100 - src/structures/application.zig | 93 + src/structures/attachment.zig | 34 + src/structures/auditlog.zig | 132 + src/structures/automod.zig | 170 + src/structures/channel.zig | 188 + src/structures/command.zig | 260 ++ src/structures/embed.zig | 106 + src/structures/emoji.zig | 22 + src/structures/events.zig | 916 +++++ src/structures/gateway.zig | 178 + src/structures/guild.zig | 204 ++ src/structures/integration.zig | 134 + src/structures/interaction.zig | 204 ++ src/structures/invite.zig | 97 + src/structures/member.zig | 62 + src/structures/message.zig | 344 ++ src/structures/monetization.zig | 74 + src/structures/oauth.zig | 47 + src/structures/partial.zig | 40 + src/structures/poll.zig | 120 + src/structures/role.zig | 49 + src/structures/scheduled_event.zig | 109 + src/structures/shared.zig | 1523 ++++++++ src/structures/snowflake.zig | 34 + src/structures/team.zig | 38 + src/structures/thread.zig | 55 + src/structures/types.zig | 46 + src/structures/user.zig | 144 + src/structures/webhook.zig | 74 + src/test.zig | 5 +- src/types.zig | 5154 ---------------------------- vendor/zjson/README.md | 58 + vendor/zjson/build.zig | 11 + vendor/zjson/build.zig.zon | 72 + vendor/zjson/json.zig | 1117 ++++++ 43 files changed, 6875 insertions(+), 5606 deletions(-) rename src/{sharder.zig => core.zig} (93%) delete mode 100644 src/parser.zig delete mode 100644 src/shared.zig create mode 100644 src/structures/application.zig create mode 100644 src/structures/attachment.zig create mode 100644 src/structures/auditlog.zig create mode 100644 src/structures/automod.zig create mode 100644 src/structures/channel.zig create mode 100644 src/structures/command.zig create mode 100644 src/structures/embed.zig create mode 100644 src/structures/emoji.zig create mode 100644 src/structures/events.zig create mode 100644 src/structures/gateway.zig create mode 100644 src/structures/guild.zig create mode 100644 src/structures/integration.zig create mode 100644 src/structures/interaction.zig create mode 100644 src/structures/invite.zig create mode 100644 src/structures/member.zig create mode 100644 src/structures/message.zig create mode 100644 src/structures/monetization.zig create mode 100644 src/structures/oauth.zig create mode 100644 src/structures/partial.zig create mode 100644 src/structures/poll.zig create mode 100644 src/structures/role.zig create mode 100644 src/structures/scheduled_event.zig create mode 100644 src/structures/shared.zig create mode 100644 src/structures/snowflake.zig create mode 100644 src/structures/team.zig create mode 100644 src/structures/thread.zig create mode 100644 src/structures/types.zig create mode 100644 src/structures/user.zig create mode 100644 src/structures/webhook.zig delete mode 100644 src/types.zig create mode 100644 vendor/zjson/README.md create mode 100644 vendor/zjson/build.zig create mode 100644 vendor/zjson/build.zig.zon create mode 100644 vendor/zjson/json.zig diff --git a/build.zig b/build.zig index 3eebdc6..6ae04dd 100644 --- a/build.zig +++ b/build.zig @@ -22,19 +22,15 @@ pub fn build(b: *std.Build) void { const zlib = b.dependency("zlib", .{}); - const zmpl = b.dependency("zmpl", .{ - .target = target, - .optimize = optimize, - }); - const deque = b.dependency("zig-deque", .{ .target = target, .optimize = optimize, }); - const json_parse = b.dependency("zjson", .{ + const json_parse = b.addModule("zjson", .{ .target = target, .optimize = optimize, + .root_source_file = b.path("vendor/zjson/json.zig"), }); const marin = b.addExecutable(.{ @@ -49,16 +45,14 @@ pub fn build(b: *std.Build) void { dzig.addImport("ws", websocket.module("websocket")); dzig.addImport("zlib", zlib.module("zlib")); - dzig.addImport("zmpl", zmpl.module("zmpl")); dzig.addImport("deque", deque.module("zig-deque")); - dzig.addImport("json", json_parse.module("zjson")); + dzig.addImport("json", json_parse); marin.root_module.addImport("discord.zig", dzig); marin.root_module.addImport("ws", websocket.module("websocket")); marin.root_module.addImport("zlib", zlib.module("zlib")); - marin.root_module.addImport("zmpl", zmpl.module("zmpl")); marin.root_module.addImport("deque", deque.module("zig-deque")); - marin.root_module.addImport("json", json_parse.module("zjson")); + marin.root_module.addImport("json", json_parse); //b.installArtifact(marin); diff --git a/build.zig.zon b/build.zig.zon index e4721ae..7b3ba17 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -31,18 +31,10 @@ .url = "https://github.com/yuzudev/zig-zlib/archive/refs/heads/main.zip", .hash = "1220cd041e8d04f1da9d6f46d0438f4e6809b113ba3454fffdaae96b59d2b35a6b2b", }, - .zmpl = .{ - .url = "https://github.com/jetzig-framework/zmpl/archive/refs/heads/main.zip", - .hash = "1220ed127f38fa51df53a85b3cc2030a7555e34058db7fd374ebaef817abb43d35f7", - }, .websocket = .{ .url = "https://github.com/yuzudev/websocket.zig/archive/refs/heads/master.zip", .hash = "12207c03624f9f5a1c444bde3d484a9b1e927a902067fded98364b714de412d318e0", }, - .zjson = .{ - .url = "git+https://codeberg.org/yuzu/json#322d90128e3a78b7639c7eb849aeee027613034a", - .hash = "12209277a5606fc3d1c6491198b787ce977ccbfc9371efb4e295a2b410d231c67e2e", - }, }, .paths = .{ "build.zig", diff --git a/src/sharder.zig b/src/core.zig similarity index 93% rename from src/sharder.zig rename to src/core.zig index 927961a..4054696 100644 --- a/src/sharder.zig +++ b/src/core.zig @@ -1,16 +1,14 @@ -const Intents = @import("types.zig").Intents; -const GatewayBotInfo = @import("shared.zig").GatewayBotInfo; -const Shared = @import("shared.zig"); -const IdentifyProperties = Shared.IdentifyProperties; -const ShardDetails = Shared.ShardDetails; -const Internal = @import("internal.zig"); -const ConnectQueue = Internal.ConnectQueue; -const GatewayDispatchEvent = Internal.GatewayDispatchEvent; +const Intents = @import("./structures/types.zig").Intents; +const GatewayBotInfo = @import("internal.zig").GatewayBotInfo; +const IdentifyProperties = @import("internal.zig").IdentifyProperties; +const ShardDetails = @import("internal.zig").ShardDetails; +const ConnectQueue = @import("internal.zig").ConnectQueue; +const GatewayDispatchEvent = @import("internal.zig").GatewayDispatchEvent; const Log = @import("internal.zig").Log; const Shard = @import("shard.zig"); const std = @import("std"); const mem = std.mem; -const debug = Internal.debug; +const debug = @import("internal.zig").debug; pub const discord_epoch = 1420070400000; @@ -222,7 +220,7 @@ pub fn send(self: *Self, shard_id: usize, data: anytype) Shard.SendError!void { inline fn logif(self: *Self, comptime format: []const u8, args: anytype) void { switch (self.log) { - .yes => Internal.debug.info(format, args), + .yes => debug.info(format, args), .no => {}, } } diff --git a/src/discord.zig b/src/discord.zig index 0ae6504..edd5adc 100644 --- a/src/discord.zig +++ b/src/discord.zig @@ -1,16 +1,14 @@ -pub usingnamespace @import("types.zig"); +pub usingnamespace @import("./structures/types.zig"); pub const Shard = @import("shard.zig"); pub const Internal = @import("internal.zig"); const GatewayDispatchEvent = Internal.GatewayDispatchEvent; +const GatewayBotInfo = Internal.GatewayBotInfo; const Log = Internal.Log; -pub const Sharder = @import("sharder.zig"); +pub const Sharder = @import("core.zig"); const SessionOptions = Sharder.SessionOptions; -pub const Shared = @import("shared.zig"); -const GatewayBotInfo = Shared.GatewayBotInfo; - pub const FetchReq = @import("http.zig").FetchReq; const std = @import("std"); diff --git a/src/internal.zig b/src/internal.zig index c113473..9f3c14c 100644 --- a/src/internal.zig +++ b/src/internal.zig @@ -1,9 +1,73 @@ const std = @import("std"); const mem = std.mem; const Deque = @import("deque").Deque; -const Discord = @import("types.zig"); const builtin = @import("builtin"); -const IdentifyProperties = @import("shared.zig").IdentifyProperties; +const Types = @import("./structures/types.zig"); + +pub const IdentifyProperties = struct { + /// Operating system the shard runs on. + os: []const u8, + /// The "browser" where this shard is running on. + browser: []const u8, + /// The device on which the shard is running. + device: []const u8, + + system_locale: ?[]const u8 = null, // TODO parse this + browser_user_agent: ?[]const u8 = null, + browser_version: ?[]const u8 = null, + os_version: ?[]const u8 = null, + referrer: ?[]const u8 = null, + referring_domain: ?[]const u8 = null, + referrer_current: ?[]const u8 = null, + referring_domain_current: ?[]const u8 = null, + release_channel: ?[]const u8 = null, + client_build_number: ?u64 = null, + client_event_source: ?[]const u8 = null, +}; + +/// https://discord.com/developers/docs/topics/gateway#get-gateway +pub const GatewayInfo = struct { + /// The WSS URL that can be used for connecting to the gateway + url: []const u8, +}; + +/// https://discord.com/developers/docs/events/gateway#session-start-limit-object +pub const GatewaySessionStartLimit = struct { + /// Total number of session starts the current user is allowed + total: u32, + /// Remaining number of session starts the current user is allowed + remaining: u32, + /// Number of milliseconds after which the limit resets + reset_after: u32, + /// Number of identify requests allowed per 5 seconds + max_concurrency: u32, +}; + +/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot +pub const GatewayBotInfo = struct { + url: []const u8, + /// The recommended number of shards to use when connecting + /// + /// See https://discord.com/developers/docs/topics/gateway#sharding + shards: u32, + /// Information on the current session start limit + /// + /// See https://discord.com/developers/docs/topics/gateway#session-start-limit-object + session_start_limit: ?GatewaySessionStartLimit, +}; + +pub const ShardDetails = struct { + /// Bot token which is used to connect to Discord */ + token: []const u8, + /// The URL of the gateway which should be connected to. + url: []const u8 = "wss://gateway.discord.gg", + /// The gateway version which should be used. + version: ?usize = 10, + /// The calculated intent value of the events which the shard should receive. + intents: Types.Intents, + /// Identify properties to use + properties: IdentifyProperties = default_identify_properties, +}; pub const debug = std.log.scoped(.@"discord.zig"); @@ -235,10 +299,10 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // interaction_create: null = null, // TODO: implement // invite_create: null = null, // TODO: implement // invite_delete: null = null, - message_create: ?*const fn (save: T, message: Discord.Message) anyerror!void = undefined, - message_update: ?*const fn (save: T, message: Discord.Message) anyerror!void = undefined, - message_delete: ?*const fn (save: T, log: Discord.MessageDelete) anyerror!void = undefined, - message_delete_bulk: ?*const fn (save: T, log: Discord.MessageDeleteBulk) 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, // TODO: implement // message_reaction_remove: null = null, // TODO: implement // message_reaction_remove_all: null = null, @@ -259,7 +323,7 @@ pub fn GatewayDispatchEvent(comptime T: type) type { // TODO: implement // message_poll_vote_add: null = null, // TODO: implement // message_poll_vote_remove: null = null, - ready: ?*const fn (save: T, data: Discord.Ready) anyerror!void = undefined, + ready: ?*const fn (save: T, data: Types.Ready) anyerror!void = undefined, // TODO: implement // resumed: null = null, any: ?*const fn (save: T, data: []const u8) anyerror!void = undefined, }; diff --git a/src/parser.zig b/src/parser.zig deleted file mode 100644 index 3632174..0000000 --- a/src/parser.zig +++ /dev/null @@ -1,91 +0,0 @@ -const zmpl = @import("zmpl"); -const Discord = @import("types.zig"); -const std = @import("std"); -const mem = std.mem; -const Snowflake = @import("shared.zig").Snowflake; -const Parser = @import("json"); - -pub fn parseUser(allocator: mem.Allocator, obj: []const u8) std.fmt.ParseIntError!Discord.User { - const user= try Parser.parse(Discord.User, allocator, obj); - return user.value; -} - -pub fn parseMember(allocator: mem.Allocator, obj: []const u8) std.fmt.ParseIntError!Discord.Member { - const member = try Parser.parse(Discord.Member, allocator, obj); - return member.value; -} - -/// caller must free the received referenced_message if any -pub fn parseMessage(allocator: mem.Allocator, obj: *zmpl.Data.Object) (mem.Allocator.Error || std.fmt.ParseIntError)!Discord.Message { - // parse mentions - const mentions_obj = obj.getT(.array, "mentions").?; - - var mentions = std.ArrayList(Discord.User).init(allocator); - defer mentions.deinit(); - - while (mentions_obj.iterator().next()) |m| { - try mentions.append(try parseUser(allocator, &m.object)); - } - - // parse member - const member = if (obj.getT(.object, "member")) |m| try parseMember(allocator, m) else null; - - // parse message - const author = try parseUser(allocator, obj.getT(.object, "author").?); - - // the referenced_message if any - const refmp = try allocator.create(Discord.Message); - - if (obj.getT(.object, "referenced_message")) |m| { - refmp.* = try parseMessage(allocator, m); - } else { - allocator.destroy(refmp); - } - - // parse message - const message = Discord.Message{ - // the id - .id = try Snowflake.fromRaw(obj.getT(.string, "id").?), - .tts = obj.getT(.boolean, "tts").?, - .mention_everyone = obj.getT(.boolean, "mention_everyone").?, - .pinned = obj.getT(.boolean, "pinned").?, - .type = @as(Discord.MessageTypes, @enumFromInt(obj.getT(.integer, "type").?)), - .channel_id = try Snowflake.fromRaw(obj.getT(.string, "channel_id").?), - .author = author, - .member = member, - .content = obj.getT(.string, "content"), - .timestamp = obj.getT(.string, "timestamp").?, - .guild_id = try Snowflake.fromMaybe(obj.getT(.string, "guild_id")), - .attachments = &[0]Discord.Attachment{}, - .edited_timestamp = null, - .mentions = try mentions.toOwnedSlice(), - .mention_roles = &[0]?[]const u8{}, - .mention_channels = &[0]?Discord.ChannelMention{}, - .embeds = &[0]Discord.Embed{}, - .reactions = &[0]?Discord.Reaction{}, - .nonce = if (obj.get("nonce")) |nonce| switch (nonce.*) { - .integer => |n| .{ .int = @as(isize, @intCast(n.value)) }, - .string => |n| .{ .string = n.value }, - .Null => null, - else => unreachable, - } else null, - .webhook_id = try Snowflake.fromMaybe(obj.getT(.string, "webhook_id")), - .activity = null, - .application = null, - .application_id = try Snowflake.fromMaybe(obj.getT(.string, "application_id")), - .message_reference = null, - .flags = if (obj.getT(.integer, "flags")) |fs| @as(Discord.MessageFlags, @bitCast(@as(u15, @intCast(fs)))) else null, - .stickers = &[0]?Discord.Sticker{}, - .message_snapshots = &[0]?Discord.MessageSnapshot{}, - .interaction_metadata = null, - .interaction = null, - .thread = null, - .components = null, - .sticker_items = &[0]?Discord.StickerItem{}, - .position = if (obj.getT(.integer, "position")) |p| @as(isize, @intCast(p)) else null, - .poll = null, - .call = null, - .referenced_message = refmp, - }; - return message; -} diff --git a/src/shard.zig b/src/shard.zig index 8c457f4..3fc7f6c 100644 --- a/src/shard.zig +++ b/src/shard.zig @@ -11,29 +11,27 @@ const http = std.http; // todo use this to read compressed messages const zlib = @import("zlib"); -const zmpl = @import("zmpl"); -const json_parse = @import("json"); -const Parser = @import("parser.zig"); +const zjson = @import("json"); const Self = @This(); -const Discord = @import("types.zig"); -const GatewayPayload = Discord.GatewayPayload; -const Opcode = Discord.GatewayOpcodes; -const Intents = Discord.Intents; +const GatewayPayload = @import("./structures/types.zig").GatewayPayload; +const Opcode = @import("./structures/types.zig").GatewayOpcodes; +const Intents = @import("./structures/types.zig").Intents; -const Shared = @import("shared.zig"); -const IdentifyProperties = Shared.IdentifyProperties; -const GatewayInfo = Shared.GatewayInfo; -const GatewayBotInfo = Shared.GatewayBotInfo; -const GatewaySessionStartLimit = Shared.GatewaySessionStartLimit; -const ShardDetails = Shared.ShardDetails; +const IdentifyProperties = @import("internal.zig").IdentifyProperties; +const GatewayInfo = @import("internal.zig").GatewayInfo; +const GatewayBotInfo = @import("internal.zig").GatewayBotInfo; +const GatewaySessionStartLimit = @import("internal.zig").GatewaySessionStartLimit; +const ShardDetails = @import("internal.zig").ShardDetails; -const Internal = @import("internal.zig"); -const Log = Internal.Log; -const GatewayDispatchEvent = Internal.GatewayDispatchEvent; -const Bucket = Internal.Bucket; -const default_identify_properties = Internal.default_identify_properties; +const Log = @import("internal.zig").Log; +const GatewayDispatchEvent = @import("internal.zig").GatewayDispatchEvent; +const Bucket = @import("internal.zig").Bucket; +const default_identify_properties = @import("internal.zig").default_identify_properties; + +const Types = @import("./structures/types.zig"); +const Snowflake = @import("./structures/snowflake.zig").Snowflake; pub const ShardSocketCloseCodes = enum(u16) { Shutdown = 3000, @@ -84,12 +82,6 @@ log: Log = .no, pub const JsonResolutionError = std.fmt.ParseIntError || std.fmt.ParseFloatError || json.ParseFromValueError || json.ParseError(json.Scanner); -fn parseJson(self: *Self, raw: []const u8) JsonResolutionError!zmpl.Data { - var data = zmpl.Data.init(self.allocator); - try data.fromJson(raw); - return data; -} - pub fn resumable(self: *Self) bool { return self.resume_gateway_url != null and self.session_id != null and @@ -112,8 +104,6 @@ inline fn gatewayUrl(self: ?*Self) []const u8 { /// identifies in order to connect to Discord and get the online status, this shall be done on hello perhaps pub fn identify(self: *Self, properties: ?IdentifyProperties) SendError!void { - self.logif("intents: {d}", .{self.details.intents.toRaw()}); - if (self.details.intents.toRaw() != 0) { const data = .{ .op = @intFromEnum(Opcode.Identify), @@ -208,7 +198,6 @@ inline fn _connect_ws(allocator: mem.Allocator, url: []const u8) !ws.Client { pub fn deinit(self: *Self) void { self.client.deinit(); - self.logif("killing the whole bot", .{}); } const ReadMessageError = mem.Allocator.Error || zlib.Error || json.ParseError(json.Scanner) || json.ParseFromValueError; @@ -220,7 +209,6 @@ fn readMessage(self: *Self, _: anytype) !void { while (try self.client.read()) |msg| { defer self.client.done(msg); - // self.logif("received: {?s}\n", .{msg.data}); try self.packets.appendSlice(msg.data); // end of zlib @@ -245,7 +233,6 @@ fn readMessage(self: *Self, _: anytype) !void { Opcode.Dispatch => { // maybe use threads and call it instead from there if (payload.t) |name| { - self.logif("logging event {s}", .{name}); self.sequence.store(payload.s orelse 0, .monotonic); try self.handleEvent(name, decompressed); } @@ -280,19 +267,16 @@ fn readMessage(self: *Self, _: anytype) !void { }, Opcode.HeartbeatACK => { // perhaps this needs a mutex? - self.logif("got heartbeat ack", .{}); self.rw_mutex.lock(); defer self.rw_mutex.unlock(); self.heart.lastBeat = std.time.milliTimestamp(); }, Opcode.Heartbeat => { - self.logif("sending requested heartbeat", .{}); self.ws_mutex.lock(); defer self.ws_mutex.unlock(); try self.send(false, .{ .op = @intFromEnum(Opcode.Heartbeat), .d = self.sequence.load(.monotonic) }); }, Opcode.Reconnect => { - self.logif("reconnecting", .{}); try self.reconnect(); }, Opcode.Resume => { @@ -310,9 +294,7 @@ fn readMessage(self: *Self, _: anytype) !void { self.session_id = resume_payload.session_id; }, Opcode.InvalidSession => {}, - else => { - self.logif("Unhandled {d} -- {s}", .{ payload.op, "none" }); - }, + else => {}, } } } @@ -325,23 +307,17 @@ pub fn heartbeat(self: *Self, initial_jitter: f64) SendHeartbeatError!void { while (true) { // basecase if (jitter == 1.0) { - // self.logif("zzz for {d}", .{self.heart.heartbeatInterval}); std.Thread.sleep(std.time.ns_per_ms * self.heart.heartbeatInterval); } else { const timeout = @as(f64, @floatFromInt(self.heart.heartbeatInterval)) * jitter; - self.logif("zzz for {d} and jitter {d}", .{ @as(u64, @intFromFloat(timeout)), jitter }); std.Thread.sleep(std.time.ns_per_ms * @as(u64, @intFromFloat(timeout))); - self.logif("end timeout", .{}); } - self.logif(">> ♥ and ack received: {d}", .{self.heart.lastBeat}); - self.rw_mutex.lock(); const last = self.heart.lastBeat; self.rw_mutex.unlock(); const seq = self.sequence.load(.monotonic); - self.logif("sending unrequested heartbeat", .{}); self.ws_mutex.lock(); try self.send(false, .{ .op = @intFromEnum(Opcode.Heartbeat), .d = seq }); self.ws_mutex.unlock(); @@ -384,7 +360,6 @@ pub fn disconnect(self: *Self) CloseError!void { pub const CloseError = mem.Allocator.Error || error{ReasonTooLong}; pub fn close(self: *Self, code: ShardSocketCloseCodes, reason: []const u8) CloseError!void { - self.logif("cooked closing ws conn...\n", .{}); // Implement reconnection logic here try self.client.close(.{ .code = @intFromEnum(code), //u16 @@ -400,196 +375,35 @@ pub fn send(self: *Self, _: bool, data: anytype) SendError!void { var string = std.ArrayList(u8).init(fba.allocator()); try std.json.stringify(data, .{}, string.writer()); - self.logif("{s}\n", .{string.items}); - try self.client.write(try string.toOwnedSlice()); } pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void { - var attempt = try self.parseJson(payload); - defer attempt.deinit(); + if (mem.eql(u8, name, "READY")) { + const ready = try zjson.parse(GatewayPayload(Types.Ready), self.allocator, payload); - const obj = attempt.getT(.object, "d").?; - if (std.ascii.eqlIgnoreCase(name, "ready")) { - self.resume_gateway_url = obj.getT(.string, "resume_gateway_url"); - - self.logif("new gateway url: {s}", .{self.gatewayUrl()}); - - const application = obj.getT(.object, "application"); - const user = try Parser.parseUser(self.allocator, obj.getT(.object, "user").?); - - var ready = Discord.Ready{ - .v = @as(isize, @intCast(obj.getT(.integer, "v").?)), - .user = user, - .shard = null, - .session_id = obj.getT(.string, "session_id").?, - .guilds = &[0]Discord.UnavailableGuild{}, - .resume_gateway_url = obj.getT(.string, "resume_gateway_url").?, - .application = if (application) |app| .{ - // todo - .name = null, - .description = null, - .rpc_origins = null, - .terms_of_service_url = null, - .privacy_policy_url = null, - .verify_key = null, - .primary_sku_id = null, - .slug = null, - .icon = null, - .bot_public = null, - .bot_require_code_grant = null, - .owner = null, - .team = null, - .guild_id = null, - .guild = null, - .cover_image = null, - .tags = null, - .install_params = null, - .integration_types_config = null, - .custom_install_url = null, - .role_connections_verification_url = null, - .approximate_guild_count = null, - .approximate_user_install_count = null, - .bot = null, - .redirect_uris = null, - .interactions_endpoint_url = null, - .flags = @as(Discord.ApplicationFlags, @bitCast(@as(u32, @intCast(app.getT(.integer, "flags").?)))), - .id = try Shared.Snowflake.fromRaw(app.getT(.string, "id").?), - } else null, - }; - - const shard = obj.getT(.array, "shard"); - - if (shard) |s| { - for (&ready.shard.?, s.items()) |*rs, ss| rs.* = switch (ss.*) { - .integer => |v| @as(isize, @intCast(v.value)), - else => unreachable, - }; - } - if (self.handler.ready) |event| try event(self, ready); - return; + try self.handler.ready.?(self, ready.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_CREATE")) { + const message = try zjson.parse(GatewayPayload(Types.Message), self.allocator, payload); - if (std.ascii.eqlIgnoreCase(name, "message_delete")) { - const data = Discord.MessageDelete{ - .id = try Shared.Snowflake.fromRaw(obj.getT(.string, "id").?), - .channel_id = try Shared.Snowflake.fromRaw(obj.getT(.string, "channel_id").?), - .guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")), - }; - - if (self.handler.message_delete) |event| try event(self, data); - return; + try self.handler.message_create.?(self, message.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_DELETE")) { + const data = try zjson.parse(GatewayPayload(Types.MessageDelete), self.allocator, payload); - if (std.ascii.eqlIgnoreCase(name, "message_delete_bulk")) { - var ids = std.ArrayList([]const u8).init(self.allocator); - defer ids.deinit(); - - while (obj.getT(.array, "ids").?.iterator().next()) |id| { - ids.append(id.string.value) catch unreachable; - } - - const data = Discord.MessageDeleteBulk{ - .ids = try Shared.Snowflake.fromMany(try ids.toOwnedSlice()), - .channel_id = try Shared.Snowflake.fromRaw(obj.getT(.string, "channel_id").?), - .guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")), - }; - - if (self.handler.message_delete_bulk) |event| try event(self, data); - return; + try self.handler.message_delete.?(self, data.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_UPDATE")) { + const message = try zjson.parse(GatewayPayload(Types.Message), self.allocator, payload); - if (std.ascii.eqlIgnoreCase(name, "message_update")) { - const message = try Parser.parseMessage(self.allocator, obj); - //defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr); - - if (self.handler.message_update) |event| try event(self, message); - return; + try self.handler.message_update.?(self, message.value.d.?); } + if (mem.eql(u8, name, "MESSAGE_DELETE_BULK")) { + const data = try zjson.parse(GatewayPayload(Types.MessageDeleteBulk), self.allocator, payload); - if (std.ascii.eqlIgnoreCase(name, "message_create")) { - self.logif("it worked {s}", .{name}); - const message = try Parser.parseMessage(self.allocator, obj); - //defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr); - self.logif("it worked {s} {?s}", .{ name, message.content }); - - if (self.handler.message_create) |event| try event(self, message); - return; + try self.handler.message_delete_bulk.?(self, data.value.d.?); } if (self.handler.any) |anyEvent| try anyEvent(self, payload); } - -/// highly experimental, do not use -pub fn loginWithEmail(allocator: mem.Allocator, settings: struct { login: []const u8, password: []const u8, run: GatewayDispatchEvent(*Self), log: Log }) !Self { - const AUTH_LOGIN = "https://discord.com/api/v9/auth/login"; - const WS_CONNECT = "gateway.discord.gg"; - - var body = std.ArrayList(u8).init(allocator); - defer body.deinit(); - - const AuthLoginResponse = struct { user_id: []const u8, token: []const u8, user_settings: struct { locale: []const u8, theme: []const u8 } }; - - var fetch_options = http.Client.FetchOptions{ - .location = http.Client.FetchOptions.Location{ - .url = AUTH_LOGIN, - }, - .extra_headers = &[_]http.Header{ - http.Header{ .name = "Accept", .value = "application/json" }, - http.Header{ .name = "Content-Type", .value = "application/json" }, - }, - .method = .POST, - .response_storage = .{ .dynamic = &body }, - }; - - fetch_options.payload = try json.stringifyAlloc(allocator, .{ - .login = settings.login, - .password = settings.password, - }, .{}); - - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); - - _ = try client.fetch(fetch_options); - - const response = try std.json.parseFromSliceLeaky(AuthLoginResponse, allocator, try body.toOwnedSlice(), .{}); - - return .{ - .allocator = allocator, - .details = ShardDetails{ - .token = response.token, - .intents = @bitCast(@as(u28, @intCast(0))), - }, - // maybe there is a better way to do this - .client = try Self._connect_ws(allocator, WS_CONNECT), - .session_id = undefined, - .options = ShardOptions{ .info = GatewayBotInfo{ .url = "wss://" ++ WS_CONNECT, .shards = 0, .session_start_limit = null }, .ratelimit_options = .{} }, - .handler = settings.run, - .log = settings.log, - .packets = std.ArrayList(u8).init(allocator), - .inflator = try zlib.Decompressor.init(allocator, .{ .header = .zlib_or_gzip }), - .properties = IdentifyProperties{ - .os = "Linux", - .browser = "Firefox", - .device = "", - .system_locale = "en-US", - .browser_user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0", - .browser_version = "132.0", - .os_version = "", - .referrer = "", - .referring_domain = "", - .referrer_current = "", - .referring_domain_current = "", - .release_channel = "stable", - .client_build_number = 342245, // TODO we should make an script to fetch this... - .client_event_source = null, - }, - }; -} - -inline fn logif(self: *Self, comptime format: []const u8, args: anytype) void { - switch (self.log) { - .yes => Internal.debug.info(format, args), - .no => {}, - } -} diff --git a/src/shared.zig b/src/shared.zig deleted file mode 100644 index e663ea0..0000000 --- a/src/shared.zig +++ /dev/null @@ -1,100 +0,0 @@ -const Intents = @import("types.zig").Intents; -const default_identify_properties = @import("internal.zig").default_identify_properties; -const std = @import("std"); - -pub const IdentifyProperties = struct { - /// Operating system the shard runs on. - os: []const u8, - /// The "browser" where this shard is running on. - browser: []const u8, - /// The device on which the shard is running. - device: []const u8, - - system_locale: ?[]const u8 = null, // TODO parse this - browser_user_agent: ?[]const u8 = null, - browser_version: ?[]const u8 = null, - os_version: ?[]const u8 = null, - referrer: ?[]const u8 = null, - referring_domain: ?[]const u8 = null, - referrer_current: ?[]const u8 = null, - referring_domain_current: ?[]const u8 = null, - release_channel: ?[]const u8 = null, - client_build_number: ?u64 = null, - client_event_source: ?[]const u8 = null, -}; - -/// https://discord.com/developers/docs/topics/gateway#get-gateway -pub const GatewayInfo = struct { - /// The WSS URL that can be used for connecting to the gateway - url: []const u8, -}; - -/// https://discord.com/developers/docs/events/gateway#session-start-limit-object -pub const GatewaySessionStartLimit = struct { - /// Total number of session starts the current user is allowed - total: u32, - /// Remaining number of session starts the current user is allowed - remaining: u32, - /// Number of milliseconds after which the limit resets - reset_after: u32, - /// Number of identify requests allowed per 5 seconds - max_concurrency: u32, -}; - -/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot -pub const GatewayBotInfo = struct { - url: []const u8, - /// The recommended number of shards to use when connecting - /// - /// See https://discord.com/developers/docs/topics/gateway#sharding - shards: u32, - /// Information on the current session start limit - /// - /// See https://discord.com/developers/docs/topics/gateway#session-start-limit-object - session_start_limit: ?GatewaySessionStartLimit, -}; - -pub const ShardDetails = struct { - /// Bot token which is used to connect to Discord */ - token: []const u8, - /// The URL of the gateway which should be connected to. - url: []const u8 = "wss://gateway.discord.gg", - /// The gateway version which should be used. - version: ?usize = 10, - /// The calculated intent value of the events which the shard should receive. - intents: Intents, - /// Identify properties to use - properties: IdentifyProperties = default_identify_properties, -}; - -pub const Snowflake = struct { - id: u64, - - pub fn fromMaybe(raw: ?[]const u8) std.fmt.ParseIntError!?Snowflake { - if (raw) |id| { - return .{ - .id = try std.fmt.parseInt(u64, id, 10), - }; - } else return null; - } - - pub fn fromRaw(raw: []const u8) std.fmt.ParseIntError!Snowflake { - return .{ - .id = try std.fmt.parseInt(u64, raw, 10), - }; - } - - pub fn fromMany(many: [][]const u8) ![]Snowflake { - var array = try std.BoundedArray(Snowflake, 64).init(many.len); - - for (many) |id| { - try array.append(try Snowflake.fromRaw(id)); - } - - return array.slice(); - } - - pub fn value(self: Snowflake) u64 { - return self.id; - } -}; diff --git a/src/structures/application.zig b/src/structures/application.zig new file mode 100644 index 0000000..a3fd400 --- /dev/null +++ b/src/structures/application.zig @@ -0,0 +1,93 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const ApplicationFlags = @import("shared.zig").ApplicationFlags; +const OAuth2Scope = @import("shared.zig").OAuth2Scope; +const Partial = @import("partial.zig").Partial; +const User = @import("user.zig").User; +const Team = @import("team.zig").Team; +const Guild = @import("guild.zig").Guild; + +/// https://discord.com/developers/docs/resources/application#application-object +pub const Application = struct { + /// The name of the app + name: []const u8, + /// The description of the app + description: []const u8, + /// An array of rpc origin urls, if rpc is enabled + 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 + privacy_policy_url: ?[]const u8, + /// The hex encoded key for verification in interactions and the GameSDK's GetTicket + verify_key: []const u8, + ///If this application is a game sold on , this field will be the id of the "Game SKU" that is created, if exists + primary_sku_id: ?Snowflake, + ///If this application is a game sold on , this field will be the URL slug that links to the store page + slug: ?[]const u8, + /// The application's public flags + flags: ?ApplicationFlags, + /// The id of the app + id: Snowflake, + /// The icon hash of the app + icon: ?[]const u8, + /// When false only app owner can join the app's bot to guilds + bot_public: bool, + /// When true the app's bot will only join upon completion of the full oauth2 code grant flow + bot_require_code_grant: bool, + /// Partial user object containing info on the owner of the application + owner: ?Partial(User), + /// If the application belongs to a team, this will be a list of the members of that team + team: ?Team, + /// Guild associated with the app. For example, a developer support server. + guild_id: ?Snowflake, + /// A partial object of the associated guild + guild: ?Partial(Guild), + ///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, + /// settings for the application's default in-app authorization link, if enabled + install_params: ?InstallParams, + // Default scopes and permissions for each supported installation context. + // partial? + // integration_types_config: ?Record(ApplicationIntegrationType, ApplicationIntegrationTypeConfiguration), + + /// the application's default custom authorization link, if enabled + custom_install_url: ?[]const u8, + /// the application's role connection verification entry point, which when configured will render the app as a verification method in the guild role verification configuration + role_connections_verification_url: ?[]const u8, + /// An approximate count of the app's guild membership. + approximate_guild_count: ?isize, + /// Approximate count of users that have installed the app. + approximate_user_install_count: ?isize, + /// 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, + /// Interactions endpoint URL for the app + interactions_endpoint_url: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/application#application-object-application-integration-type-configuration-object +pub const ApplicationIntegrationTypeConfiguration = struct { + /// + /// Install params for each installation context's default in-app authorization link + /// + /// https://discord.com/developers/docs/resources/application#install-params-object-install-params-structure + /// + oauth2_install_params: ?InstallParams, +}; + +pub const ApplicationIntegrationType = enum(u4) { + /// App is installable to servers + GuildInstall = 0, + /// App is installable to users + UserInstall = 1, +}; + +pub const InstallParams = struct { + /// Scopes to add the application to the server with + scopes: []OAuth2Scope, + /// Permissions to request for the bot role + permissions: []const u8, +}; diff --git a/src/structures/attachment.zig b/src/structures/attachment.zig new file mode 100644 index 0000000..6d1f737 --- /dev/null +++ b/src/structures/attachment.zig @@ -0,0 +1,34 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const AttachmentFlags = @import("shared.zig").AttachmentFlags; + +/// https://discord.com/developers/docs/resources/channel#attachment-object +pub const Attachment = struct { + /// Name of file attached + filename: []const u8, + /// The title of the file + title: ?[]const u8, + /// The attachment's [media type](https://en.wikipedia.org/wiki/Media_type) + content_type: ?[]const u8, + /// Size of file in bytes + size: isize, + /// Source url of file + url: []const u8, + /// A proxied url of file + proxy_url: []const u8, + /// Attachment id + id: Snowflake, + /// description for the file (max 1024 characters) + description: ?[]const u8, + /// Height of file (if image) + height: ?isize, + /// Width of file (if image) + width: ?isize, + /// whether this attachment is ephemeral. Ephemeral attachments will automatically be removed after a set period of time. Ephemeral attachments on messages are guaranteed to be available as long as the message itself exists. + ephemeral: ?bool, + /// The duration of the audio file for a voice message + duration_secs: ?isize, + /// A base64 encoded bytearray representing a sampled waveform for a voice message + waveform: ?[]const u8, + /// Attachment flags combined as a bitfield + flags: ?AttachmentFlags, +}; diff --git a/src/structures/auditlog.zig b/src/structures/auditlog.zig new file mode 100644 index 0000000..9c278c6 --- /dev/null +++ b/src/structures/auditlog.zig @@ -0,0 +1,132 @@ +const Webhook = @import("webhook.zig").Webhook; +const User = @import("user.zig").User; +const Channel = @import("channel.zig").Channel; +const ScheduledEvent = @import("scheduled_event.zig").ScheduledEvent; +const AutoModerationRule = @import("automod.zig").AutoModerationRule; +const Integration = @import("integration.zig").Integration; +const Snowflake = @import("snowflake.zig").Snowflake; +const AuditLogEvents = @import("shared.zig").AuditLogEvents; +const Partial = @import("partial.zig").Partial; +const ApplicationCommand = @import("command.zig").ApplicationCommand; + +/// https://discord.com/developers/docs/resources/audit-log#audit-log-object +pub const AuditLog = struct { + /// List of webhooks found in the audit log + webhooks: []Webhook, + /// List of users found in the audit log + users: []User, + /// List of audit log entries, sorted from most to least recent + audit_log_entries: []AuditLogEntry, + /// List of partial integration objects + integrations: []Partial(Integration), + /// + /// List of threads found in the audit log. + /// Threads referenced in `THREAD_CREATE` and `THREAD_UPDATE` events are included in the threads map since archived threads might not be kept in memory by clients. + /// + threads: []Channel, + /// List of guild scheduled events found in the audit log + guild_scheduled_events: []?ScheduledEvent, + /// List of auto moderation rules referenced in the audit log + auto_moderation_rules: []?AutoModerationRule, + /// List of application commands referenced in the audit log + application_commands: []ApplicationCommand, +}; + +/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure +pub const AuditLogEntry = struct { + /// ID of the affected entity (webhook, user, role, etc.) + target_id: ?Snowflake, + /// Changes made to the `target_id` + /// TODO: change this + changes: []?AuditLogChange(noreturn), + /// User or app that made the changes + user_id: ?Snowflake, + /// ID of the entry + id: Snowflake, + /// Type of action that occurred + action_type: AuditLogEvents, + /// Additional info for certain event types + options: ?OptionalAuditEntryInfo, + /// Reason for the change (1-512 characters) + reason: ?[]const u8, +}; + +pub fn AuditLogChange(comptime T: type) type { + return T; +} + +/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info +pub const OptionalAuditEntryInfo = struct { + /// + /// ID of the app whose permissions were targeted. + /// + /// Event types: `APPLICATION_COMMAND_PERMISSION_UPDATE`, + /// + application_id: ?Snowflake, + /// + /// Name of the Auto Moderation rule that was triggered. + /// + /// Event types: `AUTO_MODERATION_BLOCK_MESSAGE`, `AUTO_MODERATION_FLAG_TO_CHANNEL`, `AUTO_MODERATION_USER_COMMUNICATION_DISABLED`, + /// + auto_moderation_rule_name: ?[]const u8, + /// + /// Trigger type of the Auto Moderation rule that was triggered. + /// + /// Event types: `AUTO_MODERATION_BLOCK_MESSAGE`, `AUTO_MODERATION_FLAG_TO_CHANNEL`, `AUTO_MODERATION_USER_COMMUNICATION_DISABLED`, + /// + auto_moderation_rule_trigger_type: ?[]const u8, + /// + /// Channel in which the entities were targeted. + /// + /// Event types: `MEMBER_MOVE`, `MESSAGE_PIN`, `MESSAGE_UNPIN`, `MESSAGE_DELETE`, `STAGE_INSTANCE_CREATE`, `STAGE_INSTANCE_UPDATE`, `STAGE_INSTANCE_DELETE`, + /// + channel_id: ?Snowflake, + /// + /// isize of entities that were targeted. + /// + /// Event types: `MESSAGE_DELETE`, `MESSAGE_BULK_DELETE`, `MEMBER_DISCONNECT`, `MEMBER_MOVE`, + /// + count: ?[]const u8, + /// + /// isize of days after which inactive members were kicked. + /// + /// Event types: `MEMBER_PRUNE`, + /// + delete_member_days: ?[]const u8, + /// + /// ID of the overwritten entity. + /// + /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, + /// + id: ?Snowflake, + /// + /// isize of members removed by the prune. + /// + /// Event types: `MEMBER_PRUNE`, + /// + members_removed: ?[]const u8, + /// + /// ID of the message that was targeted. + /// + /// Event types: `MESSAGE_PIN`, `MESSAGE_UNPIN`, `STAGE_INSTANCE_CREATE`, `STAGE_INSTANCE_UPDATE`, `STAGE_INSTANCE_DELETE`, + /// + message_id: ?Snowflake, + /// + /// Name of the role if type is "0" (not present if type is "1"). + /// + /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, + /// + role_name: ?[]const u8, + /// + /// Type of overwritten entity - "0", for "role", or "1" for "member". + /// + /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, + /// + type: ?[]const u8, + /// + /// The type of integration which performed the action + /// + /// Event types: `MEMBER_KICK`, `MEMBER_ROLE_UPDATE`, + /// + integration_type: ?[]const u8, +}; diff --git a/src/structures/automod.zig b/src/structures/automod.zig new file mode 100644 index 0000000..d3c9f50 --- /dev/null +++ b/src/structures/automod.zig @@ -0,0 +1,170 @@ +const Snowflake = @import("snowflake.zig").Snowflake; + +/// https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object +pub const AutoModerationRule = struct { + /// The id of this rule + id: Snowflake, + /// The guild id of the rule + guild_id: Snowflake, + /// The name of the rule + name: []const u8, + /// The id of the user who created this rule. + creator_id: Snowflake, + /// Indicates in what event context a rule should be checked. + event_type: AutoModerationEventTypes, + /// The type of trigger for this rule + trigger_type: AutoModerationTriggerTypes, + /// The metadata used to determine whether a rule should be triggered. + trigger_metadata: AutoModerationRuleTriggerMetadata, + /// Actions which will execute whenever a rule is triggered. + actions: []AutoModerationAction, + /// Whether the rule is enabled. + enabled: bool, + /// The role ids that are whitelisted. Max 20. + exempt_roles: [][]const u8, + /// The channel ids that are whitelisted. Max 50. + exempt_channels: [][]const u8, +}; + +pub const AutoModerationEventTypes = enum(u4) { + /// When a user sends a message + MessageSend = 1, + /// Wen a member edits their profile + MemberUpdate, +}; + +pub const AutoModerationTriggerTypes = enum(u4) { + /// Check if content contains words from a user defined list of keywords. Max 6 per guild + Keyword = 1, + /// Check if content represents generic spam. Max 1 per guild + Spam = 3, + /// Check if content contains words from internal pre-defined word sets. Max 1 per guild + KeywordPreset, + /// Check if content contains more unique mentions than allowed. Max 1 per guild + MentionSpam, + /// Check if member profile contains words from a user defined list of keywords. Max 1 per guild + MemberProfile, +}; + +pub const AutoModerationRuleTriggerMetadata = struct { + /// + /// The keywords needed to match. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MemberProfile};. + /// + /// Can have up to 1000 elements in the array and each []const u8 can have up to 60 characters + /// + keyword_filter: []?[]const u8, + /// + /// Regular expression patterns which will be matched against content. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MemberProfile};. + /// + /// Can have up to 10 elements in the array and each []const u8 can have up to 260 characters + /// + regex_patterns: [][]const u8, + /// + /// The pre-defined lists of words to match from. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.KeywordPreset};. + /// + presets: []?AutoModerationRuleTriggerMetadataPresets, + /// + /// The substrings which will exempt from triggering the preset trigger type. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.Keyword};, {@link AutoModerationTriggerTypes.KeywordPreset}; and {@link AutoModerationTriggerTypes.MemberProfile};. + /// + /// 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, + /// + /// Total isize of mentions (role & user) allowed per message. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.MentionSpam};. + /// + /// Maximum of 50 + /// + mention_total_limit: ?isize, + /// + /// Whether to automatically detect mention raids. + /// + /// @remarks + /// Only present with {@link AutoModerationTriggerTypes.MentionSpam};. + /// + mention_raid_protection_enabled: ?bool, +}; + +pub const AutoModerationRuleTriggerMetadataPresets = enum(u4) { + /// Words that may be considered forms of swearing or cursing + Profanity = 1, + /// Words that refer to sexually explicit behavior or activity + SexualContent, + /// Personal insults or words that may be considered hate speech + Slurs, +}; + +pub const AutoModerationAction = struct { + /// The type of action to take when a rule is triggered + type: AutoModerationActionType, + /// additional metadata needed during execution for this specific action type + metadata: AutoModerationActionMetadata, +}; + +pub const AutoModerationActionType = enum(u4) { + /// Blocks the content of a message according to the rule + BlockMessage = 1, + /// Logs user content to a specified channel + SendAlertMessage, + /// + /// Times out user for specified duration + /// + /// @remarks + /// A timeout action can only be set up for {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MentionSpam}; rules. + /// + /// The `MODERATE_MEMBERS` permission is required to use the timeout action type. + /// + Timeout, + /// prevents a member from using text, voice, or other interactions + BlockMemberInteraction, +}; + +pub const AutoModerationActionMetadata = struct { + /// The id of channel to which user content should be logged. Only in ActionType.SendAlertMessage + channel_id: ?Snowflake, + /// Additional explanation that will be shown to members whenever their message is blocked. Maximum of 150 characters. Only supported for AutoModerationActionType.BlockMessage + custom_message: ?[]const u8, + /// Timeout duration in seconds maximum of 2419200 seconds (4 weeks). Only supported for TriggerType.Keyword && Only in ActionType.Timeout + duration_seconds: ?isize, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution-auto-moderation-action-execution-event-fields +pub const AutoModerationActionExecution = struct { + /// The id of the guild + guild_id: Snowflake, + /// The id of the rule that was executed + rule_id: Snowflake, + /// The id of the user which generated the content which triggered the rule + user_id: Snowflake, + /// The content from the user + content: []const u8, + /// Action which was executed + action: AutoModerationAction, + /// The trigger type of the rule that was executed. + rule_trigger_type: AutoModerationTriggerTypes, + /// The id of the channel in which user content was posted + channel_id: ?Snowflake, + /// The id of the message. Will not exist if message was blocked by automod or content was not part of any message + message_id: ?Snowflake, + /// The id of any system auto moderation messages posted as a result of this action + alert_system_message_id: ?Snowflake, + /// The word or phrase that triggerred the rule. + matched_keyword: ?[]const u8, + /// The substring in content that triggered the rule + matched_content: ?[]const u8, +}; diff --git a/src/structures/channel.zig b/src/structures/channel.zig new file mode 100644 index 0000000..2322f15 --- /dev/null +++ b/src/structures/channel.zig @@ -0,0 +1,188 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const Member = @import("member.zig").Member; + +const AllowedMentionsTypes = @import("shared.zig").AllowedMentionsTypes; +const ChannelTypes = @import("shared.zig").ChannelTypes; +const OverwriteTypes = @import("shared.zig").OverwriteTypes; +const ChannelFlags = @import("shared.zig").ChannelFlags; +const VideoQualityModes = @import("shared.zig").VideoQualityModes; +const SortOrderTypes = @import("shared.zig").SortOrderTypes; +const User = @import("user.zig").User; +const ThreadMetadata = @import("thread.zig").ThreadMetadata; +const ThreadMember = @import("thread.zig").ThreadMember; +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, + /// 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, + /// Array of user_ids to mention (Max size of 100) + users: []?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#typing-start +pub const TypingStart = struct { + /// Unix time (in seconds) of when the user started typing + timestamp: isize, + /// id of the channel + channel_id: Snowflake, + /// id of the guild + guild_id: ?Snowflake, + /// id of the user + user_id: Snowflake, + /// The member who started typing if this happened in a guild + member: ?Member, +}; + +/// https://discord.com/developers/docs/resources/channel#channel-object +pub const Channel = struct { + /// The id of the channel + id: Snowflake, + /// The type of channel + type: ChannelTypes, + /// The id of the guild + guild_id: ?Snowflake, + /// 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, + /// 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) + topic: ?[]const u8, + /// Whether the channel is nsfw + nsfw: ?bool, + /// The id of the last message sent in this channel (may not point to an existing or valid message) + last_message_id: ?Snowflake, + /// The bitrate (in bits) of the voice or stage channel + bitrate: ?isize, + /// The user limit of the voice or stage channel + user_limit: ?isize, + /// 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, + /// icon hash of the group DM + icon: ?[]const u8, + /// Id of the creator of the thread + owner_id: ?Snowflake, + /// Application id of the group DM creator if it is bot-created + application_id: ?Snowflake, + /// For group DM channels: whether the channel is managed by an application via the `gdm.join` OAuth2 scope., + managed: ?bool, + /// For guild channels: Id of the parent category for a channel (each parent category can contain up to 50 channels), for threads: id of the text channel this thread was created, + parent_id: ?Snowflake, + /// When the last pinned message was pinned. This may be null in events such as GUILD_CREATE when a message is not pinned. + last_pin_timestamp: ?[]const u8, + /// Voice region id for the voice or stage channel, automatic when set to null + rtc_region: ?[]const u8, + /// The camera video quality mode of the voice channel, 1 when not present + video_quality_mode: ?VideoQualityModes, + /// An approximate count of messages in a thread, stops counting at 50 + message_count: ?isize, + /// An approximate count of users in a thread, stops counting at 50 + member_count: ?isize, + /// Thread-specific fields not needed by other channels + thread_metadata: ?ThreadMetadata, + /// Thread member object for the current user, if they have joined the thread, only included on certain API endpoints + member: ?ThreadMember, + /// Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080, + default_auto_archive_duration: ?isize, + /// computed permissions for the invoking user in the channel, including overwrites, only included when part of the resolved data received on a slash command interaction. This does not include implicit permissions, which may need to be checked separately. + permissions: ?[]const u8, + /// The flags of the channel + flags: ?ChannelFlags, + /// 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, + /// The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel + 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. + default_thread_rate_limit_per_user: ?isize, + /// the default sort order type used to order posts in GUILD_FORUM channels. Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin + default_sort_order: ?SortOrderTypes, + /// the default forum layout view used to display posts in `GUILD_FORUM` channels. Defaults to `0`, which indicates a layout view has not been set by a channel admin + default_forum_layout: ?ForumLayout, + /// When a thread is created this will be true on that channel payload for the thread. + newly_created: ?bool, +}; + +/// https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-structure +pub const WelcomeScreen = struct { + /// The server description shown in the welcome screen + description: ?[]const u8, + /// The channels shown in the welcome screen, up to 5 + welcome_channels: []WelcomeScreenChannel, +}; + +/// https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure +pub const WelcomeScreenChannel = struct { + /// The description shown for the channel + description: []const u8, + /// The channel's id + channel_id: Snowflake, + /// The emoji id, if the emoji is custom + emoji_id: ?Snowflake, + /// The emoji name if custom, the unicode character if standard, or `null` if no emoji is set + emoji_name: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/stage-instance#auto-closing-stage-instance-structure +pub const StageInstance = struct { + /// The topic of the Stage instance (1-120 characters) + topic: []const u8, + /// The id of this Stage instance + id: Snowflake, + /// The guild id of the associated Stage channel + guild_id: Snowflake, + /// The id of the associated Stage channel + channel_id: Snowflake, + /// The id of the scheduled event for this Stage instance + guild_scheduled_event_id: ?Snowflake, +}; + +pub const Overwrite = struct { + /// Either 0 (role) or 1 (member) + type: OverwriteTypes, + /// Role or user id + id: Snowflake, + /// Permission bit set + allow: ?[]const u8, + /// Permission bit set + deny: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#followed-channel-object +pub const FollowedChannel = struct { + /// Source message id + channel_id: Snowflake, + /// Created target webhook id + webhook_id: Snowflake, +}; + +pub const ForumTag = struct { + /// The id of the tag + id: Snowflake, + /// The name of the tag (0-20 characters) + name: []const u8, + /// Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission + moderated: bool, + /// The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, +}; + +pub const DefaultReactionEmoji = struct { + /// The id of a guild's custom emoji + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, +}; diff --git a/src/structures/command.zig b/src/structures/command.zig new file mode 100644 index 0000000..1634f71 --- /dev/null +++ b/src/structures/command.zig @@ -0,0 +1,260 @@ +const ApplicationCommandTypes = @import("shared.zig").ApplicationCommandTypes; +const InteractionContextType = @import("integration.zig").InteractionContextType; +const Snowflake = @import("snowflake.zig").Snowflake; +const ApplicationCommandPermissionTypes = @import("shared.zig").ApplicationCommandPermissionTypes; +const ApplicationIntegrationType = @import("application.zig").ApplicationIntegrationType; +const ApplicationCommandOptionTypes = @import("shared.zig").ApplicationCommandOptionTypes; +const ChannelTypes = @import("shared.zig").ChannelTypes; + +/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure +pub const ApplicationCommand = struct { + /// Type of command, defaults to `ApplicationCommandTypes.ChatInput` + type: ?ApplicationCommandTypes, + /// + /// Name of command, 1-32 characters. + /// `ApplicationCommandTypes.ChatInput` command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. + /// If there is a lowercase variant of any letters used, you must use those. + /// Characters with no lowercase variants and/or uncased letters are still allowed. + /// ApplicationCommandTypes.User` and `ApplicationCommandTypes.Message` commands may be mixed case and can include spaces. + /// + name: []const u8, + /// Localization object for `name` field. Values follow the same restrictions as `name` + name_localizations: ?[]const u8, //?Localization, + /// Description for `ApplicationCommandTypes.ChatInput` commands, 1-100 characters. + description: ?[]const u8, + /// 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, + /// Set of permissions represented as a bit set + default_member_permissions: ?[]const u8, + /// + /// Installation contexts where the command is available + /// + /// @remarks + /// This value is available only for globally-scoped commands + /// Defaults to the application configured contexts + /// + integration_types: []?ApplicationIntegrationType, + /// + /// Interaction context(s) where the command can be used + /// + /// @remarks + /// This value is available only for globally-scoped commands + /// By default, all interaction context types included for new commands. + /// + contexts: []?InteractionContextType, + /// + /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + /// + /// @deprecated use {@link contexts}; instead + /// + dm_permission: ?bool, + /// Indicates whether the command is age-restricted, defaults to false + nsfw: ?bool, + /// Auto incrementing version identifier updated during substantial record changes + version: ?[]const u8, + /// + ///Determines whether the interaction is handled by the app's interactions handler or by + /// + /// @remarks + /// This can only be set for application commands of type `PRIMARY_ENTRY_POINT` for applications with the `EMBEDDED` flag (i.e. applications that have an Activity). + /// + handler: ?InteractionEntryPointCommandHandlerType, + /// Unique ID of command + id: Snowflake, + /// ID of the parent application + application_id: Snowflake, + /// Guild id of the command, if not global + guild_id: ?Snowflake, +}; + +pub const CreateApplicationCommand = struct { + /// Type of command, defaults to `ApplicationCommandTypes.ChatInput` + type: ?ApplicationCommandTypes, + /// + /// Name of command, 1-32 characters. + /// `ApplicationCommandTypes.ChatInput` command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. + /// If there is a lowercase variant of any letters used, you must use those. + /// Characters with no lowercase variants and/or uncased letters are still allowed. + /// ApplicationCommandTypes.User` and `ApplicationCommandTypes.Message` commands may be mixed case and can include spaces. + /// + name: []const u8, + /// Localization object for `name` field. Values follow the same restrictions as `name` + name_localizations: []const u8, //?Localization, + /// Description for `ApplicationCommandTypes.ChatInput` commands, 1-100 characters. + description: ?[]const u8, + /// 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, + /// Set of permissions represented as a bit set + default_member_permissions: ?[]const u8, + /// + /// Installation contexts where the command is available + /// + /// @remarks + /// This value is available only for globally-scoped commands + /// Defaults to the application configured contexts + /// + integration_types: []?ApplicationIntegrationType, + /// + /// Interaction context(s) where the command can be used + /// + /// @remarks + /// This value is available only for globally-scoped commands + /// By default, all interaction context types included for new commands. + /// + contexts: []?InteractionContextType, + /// + /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + /// + /// @deprecated use {@link contexts}; instead + /// + dm_permission: ?bool, + /// Indicates whether the command is age-restricted, defaults to false + nsfw: ?bool, + /// Auto incrementing version identifier updated during substantial record changes + version: ?[]const u8, + /// + ///Determines whether the interaction is handled by the app's interactions handler or by + /// + /// @remarks + /// This can only be set for application commands of type `PRIMARY_ENTRY_POINT` for applications with the `EMBEDDED` flag (i.e. applications that have an Activity). + /// + handler: ?InteractionEntryPointCommandHandlerType, +}; + +pub const InteractionEntryPointCommandHandlerType = enum(u4) { + /// The app handles the interaction using an interaction token + AppHandler = 1, + /// handles the interaction by launching an Activity and sending a follow-up message without coordinating with the app + LaunchActivity = 2, +}; + +/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure +pub const ApplicationCommandOption = struct { + /// Type of option + type: ApplicationCommandOptionTypes, + /// + /// Name of command, 1-32 characters. + /// + /// @remarks + ///This value should be unique within an array of {@link ApplicationCommandOption}; + /// + /// {@link ApplicationCommandTypes.ChatInput | ChatInput}; command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. + /// If there is a lowercase variant of any letters used, you must use those. + /// Characters with no lowercase variants and/or uncased letters are still allowed. + /// + /// {@link ApplicationCommandTypes.User | User}; and {@link ApplicationCommandTypes.Message | Message}; commands may be mixed case and can include spaces. + /// + name: []const u8, + /// Localization object for the `name` field. Values follow the same restrictions as `name` + name_localizations: []const u4, //?Localization, + /// 1-100 character description + description: []const u8, + /// Localization object for the `description` field. Values follow the same restrictions as `description` + description_localizations: ?[]const u8, //?Localization, + /// + /// If the parameter is required or optional. default `false` + /// + /// @remarks + /// Valid in all option types except {@link ApplicationCommandOptionTypes.SubCommand | SubCommand}; and {@link ApplicationCommandOptionTypes.SubCommandGroup | SubCommandGroup}; + /// + required: ?bool, + /// + /// Choices for the option from which the user can choose, max 25 + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8};, {@link ApplicationCommandOptionTypes.Integer | Integer};, or {@link ApplicationCommandOptionTypes.isize | isize}; + /// + /// If you provide an array of choices, they will be the ONLY accepted values for this option + /// + 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, + /// + /// If autocomplete interactions are enabled for this option. + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8};, {@link ApplicationCommandOptionTypes.Integer | Integer};, or {@link ApplicationCommandOptionTypes.isize | isize}; + /// + ///When {@link ApplicationCommandOption.choices | choices}; are provided, this may not be set to true + /// + autocomplete: ?bool, + /// + /// The channels shown will be restricted to these types + /// + /// @remarks + /// Only valid in option of type {@link ApplicationCommandOptionTypes.Channel | Channel}; + /// + channel_types: []?ChannelTypes, + /// + /// The minimum permitted value + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.Integer | Integer}; or {@link ApplicationCommandOptionTypes.isize | isize}; + /// + min_value: ?isize, + /// + /// The maximum permitted value + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.Integer | Integer}; or {@link ApplicationCommandOptionTypes.isize | isize}; + /// + max_value: ?isize, + /// + /// The minimum permitted length, should be in the range of from 0 to 600 + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8}; + /// + min_length: ?isize, + /// + /// The maximum permitted length, should be in the range of from 0 to 600 + /// + /// @remarks + /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8}; + /// + max_length: ?isize, +}; + +/// https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object +pub const ApplicationCommandOptionChoice = struct { + /// 1-100 character choice name + name: []const u8, + /// Localization object for the `name` field. Values follow the same restrictions as `name` + name_localizations: []const u8, //?Localization, + /// Value for the choice, up to 100 characters if []const u8 + value: union(enum) { + string: []const u8, + integer: isize, + }, +}; + +/// https://discord.com/developers/docs/interactions/slash-commands#guildapplicationcommandpermissions +pub const GuildApplicationCommandPermissions = struct { + /// ID of the command or the application ID. When the `id` field is the application ID instead of a command ID, the permissions apply to all commands that do not contain explicit overwrites. + id: Snowflake, + /// ID of the application the command belongs to + application_id: Snowflake, + /// ID of the guild + guild_id: Snowflake, + /// Permissions for the command in the guild, max of 100 + permissions: []ApplicationCommandPermissions, +}; + +/// https://discord.com/developers/docs/interactions/slash-commands#applicationcommandpermissions +pub const ApplicationCommandPermissions = struct { + /// ID of the role, user, or channel. It can also be a permission constant + id: Snowflake, + /// ApplicationCommandPermissionTypes.Role, ApplicationCommandPermissionTypes.User, or ApplicationCommandPermissionTypes.Channel + type: ApplicationCommandPermissionTypes, + /// `true` to allow, `false`, to disallow + permission: bool, +}; diff --git a/src/structures/embed.zig b/src/structures/embed.zig new file mode 100644 index 0000000..59614ba --- /dev/null +++ b/src/structures/embed.zig @@ -0,0 +1,106 @@ +const EmbedTypes = @import("shared.zig").EmbedTypes; + +/// https://discord.com/developers/docs/resources/channel#embed-object +pub const Embed = struct { + /// Title of embed + title: ?[]const u8, + /// Type of embed (always "rich" for webhook embeds) + type: ?EmbedTypes, + /// Description of embed + description: ?[]const u8, + /// Url of embed + url: ?[]const u8, + /// Color code of the embed + color: ?isize, + /// Timestamp of embed content + timestamp: ?[]const u8, + /// Footer information + footer: ?EmbedFooter, + /// Image information + image: ?EmbedImage, + /// Thumbnail information + thumbnail: ?EmbedThumbnail, + /// Video information + video: ?EmbedVideo, + /// Provider information + provider: ?EmbedProvider, + /// Author information + author: ?EmbedAuthor, + /// Fields information + fields: []?EmbedField, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure +pub const EmbedAuthor = struct { + /// Name of author + name: []const u8, + /// Url of author + url: ?[]const u8, + /// Url of author icon (only supports http(s) and attachments) + icon_url: ?[]const u8, + /// A proxied url of author icon + proxy_icon_url: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure +pub const EmbedField = struct { + /// Name of the field + name: []const u8, + /// Value of the field + value: []const u8, + /// Whether or not this field should display inline + @"inline": ?bool, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure +pub const EmbedFooter = struct { + /// Footer text + text: []const u8, + /// Url of footer icon (only supports http(s) and attachments) + icon_url: ?[]const u8, + /// A proxied url of footer icon + proxy_icon_url: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure +pub const EmbedImage = struct { + /// Source url of image (only supports http(s) and attachments) + url: []const u8, + /// A proxied url of the image + proxy_url: ?[]const u8, + /// Height of image + height: ?isize, + /// Width of image + width: ?isize, +}; + +pub const EmbedProvider = struct { + /// Name of provider + name: ?[]const u8, + /// Url of provider + url: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure +pub const EmbedThumbnail = struct { + /// Source url of thumbnail (only supports http(s) and attachments) + url: []const u8, + /// A proxied url of the thumbnail + proxy_url: ?[]const u8, + /// Height of thumbnail + height: ?isize, + /// Width of thumbnail + width: ?isize, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure +pub const EmbedVideo = struct { + /// Source url of video + url: ?[]const u8, + /// A proxied url of the video + proxy_url: ?[]const u8, + /// Height of video + height: ?isize, + /// Width of video + width: ?isize, +}; diff --git a/src/structures/emoji.zig b/src/structures/emoji.zig new file mode 100644 index 0000000..f4337be --- /dev/null +++ b/src/structures/emoji.zig @@ -0,0 +1,22 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const User = @import("user.zig").User; + +/// https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure +pub const Emoji = struct { + /// Emoji name (can only be null in reaction emoji objects) + name: ?[]const u8, + /// Emoji id + id: ?Snowflake, + /// Roles allowed to use this emoji + roles: []?[]const u8, + /// User that created this emoji + user: ?User, + /// Whether this emoji must be wrapped in colons + require_colons: ?bool, + /// Whether this emoji is managed + managed: ?bool, + /// Whether this emoji is animated + animated: ?bool, + /// Whether this emoji can be used, may be false due to loss of Server Boosts + available: ?bool, +}; diff --git a/src/structures/events.zig b/src/structures/events.zig new file mode 100644 index 0000000..0a058c6 --- /dev/null +++ b/src/structures/events.zig @@ -0,0 +1,916 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const MemberWithUser = @import("member.zig").MemberWithUser; +const PresenceUpdate = @import("gateway.zig").PresenceUpdate; +const User = @import("user.zig").User; +const Attachment = @import("attachment.zig").Attachment; +const Role = @import("role.zig").Role; +const Application = @import("application.zig").Application; +const AvatarDecorationData = @import("user.zig").AvatarDecorationData; +const SkuFlags = @import("shared.zig").SkuFlags; +const ApplicationFlags = @import("shared.zig").ApplicationFlags; +const TargetTypes = @import("shared.zig").TargetTypes; +const ChannelTypes = @import("shared.zig").ChannelTypes; +const MessageFlags = @import("shared.zig").MessageFlags; +const OverwriteTypes = @import("shared.zig").OverwriteTypes; +const Emoji = @import("emoji.zig").Emoji; +const Channel = @import("channel.zig").Channel; +const Overwrite = @import("channel.zig").Overwrite; +const ThreadMember = @import("thread.zig").ThreadMember; +const Embed = @import("embed.zig").Embed; +const WelcomeScreenChannel = @import("channel.zig").WelcomeScreenChannel; +const AllowedMentions = @import("channel.zig").AllowedMentions; +const MessageComponent = @import("message.zig").MessageComponent; +const Sticker = @import("message.zig").Sticker; +const Partial = @import("partial.zig").Partial; +const ReactionType = @import("message.zig").ReactionType; +const Team = @import("team.zig").Team; +const VideoQualityModes = @import("shared.zig").VideoQualityModes; +const Guild = @import("guild.zig").Guild; +const SortOrderTypes = @import("shared.zig").SortOrderTypes; +const InstallParams = @import("application.zig").InstallParams; +const ForumLayout = @import("shared.zig").ForumLayout; + +/// https://discord.com/developers/docs/topics/gateway#guild-members-chunk +pub const GuildMembersChunk = struct { + /// The id of the guild + guild_id: Snowflake, + /// Set of guild members + members: []MemberWithUser, + /// The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count) + chunk_index: isize, + /// 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, + /// If passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned members will be here + presences: []?PresenceUpdate, + /// The nonce used in the Guild Members Request + nonce: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#channel-pins-update +pub const ChannelPinsUpdate = struct { + /// The id of the guild + guild_id: ?Snowflake, + /// The id of the channel + channel_id: Snowflake, + /// The time at which the most recent pinned message was pinned + last_pin_timestamp: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-role-delete +pub const GuildRoleDelete = struct { + /// id of the guild + guild_id: Snowflake, + /// id of the role + role_id: Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-ban-add +pub const GuildBanAddRemove = struct { + /// id of the guild + guild_id: Snowflake, + /// The banned user + user: User, +}; + +/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove +pub const MessageReactionRemove = struct { + /// The id of the user + user_id: Snowflake, + /// The id of the channel + channel_id: Snowflake, + /// The id of the message + message_id: Snowflake, + /// The id of the guild + guild_id: ?Snowflake, + /// The emoji used to react + emoji: Partial(Emoji), + /// The id of the author of this message + message_author_id: ?Snowflake, + /// true if this is a super-reaction + burst: bool, + /// The type of reaction + type: ReactionType, +}; + +/// https://discord.com/developers/docs/events/gateway-events#message-reaction-add +pub const MessageReactionAdd = struct { + /// The id of the user + user_id: Snowflake, + /// The id of the channel + channel_id: Snowflake, + /// The id of the message + message_id: Snowflake, + /// The id of the guild + guild_id: ?Snowflake, + /// The member who reacted if this happened in a guild + member: ?MemberWithUser, + /// The emoji used to react + emoji: Partial(Emoji), + /// The id of the author of this message + message_author_id: ?Snowflake, + /// true if this is a super-reaction + burst: bool, + /// Colors used for super-reaction animation in "#rrggbb" format + burst_colors: []?[]const u8, + /// The type of reaction + type: ReactionType, +}; + +/// https://discord.com/developers/docs/topics/gateway#voice-server-update +pub const VoiceServerUpdate = struct { + /// Voice connection token + token: []const u8, + /// The guild this voice server update is for + guild_id: Snowflake, + /// The voice server host + endpoint: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send-voice-channel-effect-send-event-fields +pub const VoiceChannelEffectSend = struct { + /// ID of the channel the effect was sent in + channel_id: Snowflake, + /// ID of the guild the effect was sent in + guild_id: Snowflake, + /// ID of the user who sent the effect + user_id: Snowflake, + /// The emoji sent, for emoji reaction and soundboard effects + emoji: ?Emoji, + /// The type of emoji animation, for emoji reaction and soundboard effects + animation_type: ?VoiceChannelEffectAnimationType, + /// The ID of the emoji animation, for emoji reaction and soundboard effects + animation_id: ?isize, + /// The ID of the soundboard sound, for soundboard effects + sound_id: union(enum) { + string: ?[]const u8, + integer: isize, + }, + /// The volume of the soundboard sound, from 0 to 1, for soundboard effects + sound_volume: ?isize, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send-animation-types +pub const VoiceChannelEffectAnimationType = enum(u4) { + /// A fun animation, sent by a Nitro subscriber + Premium = 0, + /// The standard animation + Basic = 1, +}; + +/// https://discord.com/developers/docs/topics/gateway#invite-create +pub const InviteCreate = struct { + /// The channel the invite is for + channel_id: Snowflake, + /// The unique invite code + code: []const u8, + /// The time at which the invite was created + created_at: []const u8, + /// The guild of the invite + guild_id: ?Snowflake, + /// The user that created the invite + inviter: ?User, + /// How long the invite is valid for (in seconds) + max_age: isize, + /// The maximum isize of times the invite can be used + max_uses: isize, + /// The type of target for this voice channel invite + target_type: TargetTypes, + /// The target user for this invite + target_user: ?User, + /// The embedded application to open for this voice channel embedded application invite + target_application: ?Partial(Application), + /// Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) + temporary: bool, + /// How many times the invite has been used (always will be 0) + uses: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#hello +pub const Hello = struct { + /// The interval (in milliseconds) the client should heartbeat with + heartbeat_interval: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#ready +pub const Ready = struct { + /// Gateway version + v: isize, + /// Information about the user including email + user: User, + /// The guilds the user is in + guilds: []UnavailableGuild, + /// Used for resuming connections + session_id: []const u8, + /// Gateway url for resuming connections + resume_gateway_url: []const u8, + /// The shard information associated with this session, if sent when identifying + shard: ?[2]isize, + /// Contains id and flags, only sent to bots + application: ?struct { + flags: ApplicationFlags, + id: Snowflake, + }, +}; + +/// https://discord.com/developers/docs/resources/guild#unavailable-guild-object +pub const UnavailableGuild = struct { + unavailable: ?bool, + id: Snowflake, +}; + +/// https://discord.com/developers/docs/events/gateway-events#message-delete-bulk +pub const MessageDeleteBulk = struct { + /// The ids of the messages + ids: []Snowflake, + /// The id of the channel + channel_id: Snowflake, + /// The id of the guild + guild_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/resources/template#template-object-template-structure +pub const Template = struct { + /// The template code (unique Id) + code: []const u8, + /// Template name + name: []const u8, + /// The description for the template + description: ?[]const u8, + /// isize of times this template has been used + usage_count: isize, + /// The Id of the user who created the template + creator_id: Snowflake, + /// The user who created the template + creator: User, + /// When this template was created + created_at: []const u8, + /// When this template was last synced to the source guild + updated_at: []const u8, + /// The Id of the guild this template is based on + source_guild_id: Snowflake, + /// The guild snapshot this template contains + serialized_source_guild: TemplateSerializedSourceGuild, + is_dirty: ?bool, +}; + +pub const TemplateSerializedSourceGuild = null; + +/// https://discord.com/developers/docs/topics/gateway#guild-member-add +pub const GuildMemberAdd = struct { + /// Whether the user is deafened in voice channels + deaf: ?bool, + /// Whether the user is muted in voice channels + mute: ?bool, + /// Whether the user has not yet passed the guild's Membership Screening requirements + pending: ?bool, + /// This users guild nickname + nick: ?[]const u8, + /// The members custom avatar for this server. + avatar: ?[]const u8, + /// Array of role object ids + roles: [][]const u8, + /// When the user joined the guild + joined_at: []const u8, + /// When the user started boosting the guild + premium_since: ?[]const u8, + /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. + permissions: ?[]const u8, + /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out + communication_disabled_until: ?[]const u8, + /// Guild member flags + flags: isize, + /// data for the member's guild avatar decoration + avatar_decoration_data: ?AvatarDecorationData, + /// The user object for this member + user: User, + /// id of the guild + guild_id: Snowflake, +}; + +/// https://discord.com/developers/docs/events/gateway-events#message-delete +pub const MessageDelete = struct { + /// The id of the message + id: Snowflake, + /// The id of the channel + channel_id: Snowflake, + /// The id of the guild + guild_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#thread-members-update-thread-members-update-event-fields +pub const ThreadMembersUpdate = struct { + /// The id of the thread + id: Snowflake, + /// The id of the guild + guild_id: Snowflake, + /// The users who were added to the thread + added_members: []?ThreadMember, + /// The id of the users who were removed from the thread + removed_member_ids: []?[]const u8, + /// the approximate isize of members in the thread, capped at 50 + member_count: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#thread-member-update +pub const ThreadMemberUpdate = struct { + /// The id of the thread + id: Snowflake, + /// The id of the guild + guild_id: Snowflake, + /// The timestamp when the bot joined this thread. + joined_at: []const u8, + /// The flags this user has for this thread. Not useful for bots. + flags: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-role-create +pub const GuildRoleCreate = struct { + /// The id of the guild + guild_id: Snowflake, + /// The role created + role: Role, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-emojis-update +pub const GuildEmojisUpdate = struct { + /// id of the guild + guild_id: Snowflake, + /// Array of emojis + emojis: []Emoji, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update +pub const GuildStickersUpdate = struct { + /// id of the guild + guild_id: Snowflake, + /// Array of sticker + stickers: []Sticker, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-member-update +pub const GuildMemberUpdate = struct { + /// The id of the guild + guild_id: Snowflake, + /// User role ids + roles: [][]const u8, + /// The user + user: User, + /// Nickname of the user in the guild + nick: ?[]const u8, + /// the member's [guild avatar hash](https://discord.com/developers/docs/reference#image-formatting) + avatar: []const u8, + /// When the user joined the guild + joined_at: []const u8, + /// When the user starting boosting the guild + premium_since: ?[]const u8, + /// whether the user is deafened in voice channels + deaf: ?bool, + /// whether the user is muted in voice channels + mute: ?bool, + /// Whether the user has not yet passed the guild's Membership Screening requirements + pending: ?bool, + /// when the user's [timeout](https://support.discord.com/hc/en-us/articles/4413305239191-Time-Out-FAQ) will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out. Will throw a 403 error if the user has the ADMINISTRATOR permission or is the owner of the guild + communication_disabled_until: ?[]const u8, + /// Data for the member's guild avatar decoration + avatar_decoration_data: ?AvatarDecorationData, + /// Guild member flags + flags: ?isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all +pub const MessageReactionRemoveAll = null; + +/// https://discord.com/developers/docs/topics/gateway#guild-role-update +pub const GuildRoleUpdate = struct { + /// The id of the guild + guild_id: Snowflake, + /// The role updated + role: Role, +}; + +pub const ScheduledEventUserAdd = struct { + /// id of the guild scheduled event + guild_scheduled_event_id: Snowflake, + /// id of the user + user_id: Snowflake, + /// id of the guild + guild_id: Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove-emoji +pub const MessageReactionRemoveEmoji = null; + +/// https://discord.com/developers/docs/topics/gateway#guild-member-remove +pub const GuildMemberRemove = struct { + /// The id of the guild + guild_id: Snowflake, + /// The user who was removed + user: User, +}; + +/// https://discord.com/developers/docs/resources/guild#ban-object +pub const Ban = struct { + /// The reason for the ban + reason: ?[]const u8, + /// The banned user + user: User, +}; + +pub const ScheduledEventUserRemove = struct { + /// id of the guild scheduled event + guild_scheduled_event_id: Snowflake, + /// id of the user + user_id: Snowflake, + /// id of the guild + guild_id: Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#invite-delete +pub const InviteDelete = struct { + /// The channel of the invite + channel_id: Snowflake, + /// The guild of the invite + guild_id: ?Snowflake, + /// The unique invite code + code: []const u8, +}; + +/// https://discord.com/developers/docs/resources/voice#voice-region-object-voice-region-structure +pub const VoiceRegion = struct { + /// Unique Id for the region + id: Snowflake, + /// Name of the region + name: []const u8, + /// true for a single server that is closest to the current user's client + optimal: bool, + /// Whether this is a deprecated voice region (avoid switching to these) + deprecated: bool, + /// Whether this is a custom voice region (used for events/etc) + custom: bool, +}; + +pub const GuildWidgetSettings = struct { + /// whether the widget is enabled + enabled: bool, + /// the widget channel id + channel_id: ?Snowflake, +}; + +pub const ForumTag = struct { + /// The id of the tag + id: Snowflake, + /// The name of the tag (0-20 characters) + name: []const u8, + /// Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission + moderated: bool, + /// The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, +}; + +pub const DefaultReactionEmoji = struct { + /// The id of a guild's custom emoji + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, +}; + +pub const ModifyChannel = struct { + /// 1-100 character channel name + name: ?[]const u8, + /// The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature + type: ?ChannelTypes, + /// The position of the channel in the left-hand listing + position: ?isize, + /// 0-1024 character channel topic + topic: ?[]const u8, + /// Whether the channel is nsfw + nsfw: ?bool, + /// 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 bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) + bitrate: ?isize, + /// 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, + /// Id of the new parent category for a channel + parent_id: ?Snowflake, + /// Voice region id for the voice channel, automatic when set to null + rtc_region: ?[]const u8, + /// The camera video quality mode of the voice channel + video_quality_mode: ?VideoQualityModes, + /// Whether the thread is archived + archived: ?bool, + /// Duration in minutes to automatically archive the thread after recent activity + auto_archive_duration: ?isize, + /// When a thread is locked, only users with `MANAGE_THREADS` can unarchive it + locked: ?bool, + /// whether non-moderators can add other non-moderators to a thread; only available on private threads + invitable: ?bool, + /// The set of tags that can be used in a GUILD_FORUM channel + available_tags: []struct { + /// The id of the tag + id: Snowflake, + /// The name of the tag (0-20 characters) + name: []const u8, + /// Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission + moderated: bool, + /// The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. + emoji_id: Snowflake, + /// The unicode character of the emoji + 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, + /// 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 + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, + }, + /// 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. + default_thread_rate_limit_per_user: ?isize, + /// the default sort order type used to order posts in forum channels + default_sort_order: ?SortOrderTypes, + /// the default forum layout view used to display posts in `GUILD_FORUM` channels. Defaults to `0`, which indicates a layout view has not been set by a channel admin + default_forum_layout: ?ForumLayout, +}; + +/// https://discord.com/developers/docs/resources/emoji#create-guild-emoji +pub const CreateGuildEmoji = struct { + /// Name of the emoji + name: []const u8, + ///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, +}; + +/// https://discord.com/developers/docs/resources/emoji#modify-guild-emoji +pub const ModifyGuildEmoji = struct { + /// Name of the emoji + name: ?[]const u8, + /// Roles allowed to use this emoji + roles: []?[]const u8, +}; + +pub const CreateGuildChannel = struct { + /// Channel name (1-100 characters) + name: []const u8, + /// The type of channel + type: ?ChannelTypes, + /// Channel topic (0-1024 characters) + topic: ?[]const u8, + /// The bitrate (in bits) of the voice channel (voice only) + bitrate: ?isize, + /// The user limit of the voice channel (voice only) + user_limit: ?isize, + /// 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, + /// Sorting position of the channel + position: ?isize, + /// The channel's permission overwrites + permission_overwrites: []?Overwrite, + /// Id of the parent category for a channel + parent_id: ?Snowflake, + /// Whether the channel is nsfw + nsfw: ?bool, + /// Default duration (in minutes) that clients (not the API) use for newly created threads in this channel, to determine when to automatically archive the thread after the last activity + default_auto_archive_duration: ?isize, + /// Emoji to show in the add reaction button on a thread in a forum channel + default_reaction_emoji: ?struct { + /// The id of a guild's custom emoji. Exactly one of `emojiId` and `emojiName` must be set. + emoji_id: ?Snowflake, + /// The unicode character of the emoji. Exactly one of `emojiId` and `emojiName` must be set. + emoji_name: ?[]const u8, + }, + /// Set of tags that can be used in a forum channel + available_tags: ?[]struct { + /// The id of the tag + id: Snowflake, + /// The name of the tag (0-20 characters) + name: []const u8, + /// whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission + moderated: bool, + /// The id of a guild's custom emoji + emoji_id: Snowflake, + /// The unicode character of the emoji + emoji_name: ?[]const u8, + }, + /// the default sort order type used to order posts in forum channels + default_sort_order: ?SortOrderTypes, +}; + +pub const CreateMessage = struct { + /// The message contents (up to 2000 characters) + content: ?[]const u8, + /// Can be used to verify a message was sent (up to 25 characters). Value will appear in the Message Create event. + nonce: ?union(enum) { + string: ?[]const u8, + integer: isize, + }, + /// true if this is a TTS message + tts: ?bool, + /// Embedded `rich` content (up to 6000 characters) + embeds: []?Embed, + /// Allowed mentions for the message + allowed_mentions: ?AllowedMentions, + /// Include to make your message a reply + message_reference: ?struct { + /// id of the originating message + message_id: ?Snowflake, + /// + /// id of the originating message's channel + /// Note: `channel_id` is optional when creating a reply, but will always be present when receiving an event/response that includes this data model., + /// + channel_id: ?Snowflake, + /// id of the originating message's guild + guild_id: ?Snowflake, + /// When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true + fail_if_not_exists: bool, + }, + /// The components you would like to have sent in this message + components: ?[]MessageComponent, + /// IDs of up to 3 stickers in the server to send in the message + stickerIds: ?union(enum) { one: struct { []const u8 }, two: struct { []const u8 }, three: struct { []const u8 } }, +}; + +/// https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen +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, + /// The server description to show in the welcome screen + description: ?[]const u8, +}; + +pub const FollowAnnouncementChannel = struct { + /// The id of the channel to send announcements to. + webhook_channel_id: Snowflake, +}; + +pub const EditChannelPermissionOverridesOptions = struct { + /// Permission bit set + allow: []const u8, + /// Permission bit set + deny: []const u8, + /// Either 0 (role) or 1 (member) + type: OverwriteTypes, +}; + +/// https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions +pub const ModifyGuildChannelPositions = struct { + /// Channel id + id: Snowflake, + /// Sorting position of the channel + position: ?isize, + /// Syncs the permission overwrites with the new parent, if moving to a new category + lock_positions: ?bool, + /// The new parent ID for the channel that is moved + parent_id: ?Snowflake, +}; + +pub const CreateWebhook = struct { + /// Name of the webhook (1-80 characters) + name: []const u8, + /// Image url for the default webhook avatar + avatar: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#start-thread-in-forum-channel +pub const CreateForumPostWithMessage = struct { + /// 1-100 character channel name + name: []const u8, + /// duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080, + auto_archive_duration: ?isize, + /// amount of seconds a user has to wait before sending another message (0-21600) + rate_limit_per_user: ?isize, + /// contents of the first message in the forum thread + message: struct { + /// Message contents (up to 2000 characters) + content: ?[]const u8, + /// Embedded rich content (up to 6000 characters) + embeds: []?Embed, + /// Allowed mentions for the message + allowed_mentions: []?AllowedMentions, + /// Components to include with the message + components: []?[]MessageComponent, + /// IDs of up to 3 stickers in the server to send in the message + 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, + /// 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, +}; + +pub const ArchivedThreads = struct { + threads: []Channel, + members: []ThreadMember, + hasMore: bool, +}; + +pub const ActiveThreads = struct { + threads: []Channel, + members: []ThreadMember, +}; + +pub const VanityUrl = struct { + code: ?[]const u8, + uses: isize, +}; + +pub const PrunedCount = struct { + pruned: isize, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-guild-onboarding-structure +pub const GuildOnboarding = struct { + /// ID of the guild this onboarding is part of + guild_id: Snowflake, + /// Prompts shown during onboarding and in customize community + prompts: []GuildOnboardingPrompt, + /// Channel IDs that members get opted into automatically + default_channel_ids: [][]const u8, + /// Whether onboarding is enabled in the guild + enabled: bool, + /// Current mode of onboarding + mode: GuildOnboardingMode, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-prompt-structure +pub const GuildOnboardingPrompt = struct { + /// ID of the prompt + id: Snowflake, + /// Type of prompt + type: GuildOnboardingPromptType, + /// Options available within the prompt + options: []GuildOnboardingPromptOption, + /// Title of the prompt + title: []const u8, + /// Indicates whether users are limited to selecting one option for the prompt + single_select: bool, + /// Indicates whether the prompt is required before a user completes the onboarding flow + required: bool, + /// Indicates whether the prompt is present in the onboarding flow. If `false`, the prompt will only appear in the Channels & Roles tab + in_onboarding: bool, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-option-structure +pub const GuildOnboardingPromptOption = struct { + /// ID of the prompt option + id: Snowflake, + /// IDs for channels a member is added to when the option is selected + channel_ids: [][]const u8, + /// IDs for roles assigned to a member when the option is selected + role_ids: [][]const u8, + /// + /// Emoji of the option + /// + /// @remarks + /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. + /// + emoji: ?Emoji, + /// + /// Emoji ID of the option + /// + /// @remarks + /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. + /// + emoji_id: ?Snowflake, + /// + /// Emoji name of the option + /// + /// @remarks + /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. + /// + emoji_name: ?[]const u8, + /// + /// Whether the emoji is animated + /// + /// @remarks + /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. + /// + emoji_animated: ?bool, + /// Title of the option + title: []const u8, + /// Description of the option + description: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-types +pub const GuildOnboardingPromptType = enum { + MultipleChoice, + DropDown, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-mode +pub const GuildOnboardingMode = enum { + /// Counts only Default Channels towards constraints + OnboardingDefault, + /// Counts Default Channels and Questions towards constraints + OnboardingAdvanced, +}; + +/// https://discord.com/developers/docs/topics/teams#team-member-roles-team-member-role-types +pub const TeamMemberRole = enum { + /// Owners are the most permissiable role, and can take destructive, irreversible actions like deleting the team itself. Teams are limited to 1 owner. + owner, + /// Admins have similar access as owners, except they cannot take destructive actions on the team or team-owned apps. + admin, + /// broken + /// + developer, + /// Read-only members can access information about a team and any team-owned apps. Some examples include getting the IDs of applications and exporting payout records. + read_only, +}; + +/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-structure +pub const Entitlement = struct { + /// ID of the entitlement + id: Snowflake, + /// ID of the SKU + sku_id: Snowflake, + /// ID of the user that is granted access to the entitlement's sku + user_id: ?Snowflake, + /// ID of the guild that is granted access to the entitlement's sku + guild_id: ?Snowflake, + /// ID of the parent application + application_id: Snowflake, + /// Type of entitlement + type: EntitlementType, + /// Entitlement was deleted + deleted: bool, + /// Start date at which the entitlement is valid. Not present when using test entitlements + starts_at: ?[]const u8, + /// Date at which the entitlement is no longer valid. Not present when using test entitlements + ends_at: ?[]const u8, + /// For consumable items, whether or not the entitlement has been consumed + consumed: ?bool, +}; + +/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-types +pub const EntitlementType = enum(u4) { + /// Entitlement was purchased by user + Purchase = 1, + ///Entitlement for Nitro subscription + PremiumSubscription = 2, + /// Entitlement was gifted by developer + DeveloperGift = 3, + /// Entitlement was purchased by a dev in application test mode + TestModePurchase = 4, + /// Entitlement was granted when the SKU was free + FreePurchase = 5, + /// Entitlement was gifted by another user + UserGift = 6, + /// Entitlement was claimed by user for free as a Nitro Subscriber + PremiumPurchase = 7, + /// Entitlement was purchased as an app subscription + ApplicationSubscription = 8, +}; + +/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-structure +pub const Sku = struct { + /// ID of SKU + id: Snowflake, + /// Type of SKU + type: SkuType, + /// ID of the parent application + application_id: Snowflake, + /// Customer-facing name of your premium offering + name: []const u8, + /// System-generated URL slug based on the SKU's name + slug: []const u8, + /// SKU flags combined as a bitfield + flags: SkuFlags, +}; + +/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-types +pub const SkuType = enum(u4) { + /// Durable one-time purchase + Durable = 2, + /// Consumable one-time purchase + Consumable = 3, + /// Represents a recurring subscription + Subscription = 5, + /// System-generated group for each SUBSCRIPTION SKU created + SubscriptionGroup = 6, +}; + +/// https://discord.com/developers/docs/resources/guild#bulk-guild-ban +pub const BulkBan = struct { + /// list of user ids, that were successfully banned + banned_users: [][]const u8, + /// list of user ids, that were not banned + failed_users: [][]const u8, +}; diff --git a/src/structures/gateway.zig b/src/structures/gateway.zig new file mode 100644 index 0000000..e243489 --- /dev/null +++ b/src/structures/gateway.zig @@ -0,0 +1,178 @@ +const User = @import("user.zig").User; +const Snowflake = @import("snowflake.zig").Snowflake; +const ActivityTypes = @import("shared.zig").ActivityTypes; + +/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot +pub const GetGatewayBot = struct { + /// The WSS URL that can be used for connecting to the gateway + url: []const u8, + /// The recommended isize of shards to use when connecting + shards: isize, + /// Information on the current session start limit + session_start_limit: SessionStartLimit, +}; + +/// https://discord.com/developers/docs/topics/gateway#session-start-limit-object +pub const SessionStartLimit = struct { + /// The total isize of session starts the current user is allowed + total: isize, + /// The remaining isize of session starts the current user is allowed + remaining: isize, + /// The isize of milliseconds after which the limit resets + reset_after: isize, + /// The isize of identify requests allowed per 5 seconds + max_concurrency: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#presence-update +pub const PresenceUpdate = struct { + /// Either "idle", "dnd", "online", or "offline" + status: union(enum) { + idle, + dnd, + online, + offline, + }, + /// The user presence is being updated for + user: User, + /// id of the guild + guild_id: Snowflake, + /// User's current activities + activities: []Activity, + /// User's platform-dependent status + client_status: ClientStatus, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#activity-object +pub const Activity = struct { + /// The activity's name + name: []const u8, + /// Activity type + type: ActivityTypes, + /// Stream url, is validated when type is 1 + url: ?[]const u8, + /// Unix timestamp of when the activity was added to the user's session + created_at: isize, + /// What the player is currently doing + details: ?[]const u8, + /// The user's current party status + state: ?[]const u8, + /// Whether or not the activity is an instanced game session + instance: ?bool, + /// Activity flags `OR`d together, describes what the payload includes + flags: ?isize, + /// Unix timestamps for start and/or end of the game + timestamps: ?ActivityTimestamps, + /// Application id for the game + application_id: ?Snowflake, + /// The emoji used for a custom status + emoji: ?ActivityEmoji, + /// Information for the current party of the player + party: ?ActivityParty, + /// Images for the presence and their hover texts + assets: ?ActivityAssets, + /// Secrets for Rich Presence joining and spectating + secrets: ?ActivitySecrets, + /// The custom buttons shown in the Rich Presence (max 2) + buttons: []?ActivityButton, +}; + +/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-instance-object +pub const ActivityInstance = struct { + /// Application ID + application_id: Snowflake, + /// Activity Instance ID + instance_id: Snowflake, + /// Unique identifier for the launch + launch_id: Snowflake, + /// The Location the instance is runnning in + location: ActivityLocation, + /// The IDs of the Users currently connected to the instance + users: [][]const u8, +}; + +/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-object +pub const ActivityLocation = struct { + /// The unique identifier for the location + id: Snowflake, + /// Enum describing kind of location + kind: ActivityLocationKind, + /// The id of the Channel + channel_id: Snowflake, + /// The id of the Guild + guild_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-kind-enum +pub const ActivityLocationKind = enum { + /// The Location is a Guild Channel + gc, + /// The Location is a Private Channel, such as a DM or GDM + pc, +}; + +/// https://discord.com/developers/docs/topics/gateway#client-status-object +pub const ClientStatus = struct { + /// The user's status set for an active desktop (Windows, Linux, Mac) application session + desktop: ?[]const u8, + /// The user's status set for an active mobile (iOS, Android) application session + mobile: ?[]const u8, + /// The user's status set for an active web (browser, bot account) application session + web: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-timestamps +pub const ActivityTimestamps = struct { + /// Unix time (in milliseconds) of when the activity started + start: ?isize, + /// Unix time (in milliseconds) of when the activity ends + end: ?isize, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-emoji +pub const ActivityEmoji = struct { + /// The name of the emoji + name: []const u8, + /// Whether this emoji is animated + animated: ?bool, + /// The id of the emoji + id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-party +pub const ActivityParty = struct { + /// Used to show the party's current and maximum size + size: ?[2]i64, + /// The id of the party + id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-assets +pub const ActivityAssets = struct { + /// Text displayed when hovering over the large image of the activity + large_text: ?[]const u8, + /// Text displayed when hovering over the small image of the activity + small_text: ?[]const u8, + /// The id for a large asset of the activity, usually a snowflake + large_image: ?[]const u8, + /// The id for a small asset of the activity, usually a snowflake + small_image: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-secrets +pub const ActivitySecrets = struct { + /// The secret for joining a party + join: ?[]const u8, + /// The secret for spectating a game + spectate: ?[]const u8, + /// The secret for a specific instanced match + match: ?[]const u8, +}; + +/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-buttons +pub const ActivityButton = struct { + /// The text shown on the button (1-32 characters) + label: []const u8, + /// The url opened when clicking the button (1-512 characters) + url: []const u8, +}; diff --git a/src/structures/guild.zig b/src/structures/guild.zig new file mode 100644 index 0000000..880c89e --- /dev/null +++ b/src/structures/guild.zig @@ -0,0 +1,204 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const VerificationLevels = @import("shared.zig").VerificationLevels; +const DefaultMessageNotificationLevels = @import("shared.zig").DefaultMessageNotificationLevels; +const ExplicitContentFilterLevels = @import("shared.zig").ExplicitContentFilterLevels; +const GuildFeatures = @import("shared.zig").GuildFeatures; +const GuildNsfwLevel = @import("shared.zig").GuildNsfwLevel; +const Role = @import("role.zig").Role; +const MemberWithUser = @import("member.zig").MemberWithUser; +const Member = @import("member.zig").Member; +const Channel = @import("channel.zig").Channel; +const MfaLevels = @import("shared.zig").MfaLevels; +const SystemChannelFlags = @import("shared.zig").SystemChannelFlags; +const PremiumTiers = @import("shared.zig").PremiumTiers; +const Emoji = @import("emoji.zig").Emoji; +const Sticker = @import("message.zig").Sticker; +const Partial = @import("partial.zig").Partial; +const PresenceUpdate = @import("gateway.zig").PresenceUpdate; +const WelcomeScreen = @import("channel.zig").WelcomeScreen; +const StageInstance = @import("channel.zig").StageInstance; + +/// https://discord.com/developers/docs/resources/guild#guild-object +pub const Guild = struct { + /// Guild name (2-100 characters, excluding trailing and leading whitespace) + name: []const u8, + /// True if the user is the owner of the guild + owner: ?bool, + /// Afk timeout in seconds + afk_timeout: isize, + /// True if the server widget is enabled + widget_enabled: ?bool, + /// Verification level required for the guild + verification_level: VerificationLevels, + /// Default message notifications level + default_message_notifications: DefaultMessageNotificationLevels, + /// Explicit content filter level + explicit_content_filter: ExplicitContentFilterLevels, + /// Enabled guild features + features: []GuildFeatures, + /// Required MFA level for the guild + mfa_level: MfaLevels, + /// System channel flags + system_channel_flags: SystemChannelFlags, + /// True if this is considered a large guild + large: ?bool, + /// True if this guild is unavailable due to an outage + unavailable: ?bool, + /// Total isize of members in this guild + member_count: ?isize, + /// The maximum isize of presences for the guild (the default value, currently 25000, is in effect when null is returned) + max_presences: ?isize, + /// The maximum isize of members for the guild + max_members: ?isize, + /// The vanity url code for the guild + vanity_url_code: ?[]const u8, + /// The description of a guild + description: ?[]const u8, + /// Premium tier (Server Boost level) + premium_tier: PremiumTiers, + /// The isize of boosts this guild currently has + premium_subscription_count: ?isize, + /// The maximum amount of users in a video channel + max_video_channel_users: ?isize, + /// Maximum amount of users in a stage video channel + max_stage_video_channel_users: ?isize, + /// Approximate isize of members in this guild, returned from the GET /guilds/id endpoint when with_counts is true + approximate_member_count: ?isize, + /// Approximate isize of non-offline members in this guild, returned from the GET /guilds/id endpoint when with_counts is true + approximate_presence_count: ?isize, + /// Guild NSFW level + nsfw_level: GuildNsfwLevel, + /// Whether the guild has the boost progress bar enabled + premium_progress_bar_enabled: bool, + /// Guild id + id: Snowflake, + /// Icon hash + icon: ?[]const u8, + /// Icon hash, returned when in the template object + icon_hash: ?[]const u8, + /// Splash hash + splash: ?[]const u8, + /// Discovery splash hash; only present for guilds with the "DISCOVERABLE" feature + discovery_splash: ?[]const u8, + /// Id of the owner + owner_id: Snowflake, + /// Total permissions for the user in the guild (excludes overwrites and implicit permissions) + permissions: ?[]const u8, + /// Id of afk channel + afk_channel_id: ?Snowflake, + /// The channel id that the widget will generate an invite to, or null if set to no invite + widget_channel_id: ?Snowflake, + /// Roles in the guild + roles: []Role, + /// Custom guild emojis + emojis: []Emoji, + /// Application id of the guild creator if it is bot-created + application_id: ?Snowflake, + /// The id of the channel where guild notices such as welcome messages and boost events are posted + system_channel_id: ?Snowflake, + /// The id of the channel where community guilds can display rules and/or guidelines + rules_channel_id: ?Snowflake, + /// 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"}), + /// Users in the guild + members: []?Member, + /// Channels in the guild + channels: []?Channel, + /// All active threads in the guild that the current user has permission to view + 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), + /// Banner hash + banner: ?[]const u8, + ///The preferred locale of a Community guild; used in server discovery and notices from ; defaults to "en-US" + preferred_locale: []const u8, + ///The id of the channel where admins and moderators of Community guilds receive notices from + public_updates_channel_id: ?Snowflake, + /// 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, + /// Custom guild stickers + stickers: []?Sticker, + ///The id of the channel where admins and moderators of Community guilds receive safety alerts from + safety_alerts_channel_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/resources/voice#voice-state-object-voice-state-structure +pub const VoiceState = struct { + /// The session id for this voice state + session_id: []const u8, + /// The guild id this voice state is for + guild_id: ?Snowflake, + /// The channel id this user is connected to + channel_id: ?Snowflake, + /// The user id this voice state is for + user_id: Snowflake, + /// The guild member this voice state is for + member: ?MemberWithUser, + /// Whether this user is deafened by the server + deaf: bool, + /// Whether this user is muted by the server + mute: bool, + /// Whether this user is locally deafened + self_deaf: bool, + /// Whether this user is locally muted + self_mute: bool, + /// Whether this user is streaming using "Go Live" + self_stream: ?bool, + /// Whether this user's camera is enabled + self_video: bool, + /// Whether this user is muted by the current user + suppress: bool, + /// The time at which the user requested to speak + request_to_speak_timestamp: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/guild#get-guild-widget-example-get-guild-widget +pub const GuildWidget = struct { + id: Snowflake, + name: []const u8, + instant_invite: []const u8, + channels: []struct { + id: Snowflake, + name: []const u8, + position: isize, + }, + members: []struct { + id: Snowflake, + username: []const u8, + discriminator: []const u8, + avatar: ?[]const u8, + status: []const u8, + avatar_url: []const u8, + }, + presence_count: isize, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-preview-object +pub const GuildPreview = struct { + /// Guild id + id: Snowflake, + /// Guild name (2-100 characters) + name: []const u8, + /// Icon hash + icon: ?[]const u8, + /// Splash hash + splash: ?[]const u8, + /// Discovery splash hash + discovery_splash: ?[]const u8, + /// Custom guild emojis + emojis: []Emoji, + /// Enabled guild features + features: []GuildFeatures, + /// Approximate isize of members in this guild + approximate_member_count: isize, + /// Approximate isize of online members in this guild + approximate_presence_count: isize, + /// The description for the guild, if the guild is discoverable + description: ?[]const u8, + /// Custom guild stickers + stickers: []Sticker, +}; diff --git a/src/structures/integration.zig b/src/structures/integration.zig new file mode 100644 index 0000000..c820fb7 --- /dev/null +++ b/src/structures/integration.zig @@ -0,0 +1,134 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const IntegrationExpireBehaviors = @import("shared.zig").IntegrationExpireBehaviors; +const OAuth2Scope = @import("shared.zig").OAuth2Scope; +const User = @import("user.zig").User; + +/// https://discord.com/developers/docs/resources/guild#integration-object-integration-structure +pub const Integration = struct { + /// Integration Id + id: Snowflake, + /// Integration name + name: []const u8, + /// Integration type (twitch, youtube, discord, or guild_subscription). + type: union(enum) { + twitch, + youtube, + discord, + }, + /// Is this integration enabled + enabled: ?bool, + /// Is this integration syncing + syncing: ?bool, + /// Role Id that this integration uses for "subscribers" + role_id: ?Snowflake, + /// Whether emoticons should be synced for this integration (twitch only currently) + enable_emoticons: ?bool, + /// The behavior of expiring subscribers + expire_behavior: ?IntegrationExpireBehaviors, + /// The grace period (in days) before expiring subscribers + expire_grace_period: ?isize, + /// When this integration was last synced + synced_at: ?[]const u8, + /// How many subscribers this integration has + subscriber_count: ?isize, + /// Has this integration been revoked + revoked: ?bool, + /// User for this integration + user: ?User, + /// Integration account information + account: IntegrationAccount, + /// The bot/OAuth2 application for discord integrations + application: ?IntegrationApplication, + /// the scopes the application has been authorized for + scopes: []OAuth2Scope, +}; + +/// https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure +pub const IntegrationAccount = struct { + /// Id of the account + id: Snowflake, + /// Name of the account + name: []const u8, +}; + +/// https://discord.com/developers/docs/resources/guild#integration-application-object-integration-application-structure +pub const IntegrationApplication = struct { + /// The id of the app + id: Snowflake, + /// The name of the app + name: []const u8, + /// the icon hash of the app + icon: ?[]const u8, + /// The description of the app + description: []const u8, + /// The bot associated with this application + bot: ?User, +}; + +/// https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-create-event-additional-fields +pub const IntegrationCreateUpdate = struct { + /// Integration Id + id: Snowflake, + /// Integration name + name: []const u8, + /// Integration type (twitch, youtube, discord, or guild_subscription). + type: union(enum) { + twitch, + youtube, + discord, + }, + /// Is this integration enabled + enabled: ?bool, + /// Is this integration syncing + syncing: ?bool, + /// Role Id that this integration uses for "subscribers" + role_id: ?Snowflake, + /// Whether emoticons should be synced for this integration (twitch only currently) + enable_emoticons: ?bool, + /// The behavior of expiring subscribers + expire_behavior: ?IntegrationExpireBehaviors, + /// The grace period (in days) before expiring subscribers + expire_grace_period: ?isize, + /// When this integration was last synced + synced_at: ?[]const u8, + /// How many subscribers this integration has + subscriber_count: ?isize, + /// Has this integration been revoked + revoked: ?bool, + /// User for this integration + user: ?User, + /// Integration account information + account: IntegrationAccount, + /// The bot/OAuth2 application for discord integrations + application: ?IntegrationApplication, + /// the scopes the application has been authorized for + scopes: []OAuth2Scope, + /// Id of the guild + guild_id: Snowflake, +}; + +/// https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-delete-event-fields +pub const IntegrationDelete = struct { + /// Integration id + id: Snowflake, + /// Id of the guild + guild_id: Snowflake, + /// Id of the bot/OAuth2 application for this discord integration + application_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/topics/gateway#guild-integrations-update +pub const GuildIntegrationsUpdate = struct { + /// id of the guild whose integrations were updated + guild_id: Snowflake, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types +pub const InteractionContextType = enum { + /// Interaction can be used within servers + Guild, + /// Interaction can be used within DMs with the app's bot user + BotDm, + /// Interaction can be used within Group DMs and DMs other than the app's bot user + PrivateChannel, +}; diff --git a/src/structures/interaction.zig b/src/structures/interaction.zig new file mode 100644 index 0000000..9b84798 --- /dev/null +++ b/src/structures/interaction.zig @@ -0,0 +1,204 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const InteractionTypes = @import("shared.zig").InteractionTypes; +const Guild = @import("guild.zig").Guild; +const Attachment = @import("attachment.zig").Attachment; +const Message = @import("message.zig").Message; +const Channel = @import("channel.zig").Channel; +const User = @import("user.zig").User; +const Role = @import("role.zig").Role; +const AvatarDecorationData = @import("user.zig").AvatarDecorationData; +const Partial = @import("partial.zig").Partial; +const ApplicationCommandOptionTypes = @import("shared.zig").ApplicationCommandOptionTypes; +const MessageComponentTypes = @import("shared.zig").MessageComponentTypes; +const ChannelTypes = @import("shared.zig").ChannelTypes; +const MessageComponent = @import("message.zig").MessageComponent; +const ApplicationCommandTypes = @import("shared.zig").ApplicationCommandTypes; +const InteractionResponseTypes = @import("shared.zig").InteractionResponseTypes; +const InteractionContextType = @import("command.zig").InteractionContextType; +const Entitlement = @import("monetization.zig").Entitlement; +const Record = @import("json").Record; + +pub const Interaction = struct { + /// Id of the interaction + id: Snowflake, + /// Id of the application this interaction is for + application_id: Snowflake, + /// The type of interaction + type: InteractionTypes, + /// Guild that the interaction was sent from + guild: ?Partial(Guild), + /// The guild it was sent from + guild_id: ?Snowflake, + /// The channel it was sent from + channel: Partial(Channel), + /// + /// The ID of channel it was sent from + /// + /// @remarks + /// It is recommended that you begin using this channel field to identify the source channel of the interaction as they may deprecate the existing channel_id field in the future. + /// + channel_id: ?Snowflake, + /// Guild member data for the invoking user, including permissions + member: ?InteractionMember, + /// User object for the invoking user, if invoked in a DM + user: ?User, + /// A continuation token for responding to the interaction + token: []const u8, + /// Read-only property, always `1` + version: 1, + /// For the message the button was attached to + message: ?Message, + /// the command data payload + data: ?InteractionData, + /// The selected language of the invoking user + locale: ?[]const u8, + /// The guild's preferred locale, if invoked in a guild + guild_locale: ?[]const u8, + /// The computed permissions for a bot or app in the context of a specific interaction (including channel overwrites) + app_permissions: []const u8, + /// For monetized apps, any entitlements for the invoking user, representing access to premium SKUs + entitlements: []Entitlement, + // Mapping of installation contexts that the interaction was authorized for to related user or guild IDs. + // authorizing_integration_owners: Partial(AutoArrayHashMap(ApplicationIntegrationType, []const u8)), + /// Context where the interaction was triggered from + context: ?InteractionContextType, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-response-object +pub const InteractionCallbackResponse = struct { + /// The interaction object associated with the interaction response + interaction: InteractionCallback, + /// The resource that was created by the interaction response. + resource: ?InteractionResource, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-object +pub const InteractionCallback = struct { + /// ID of the interaction + id: Snowflake, + /// Interaction type + type: InteractionTypes, + /// Instance ID of the Activity if one was launched or joined + activity_instance_id: ?Snowflake, + /// ID of the message that was created by the interaction + response_message_id: ?Snowflake, + /// Whether or not the message is in a loading state + response_message_loading: ?bool, + /// Whether or not the response message was ephemeral + response_message_ephemeral: ?bool, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-resource-object +pub const InteractionResource = struct { + type: InteractionResponseTypes, + /// + /// Represents the Activity launched by this interaction. + /// + /// @remarks + /// Only present if type is `LAUNCH_ACTIVITY`. + /// + activity_instance: ?ActivityInstanceResource, + /// + /// Message created by the interaction. + /// + /// @remarks + /// Only present if type is either `CHANNEL_MESSAGE_WITH_SOURCE` or `UPDATE_MESSAGE`. + /// + message: ?Message, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-activity-instance-resource +pub const ActivityInstanceResource = struct { + /// Instance ID of the Activity if one was launched or joined. + id: Snowflake, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-member-object +pub const InteractionMember = struct { + /// Whether the user is deafened in voice channels + deaf: ?bool, + /// Whether the user is muted in voice channels + mute: ?bool, + /// Whether the user has not yet passed the guild's Membership Screening requirements + pending: ?bool, + /// This users guild nickname + nick: ?[]const u8, + /// The members custom avatar for this server. + avatar: ?[]const u8, + /// Array of role object ids + roles: [][]const u8, + /// When the user joined the guild + joined_at: []const u8, + /// When the user started boosting the guild + premium_since: ?[]const u8, + /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out + communication_disabled_until: ?[]const u8, + /// Guild member flags + flags: isize, + /// data for the member's guild avatar decoration + avatar_decoration_data: ?AvatarDecorationData, + /// The user object for this member + user: User, + /// Total permissions of the member in the channel, including overwrites, returned when in the interaction object + permissions: []const u8, +}; + +pub const InteractionData = struct { + /// The type of component + component_type: ?MessageComponentTypes, + /// The custom id provided for this component. + custom_id: ?Snowflake, + /// The components if its a Modal Submit interaction. + components: ?[]MessageComponent, + /// The values chosen by the user. + values: []?[]const u8, + /// The Id of the invoked command + id: Snowflake, + /// The name of the invoked command + name: []const u8, + /// the type of the invoked command + type: ApplicationCommandTypes, + /// Converted users + roles + channels + attachments + resolved: ?struct { + /// The Ids and Message objects + messages: ?Record(Message), + /// The Ids and User objects + users: ?Record(User), + // The Ids and partial Member objects + //members: ?Record(Omit(InteractionMember, .{ "user", "deaf", "mute" })), + /// The Ids and Role objects + roles: ?Record(Role), + /// The Ids and partial Channel objects + channels: ?Record(struct { + id: Snowflake, + type: ChannelTypes, + name: ?[]const u8, + permissions: ?[]const u8, + }), + /// The ids and attachment objects + attachments: Record(Attachment), + }, + /// The params + values from the user + 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 + guild_id: ?Snowflake, +}; + +pub const InteractionDataOption = struct { + /// Name of the parameter + name: []const u8, + /// Value of application command option type + type: ApplicationCommandOptionTypes, + /// Value of the option resulting from user input + value: ?union(enum) { + string: []const u8, + bool: bool, + integer: isize, + }, + /// Present if this option is a group or subcommand + options: []?InteractionDataOption, + /// `true` if this option is the currently focused option for autocomplete + focused: ?bool, +}; diff --git a/src/structures/invite.zig b/src/structures/invite.zig new file mode 100644 index 0000000..77a62b0 --- /dev/null +++ b/src/structures/invite.zig @@ -0,0 +1,97 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const User = @import("user.zig").User; +const Guild = @import("guild.zig").Guild; +const Channel = @import("channel.zig").Channel; +const Member = @import("member.zig").Member; +const Application = @import("application.zig").Application; +const MessageActivityTypes = @import("shared.zig").MessageActivityTypes; +const ScheduledEvent = @import("scheduled_event.zig").ScheduledEvent; +const TargetTypes = @import("shared.zig").TargetTypes; +const Partial = @import("partial.zig").Partial; + +/// https://discord.com/developers/docs/resources/invite#invite-metadata-object +pub const InviteMetadata = struct { + /// The type of invite + type: InviteType, + /// The invite code (unique Id) + code: []const u8, + /// The guild this invite is for + guild: ?Partial(Guild), + /// The channel this invite is for + channel: ?Partial(Channel), + /// The user who created the invite + inviter: ?User, + /// The type of target for this voice channel invite + target_type: ?TargetTypes, + /// The target user for this invite + target_user: ?User, + /// The embedded application to open for this voice channel embedded application invite + target_application: ?Partial(Application), + /// Approximate count of online members (only present when target_user is set) + approximate_presence_count: ?isize, + /// Approximate count of total members + approximate_member_count: ?isize, + /// The expiration date of this invite, returned from the `GET /invites/` endpoint when `with_expiration` is `true` + expires_at: ?[]const u8, + /// Stage instance data if there is a public Stage instance in the Stage channel this invite is for + stage_instance: ?InviteStageInstance, + /// guild scheduled event data + guild_scheduled_event: ?ScheduledEvent, + /// isize of times this invite has been used + uses: isize, + /// Max isize of times this invite can be used + max_uses: isize, + /// Duration (in seconds) after which the invite expires + max_age: isize, + /// Whether this invite only grants temporary membership + temporary: bool, + /// When this invite was created + created_at: []const u8, +}; + +/// https://discord.com/developers/docs/resources/invite#invite-object +pub const Invite = struct { + /// The type of invite + type: InviteType, + /// The invite code (unique Id) + code: []const u8, + /// The guild this invite is for + guild: ?Partial(Guild), + /// The channel this invite is for + channel: ?Partial(Channel), + /// The user who created the invite + inviter: ?User, + /// The type of target for this voice channel invite + target_type: ?TargetTypes, + /// The target user for this invite + target_user: ?User, + /// The embedded application to open for this voice channel embedded application invite + target_application: ?Partial(Application), + /// Approximate count of online members (only present when target_user is set) + approximate_presence_count: ?isize, + /// Approximate count of total members + approximate_member_count: ?isize, + /// The expiration date of this invite, returned from the `GET /invites/` endpoint when `with_expiration` is `true` + expires_at: ?[]const u8, + /// Stage instance data if there is a public Stage instance in the Stage channel this invite is for + stage_instance: ?InviteStageInstance, + /// guild scheduled event data + guild_scheduled_event: ?ScheduledEvent, +}; + +pub const InviteType = enum { + Guild, + GroupDm, + Friend, +}; + +pub const InviteStageInstance = struct { + /// The members speaking in the Stage + members: []Partial(Member), + /// The isize of users in the Stage + participant_count: isize, + /// The isize of users speaking in the Stage + speaker_count: isize, + /// The topic of the Stage instance (1-120 characters) + topic: []const u8, +}; diff --git a/src/structures/member.zig b/src/structures/member.zig new file mode 100644 index 0000000..00df33a --- /dev/null +++ b/src/structures/member.zig @@ -0,0 +1,62 @@ +const User = @import("user.zig").User; +const AvatarDecorationData = @import("user.zig").AvatarDecorationData; + +/// https://discord.com/developers/docs/resources/guild#guild-member-object +pub const Member = struct { + /// Whether the user is deafened in voice channels + deaf: ?bool, + /// Whether the user is muted in voice channels + mute: ?bool, + /// Whether the user has not yet passed the guild's Membership Screening requirements + pending: ?bool, + /// The user this guild member represents + user: ?User, + /// This users guild nickname + nick: ?[]const u8, + /// The members custom avatar for this server. + avatar: ?[]const u8, + /// Array of role object ids + roles: [][]const u8, + /// When the user joined the guild + joined_at: []const u8, + /// When the user started boosting the guild + premium_since: ?[]const u8, + /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. + permissions: ?[]const u8, + /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out + communication_disabled_until: ?[]const u8, + /// Guild member flags + flags: isize, + /// data for the member's guild avatar decoration + avatar_decoration_data: ?AvatarDecorationData, +}; + +/// inherits +pub const MemberWithUser = struct { + /// Whether the user is deafened in voice channels + deaf: ?bool, + /// Whether the user is muted in voice channels + mute: ?bool, + /// Whether the user has not yet passed the guild's Membership Screening requirements + pending: ?bool, + /// This users guild nickname + nick: ?[]const u8, + /// The members custom avatar for this server. + avatar: ?[]const u8, + /// Array of role object ids + roles: [][]const u8, + /// When the user joined the guild + joined_at: []const u8, + /// When the user started boosting the guild + premium_since: ?[]const u8, + /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. + permissions: ?[]const u8, + /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out + communication_disabled_until: ?[]const u8, + /// Guild member flags + flags: isize, + /// data for the member's guild avatar decoration + avatar_decoration_data: ?AvatarDecorationData, + /// The user object for this member + user: User, +}; diff --git a/src/structures/message.zig b/src/structures/message.zig new file mode 100644 index 0000000..96ec554 --- /dev/null +++ b/src/structures/message.zig @@ -0,0 +1,344 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const User = @import("user.zig").User; +const Member = @import("member.zig").Member; +const Attachment = @import("attachment.zig").Attachment; +const Application = @import("application.zig").Application; +const Embed = @import("embed.zig").Embed; +const PremiumTypes = @import("shared.zig").PremiumTypes; +const InteractionTypes = @import("shared.zig").InteractionTypes; +const StickerTypes = @import("shared.zig").StickerTypes; +const StickerFormatTypes = @import("shared.zig").StickerFormatTypes; +const MessageTypes = @import("shared.zig").MessageTypes; +const MessageFlags = @import("shared.zig").MessageFlags; +const Emoji = @import("emoji.zig").Emoji; +const Poll = @import("poll.zig").Poll; +const AvatarDecorationData = @import("user.zig").AvatarDecorationData; +const MessageActivityTypes = @import("shared.zig").MessageActivityTypes; +const Partial = @import("partial.zig").Partial; + +/// TODO: fix this +pub const MessageComponent = isize; + +/// https://discord.com/developers/docs/resources/channel#message-object +pub const Message = struct { + /// id of the message + id: Snowflake, + /// id of the channel the message was sent in + channel_id: Snowflake, + /// + /// id of the guild the message was sent in + /// Note: For MESSAGE_CREATE and MESSAGE_UPDATE events, the message object may not contain a guild_id or member field since the events are sent directly to the receiving user and the bot who sent the message, rather than being sent through the guild like non-ephemeral messages., + /// + guild_id: ?Snowflake, + /// + /// The author of this message (not guaranteed to be a valid user) + /// Note: The author object follows the structure of the user object, but is only a valid user in the case where the message is generated by a user or bot user. If the message is generated by a webhook, the author object corresponds to the webhook's id, username, and avatar. You can tell if a message is generated by a webhook by checking for the webhook_id on the message object., + /// + author: User, + /// + /// Member properties for this message's author + /// Note: The member object exists in `MESSAGE_CREATE` and `MESSAGE_UPDATE` events from text-based guild channels. This allows bots to obtain real-time member data without requiring bots to store member state in memory., + /// + member: ?Member, + /// Contents of the message + content: ?[]const u8, + /// When this message was sent + timestamp: []const u8, + /// When this message was edited (or null if never) + edited_timestamp: ?[]const u8, + /// Whether this was a TTS message + tts: bool, + /// Whether this message mentions everyone + mention_everyone: bool, + /// + /// Users specifically mentioned in the message + /// Note: The user objects in the mentions array will only have the partial member field present in `MESSAGE_CREATE` and `MESSAGE_UPDATE` events from text-based guild channels., + /// + mentions: []User, + /// Roles specifically mentioned in this message + mention_roles: ?[][]const u8, + /// + /// Channels specifically mentioned in this message + /// Note: Not all channel mentions in a message will appear in `mention_channels`. Only textual channels that are visible to everyone in a discoverable guild will ever be included. Only crossposted messages (via Channel Following) currently include `mention_channels` at all. If no mentions in the message meet these requirements, this field will not be sent., + /// + mention_channels: ?[]ChannelMention, + /// Any attached files + attachments: []Attachment, + /// Any embedded content + embeds: []Embed, + /// Reactions to the message + reactions: ?[]Reaction, + // Used for validating a message was sent + //nonce: ?union(enum) {int: isize,string: []const u8,}, + /// Whether this message is pinned + pinned: bool, + /// If the message is generated by a webhook, this is the webhook's id + webhook_id: ?Snowflake, + /// Type of message + type: MessageTypes, + /// Sent with Rich Presence-related chat embeds + activity: ?MessageActivity, + /// Sent with Rich Presence-related chat embeds + application: ?Partial(Application), + /// if the message is an Interaction or application-owned webhook, this is the id of the application + application_id: ?Snowflake, + // Data showing the source of a crosspost, channel follow add, pin, or reply message + // message_reference: ?Omit(MessageReference, .{"failIfNotExists"}), + /// Message flags combined as a bitfield + flags: ?MessageFlags, + /// + /// The stickers sent with the message (bots currently can only receive messages with stickers, not send) + /// @deprecated + /// + stickers: ?[]Sticker, + /// + /// The message associated with the `message_reference` + /// Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted., + /// TAKES A POINTER + referenced_message: ?*Message, + /// The message associated with the `message_reference`. This is a minimal subset of fields in a message (e.g. `author` is excluded.) + message_snapshots: ?[]MessageSnapshot, + /// sent if the message is sent as a result of an interaction + interaction_metadata: ?MessageInteractionMetadata, + /// + /// Sent if the message is a response to an Interaction + /// + /// @deprecated Deprecated in favor of {@link interaction_metadata}; + /// + interaction: ?MessageInteraction, + // The thread that was started from this message, includes thread member object + // thread: ?Omit(Channel, .{"member"}), //& { member: ThreadMember };, + /// The components related to this message + components: ?[]MessageComponent, + /// Sent if the message contains stickers + sticker_items: ?[]StickerItem, + /// A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with `total_message_sent` on parent thread + position: ?isize, + /// The poll object + poll: ?Poll, + /// The call associated with the message + call: ?MessageCall, +}; + +/// https://discord.com/developers/docs/resources/channel#message-call-object +pub const MessageCall = struct { + /// Array of user object ids that participated in the call + participants: [][]const u8, + /// Time when call ended + ended_timestamp: []const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#channel-mention-object +pub const ChannelMention = struct { + /// id of the channel + id: Snowflake, + /// id of the guild containing the channel + guild_id: Snowflake, + /// The type of channel + type: isize, + /// The name of the channel + name: []const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#reaction-object +pub const Reaction = struct { + /// Total isize of times this emoji has been used to react (including super reacts) + count: isize, + /// + count_details: ReactionCountDetails, + /// Whether the current user reacted using this emoji + me: bool, + /// + me_burst: bool, + /// Emoji information + emoji: Partial(Emoji), + /// HEX colors used for super reaction + burst_colors: [][]const u8, +}; + +/// https://discord.com/developers/docs/resources/channel#get-reactions-reaction-types +pub const ReactionType = enum { + Normal, + Burst, +}; + +/// https://discord.com/developers/docs/resources/channel#reaction-count-details-object +pub const ReactionCountDetails = struct { + /// Count of super reactions + burst: isize, + /// + normal: isize, +}; + +/// https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure +pub const MessageActivity = struct { + /// Type of message activity + type: MessageActivityTypes, + /// `party_id` from a Rich Presence event + party_id: ?Snowflake, +}; + +/// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure +pub const MessageReference = struct { + /// Type of reference + type: ?MessageReferenceType, + /// id of the originating message + message_id: ?Snowflake, + /// + /// id of the originating message's channel + /// Note: `channel_id` is optional when creating a reply, but will always be present when receiving an event/response that includes this data model., + /// + channel_id: ?Snowflake, + /// id of the originating message's guild + guild_id: ?Snowflake, + /// When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true + fail_if_not_exists: bool, +}; + +/// https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-types +pub const MessageReferenceType = enum { + /// + /// A standard reference used by replies. + /// + /// @remarks + /// When the type is set to this value, the field referenced_message on the message will be present + /// + Default, + /// + /// Reference used to point to a message at a point in time. + /// + /// @remarks + /// When the type is set to this value, the field message_snapshot on the message will be present + /// + /// This value can only be used for basic messages; + /// i.e. messages which do not have strong bindings to a non global entity. + /// Thus we support only messages with `DEFAULT` or `REPLY` types, but disallowed if there are any polls, calls, or components. + /// + Forward, +}; + +/// https://discord.com/developers/docs/resources/channel#message-snapshot-object-message-snapshot-structure +pub const MessageSnapshot = struct { + /// https://discord.com/developers/docs/resources/channel#message-object + /// Minimal subset of fields in the forwarded message + message: struct { + content: ?[]const u8, + timestamp: []const u8, + edited_timestamp: ?[]const u8, + mentions: []struct { + username: []const u8, + global_name: ?[]const u8, + locale: ?[]const u8, + flags: ?isize, + premium_type: ?PremiumTypes, + public_flags: ?isize, + accent_color: ?isize, + id: Snowflake, + discriminator: []const u8, + avatar: ?[]const u8, + bot: ?bool, + system: ?bool, + mfa_enabled: ?bool, + verified: ?bool, + email: ?[]const u8, + banner: ?[]const u8, + avatar_decoration_data: ?AvatarDecorationData, + member: ?Partial(Member), + }, + mention_roles: []?[]const u8, + type: MessageTypes, + flags: ?MessageFlags, + stickers: []?Sticker, + components: ?[]MessageComponent, + sticker_items: []?StickerItem, + attachments: []Attachment, + embeds: []Embed, + }, +}; + +/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-structure +pub const Sticker = struct { + /// [Id of the sticker](https://discord.com/developers/docs/reference#image-formatting) + id: Snowflake, + /// Id of the pack the sticker is from + pack_id: ?Snowflake, + /// Name of the sticker + name: []const u8, + /// Description of the sticker + description: []const u8, + /// a unicode emoji representing the sticker's expression + tags: []const u8, + /// [type of sticker](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types) + type: StickerTypes, + /// [Type of sticker format](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types) + format_type: StickerFormatTypes, + /// Whether or not the sticker is available + available: ?bool, + /// Id of the guild that owns this sticker + guild_id: ?Snowflake, + /// The user that uploaded the sticker + user: ?User, + /// A sticker's sort order within a pack + sort_value: ?isize, +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object-message-interaction-structure +pub const MessageInteraction = struct { + /// Id of the interaction + id: Snowflake, + /// The type of interaction + type: InteractionTypes, + /// The name of the ApplicationCommand including the name of the subcommand/subcommand group + name: []const u8, + /// The user who invoked the interaction + user: User, + /// The member who invoked the interaction in the guild + member: ?Partial(Member), +}; + +/// https://discord.com/developers/docs/resources/channel#message-interaction-metadata-object-message-interaction-metadata-structure +pub const MessageInteractionMetadata = struct { + /// Id of the interaction + id: Snowflake, + /// The type of interaction + type: InteractionTypes, + /// User who triggered the interaction + user: User, + // IDs for installation context(s) related to an interaction + // authorizing_integration_owners: Partial(AutoArrayHashMap(ApplicationIntegrationType, []const u8)), + /// ID of the original response message, present only on follow-up messages + original_response_message_id: ?Snowflake, + /// ID of the message that contained interactive component, present only on messages created from component interactions + interacted_message_id: ?Snowflake, + /// Metadata for the interaction that was used to open the modal, present only on modal submit interactions + /// TAKES A POINTER + triggering_interaction_metadata: ?*MessageInteractionMetadata, +}; + +/// https://discord.com/developers/docs/resources/sticker#sticker-item-object-sticker-item-structure +pub const StickerItem = struct { + /// Id of the sticker + id: Snowflake, + /// Name of the sticker + name: []const u8, + /// [Type of sticker format](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types) + format_type: StickerFormatTypes, +}; + +/// https://discord.com/developers/docs/resources/sticker#sticker-pack-object-sticker-pack-structure +pub const StickerPack = struct { + /// id of the sticker pack + id: Snowflake, + /// the stickers in the pack + stickers: []Sticker, + /// name of the sticker pack + name: []const u8, + /// id of the pack's SKU + sku_id: Snowflake, + /// id of a sticker in the pack which is shown as the pack's icon + cover_sticker_id: ?Snowflake, + /// description of the sticker pack + description: []const u8, + /// id of the sticker pack's [banner image](https://discord.com/developers/docs/reference#image-formatting) + banner_asset_id: ?Snowflake, +}; diff --git a/src/structures/monetization.zig b/src/structures/monetization.zig new file mode 100644 index 0000000..55bd5dd --- /dev/null +++ b/src/structures/monetization.zig @@ -0,0 +1,74 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const SkuFlags = @import("shared.zig").SkuFlags; + +/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-structure +pub const Entitlement = struct { + /// ID of the entitlement + id: Snowflake, + /// ID of the SKU + sku_id: Snowflake, + /// ID of the user that is granted access to the entitlement's sku + user_id: ?Snowflake, + /// ID of the guild that is granted access to the entitlement's sku + guild_id: ?Snowflake, + /// ID of the parent application + application_id: Snowflake, + /// Type of entitlement + type: EntitlementType, + /// Entitlement was deleted + deleted: bool, + /// Start date at which the entitlement is valid. Not present when using test entitlements + starts_at: ?[]const u8, + /// Date at which the entitlement is no longer valid. Not present when using test entitlements + ends_at: ?[]const u8, + /// For consumable items, whether or not the entitlement has been consumed + consumed: ?bool, +}; + +/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-types +pub const EntitlementType = enum(u4) { + /// Entitlement was purchased by user + Purchase = 1, + ///Entitlement for Nitro subscription + PremiumSubscription = 2, + /// Entitlement was gifted by developer + DeveloperGift = 3, + /// Entitlement was purchased by a dev in application test mode + TestModePurchase = 4, + /// Entitlement was granted when the SKU was free + FreePurchase = 5, + /// Entitlement was gifted by another user + UserGift = 6, + /// Entitlement was claimed by user for free as a Nitro Subscriber + PremiumPurchase = 7, + /// Entitlement was purchased as an app subscription + ApplicationSubscription = 8, +}; + +/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-structure +pub const Sku = struct { + /// ID of SKU + id: Snowflake, + /// Type of SKU + type: SkuType, + /// ID of the parent application + application_id: Snowflake, + /// Customer-facing name of your premium offering + name: []const u8, + /// System-generated URL slug based on the SKU's name + slug: []const u8, + /// SKU flags combined as a bitfield + flags: SkuFlags, +}; + +/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-types +pub const SkuType = enum(u4) { + /// Durable one-time purchase + Durable = 2, + /// Consumable one-time purchase + Consumable = 3, + /// Represents a recurring subscription + Subscription = 5, + /// System-generated group for each SUBSCRIPTION SKU created + SubscriptionGroup = 6, +}; diff --git a/src/structures/oauth.zig b/src/structures/oauth.zig new file mode 100644 index 0000000..0a0f390 --- /dev/null +++ b/src/structures/oauth.zig @@ -0,0 +1,47 @@ +const OAuth2Scope = @import("shared.zig").OAuth2Scope; +const Guild = @import("guild.zig").Guild; +const IncomingWebhook = @import("webhook.zig").IncomingWebhook; + +pub const TokenExchangeAuthorizationCode = struct { + grant_type: []const u8, //"authorization_code", + /// The code for the token exchange + code: []const u8, + /// The redirect_uri associated with this authorization + redirect_uri: []const u8, +}; + +/// https://discord.com/developers/docs/topics/oauth2#client-credentials-grant +pub const TokenExchangeRefreshToken = struct { + grant_type: "refresh_token", + /// the user's refresh token + refresh_token: []const u8, +}; + +/// https://discord.com/developers/docs/topics/oauth2#client-credentials-grant +pub const TokenExchangeClientCredentials = struct { + grant_type: "client_credentials", + /// The scope(s) for the access token + scope: []OAuth2Scope, +}; + +pub const AccessTokenResponse = struct { + /// The access token of the user + access_token: []const u8, + /// The type of token + token_type: []const u8, + /// The isize of seconds after that the access token is expired + expires_in: isize, + /// + /// The refresh token to refresh the access token + /// + /// @remarks + /// When the token exchange is a client credentials type grant this value is not defined. + /// + refresh_token: []const u8, + /// The scopes for the access token + scope: []const u8, + /// The webhook the user created for the application. Requires the `webhook.incoming` scope + webhook: ?IncomingWebhook, + /// The guild the bot has been added. Requires the `bot` scope + guild: ?Guild, +}; diff --git a/src/structures/partial.zig b/src/structures/partial.zig new file mode 100644 index 0000000..e4b7149 --- /dev/null +++ b/src/structures/partial.zig @@ -0,0 +1,40 @@ +const std = @import("std"); + +pub fn Partial(comptime T: type) type { + const info = @typeInfo(T); + switch (info) { + .@"struct" => |s| { + comptime var fields: []const std.builtin.Type.StructField = &[_]std.builtin.Type.StructField{}; + inline for (s.fields) |field| { + if (field.is_comptime) { + @compileError("Cannot make Partial of " ++ @typeName(T) ++ ", it has a comptime field " ++ field.name); + } + const optional_type = switch (@typeInfo(field.type)) { + .optional => field.type, + else => ?field.type, + }; + const default_value: optional_type = null; + const aligned_ptr: *align(field.alignment) const anyopaque = @alignCast(@ptrCast(&default_value)); + const optional_field: [1]std.builtin.Type.StructField = [_]std.builtin.Type.StructField{.{ + .alignment = field.alignment, + .default_value = aligned_ptr, + .is_comptime = false, + .name = field.name, + .type = optional_type, + }}; + fields = fields ++ optional_field; + } + const partial_type_info: std.builtin.Type = .{ .@"struct" = .{ + .backing_integer = s.backing_integer, + .decls = &[_]std.builtin.Type.Declaration{}, + .fields = fields, + .is_tuple = s.is_tuple, + .layout = s.layout, + } }; + return @Type(partial_type_info); + }, + else => @compileError("Cannot make Partial of " ++ @typeName(T) ++ + ", the type must be a struct"), + } + unreachable; +} diff --git a/src/structures/poll.zig b/src/structures/poll.zig new file mode 100644 index 0000000..0693604 --- /dev/null +++ b/src/structures/poll.zig @@ -0,0 +1,120 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const User = @import("user.zig").User; +const Emoji = @import("emoji.zig").Emoji; +const Partial = @import("partial.zig").Partial; + +/// https://discord.com/developers/docs/resources/poll#poll-object +pub const Poll = struct { + /// The question of the poll. Only `text` is supported. + question: PollMedia, + /// Each of the answers available in the poll. There is a maximum of 10 answers per poll. + answers: []PollAnswer, + /// + /// The time when the poll ends. + /// + /// @remarks + /// `expiry` is marked as nullable to support non-expiring polls in the future, but all polls have an expiry currently. + /// + expiry: ?[]const u8, + /// Whether a user can select multiple answers + allow_multiselect: bool, + /// The layout type of the poll + layout_type: PollLayoutType, + /// + /// The results of the poll + /// + /// @remarks + /// This value will not be sent by discord under specific conditions where they don't fetch them on their backend. When this value is missing it should be interpreted as "Unknown results" and not as "No results" + ///The results may not be totally accurate while the poll has not ended. When it ends discord will re-calculate all the results and set {@link PollResult.is_finalized}; to true + /// + results: ?PollResult, +}; + +/// https://discord.com/developers/docs/resources/poll#layout-type +pub const PollLayoutType = enum(u4) { + /// The default layout + Default = 1, +}; + +/// https://discord.com/developers/docs/resources/poll#poll-media-object +pub const PollMedia = struct { + /// + /// The text of the field + /// + /// @remarks + /// `text` should always be non-null for both questions and answers, but this is subject to changes. + /// The maximum length of `text` is 300 for the question, and 55 for any answer. + /// + text: ?[]const u8, + /// + /// The emoji of the field + /// + /// @remarks + /// When creating a poll answer with an emoji, one only needs to send either the `id` (custom emoji) or `name` (default emoji) as the only field. + /// + emoji: ?Partial(Emoji), +}; + +/// https://discord.com/developers/docs/resources/poll#poll-answer-object +pub const PollAnswer = struct { + /// + /// The id of the answer + /// + /// @remarks + ///This id labels each answer. It starts at 1 and goes up sequentially. recommend against depending on this value as is a implementation detail. + /// + answer_id: isize, + /// The data of the answer + poll_media: PollMedia, +}; + +pub const PollAnswerCount = struct { + ///The {@link PollAnswer.answer_id | answer_id}; + id: isize, + /// The isize of votes for this answer + count: isize, + /// Whether the current user voted for this answer + me_voted: bool, +}; + +/// https://discord.com/developers/docs/resources/poll#poll-results-object +pub const PollResult = struct { + /// Whether the votes have been precisely counted + is_finalized: bool, + /// The counts for each answer + answer_counts: []PollAnswerCount, +}; + +/// https://discord.com/developers/docs/resources/poll#get-answer-voters-response-body +pub const GetAnswerVotesResponse = struct { + /// Users who voted for this answer + users: []User, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add +pub const PollVoteAdd = struct { + /// ID of the user. Usually a snowflake + user_id: Snowflake, + /// ID of the channel. Usually a snowflake + channel_id: Snowflake, + /// ID of the message. Usually a snowflake + message_id: Snowflake, + /// ID of the guild. Usually a snowflake + guild_id: ?Snowflake, + /// ID of the answer. + answer_id: isize, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove +pub const PollVoteRemove = struct { + /// ID of the user. Usually a snowflake + user_id: Snowflake, + /// ID of the channel. Usually a snowflake + channel_id: Snowflake, + /// ID of the message. Usually a snowflake + message_id: Snowflake, + /// ID of the guild. Usually a snowflake + guild_id: ?Snowflake, + /// ID of the answer. + answer_id: isize, +}; diff --git a/src/structures/role.zig b/src/structures/role.zig new file mode 100644 index 0000000..d865fd1 --- /dev/null +++ b/src/structures/role.zig @@ -0,0 +1,49 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const RoleFlags = @import("shared.zig").RoleFlags; + +/// https://discord.com/developers/docs/topics/permissions#role-object-role-structure +pub const Role = struct { + /// Role id + id: Snowflake, + /// If this role is showed separately in the user listing + hoist: bool, + /// Permission bit set + permissions: []const u8, + /// Whether this role is managed by an integration + managed: bool, + /// Whether this role is mentionable + mentionable: bool, + /// The tags this role has + tags: ?RoleTags, + /// the role emoji hash + icon: ?[]const u8, + /// Role name + name: []const u8, + /// Integer representation of hexadecimal color code + color: isize, + /// Position of this role (roles with the same position are sorted by id) + position: isize, + /// role unicode emoji + unicode_emoji: ?[]const u8, + /// Role flags combined as a bitfield + flags: RoleFlags, +}; + +/// https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure +pub const RoleTags = struct { + /// The id of the bot this role belongs to + bot_id: ?Snowflake, + /// The id of the integration this role belongs to + integration_id: ?Snowflake, + /// Whether this is the guild's premium subscriber role + /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". + premium_subscriber: ?bool, + /// Id of this role's subscription sku and listing. + subscription_listing_id: ?Snowflake, + /// Whether this role is available for purchase. + /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". + available_for_purchase: ?bool, + /// Whether this is a guild's linked role + /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". + guild_connections: ?bool, +}; diff --git a/src/structures/scheduled_event.zig b/src/structures/scheduled_event.zig new file mode 100644 index 0000000..2e4f11d --- /dev/null +++ b/src/structures/scheduled_event.zig @@ -0,0 +1,109 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const ScheduledEventPrivacyLevel = @import("shared.zig").ScheduledEventPrivacyLevel; +const ScheduledEventStatus = @import("shared.zig").ScheduledEventStatus; +const ScheduledEventEntityType = @import("shared.zig").ScheduledEventEntityType; +const User = @import("user.zig").User; + +pub const ScheduledEvent = struct { + /// the id of the scheduled event + id: Snowflake, + /// the guild id which the scheduled event belongs to + guild_id: Snowflake, + /// the channel id in which the scheduled event will be hosted if specified + channel_id: ?Snowflake, + /// the id of the user that created the scheduled event + creator_id: ?Snowflake, + /// the name of the scheduled event + name: []const u8, + /// the description of the scheduled event + description: ?[]const u8, + /// the time the scheduled event will start + scheduled_start_time: []const u8, + /// the time the scheduled event will end if it does end. + scheduled_end_time: ?[]const u8, + /// the privacy level of the scheduled event + privacy_level: ScheduledEventPrivacyLevel, + /// the status of the scheduled event + status: ScheduledEventStatus, + /// the type of hosting entity associated with a scheduled event + entity_type: ScheduledEventEntityType, + /// any additional id of the hosting entity associated with event + entity_id: ?Snowflake, + /// the entity metadata for the scheduled event + entity_metadata: ?ScheduledEventEntityMetadata, + /// the user that created the scheduled event + creator: ?User, + /// the isize of users subscribed to the scheduled event + user_count: ?isize, + /// the cover image hash of the scheduled event + image: ?[]const u8, + /// the definition for how often this event should recur + recurrence_rule: ?ScheduledEventRecurrenceRule, +}; + +pub const ScheduledEventEntityMetadata = struct { + /// location of the event + location: ?[]const u8, +}; + +pub const ScheduledEventRecurrenceRule = struct { + /// Starting time of the recurrence interval + start: []const u8, + /// Ending time of the recurrence interval + end: ?[]const u8, + /// How often the event occurs + frequency: ScheduledEventRecurrenceRuleFrequency, + /// 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, + /// List of specific days within a specific week (1-5) to recur on + by_n_weekday: []?ScheduledEventRecurrenceRuleNWeekday, + /// Set of specific months to recur on + by_month: []?ScheduledEventRecurrenceRuleMonth, + /// Set of specific dates within a month to recur on + by_month_day: []?isize, + /// Set of days within a year to recur on (1-364) + by_year_day: []?isize, + /// The total amount of times that the event is allowed to recur before stopping + count: ?isize, +}; + +pub const ScheduledEventRecurrenceRuleFrequency = enum { + Yearly, + Monthly, + Weekly, + Daily, +}; + +pub const ScheduledEventRecurrenceRuleWeekday = enum { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +}; + +pub const ScheduledEventRecurrenceRuleNWeekday = struct { + /// The week to reoccur on. 1 - 5 + n: isize, + /// The day within the week to reoccur on + day: ScheduledEventRecurrenceRuleWeekday, +}; + +pub const ScheduledEventRecurrenceRuleMonth = enum(u4) { + January = 1, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +}; diff --git a/src/structures/shared.zig b/src/structures/shared.zig new file mode 100644 index 0000000..9b964c9 --- /dev/null +++ b/src/structures/shared.zig @@ -0,0 +1,1523 @@ +const std = @import("std"); +const Snowflake = @import("snowflake.zig").Snowflake; +const zjson = @import("json"); + +pub const PresenceStatus = enum { + online, + dnd, + idle, + offline, +}; + +/// https://discord.com/developers/docs/resources/user#user-object-premium-types +pub const PremiumTypes = enum { + None, + NitroClassic, + Nitro, + NitroBasic, +}; + +/// https://discord.com/developers/docs/resources/user#user-object-user-flags +pub const UserFlags = packed struct { + pub fn toRaw(self: UserFlags) u32 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u32) UserFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u32)); + } + + DiscordEmployee: bool = false, + PartneredServerOwner: bool = false, + HypeSquadEventsMember: bool = false, + BugHunterLevel1: bool = false, + _pad: u3 = 0, + HouseBravery: bool = false, + HouseBrilliance: bool = false, + HouseBalance: bool = false, + EarlySupporter: bool = false, + TeamUser: bool = false, + _pad2: u4 = 0, + BugHunterLevel2: bool = false, + _pad3: u1 = 0, + VerifiedBot: bool = false, + EarlyVerifiedBotDeveloper: bool = false, + DiscordCertifiedModerator: bool = false, + BotHttpInteractions: bool = false, + _pad4: u3 = 0, + ActiveDeveloper: bool = false, + _pad5: u6 = 0, +}; + +pub const MemberFlags = packed struct { + pub fn toRaw(self: MemberFlags) u16 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u16) MemberFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u16)); + } + + /// + /// Member has left and rejoined the guild + /// + /// @remarks + /// This value is not editable + ////Message + DidRejoin: bool = false, + /// + /// Member has completed onboarding + /// + /// @remarks + /// This value is not editable + //// + CompletedOnboarding: bool = false, + /// Member is exempt from guild verification requirements + BypassesVerification: bool = false, + /// + /// Member has started onboarding + /// + /// @remarks + /// This value is not editable + //// + StartedOnboarding: bool = false, + /// + /// Member is a guest and can only access the voice channel they were invited to + /// + /// @remarks + /// This value is not editable + //// + IsGuest: bool = false, + /// + /// Member has started Server Guide new member actions + /// + /// @remarks + /// This value is not editable + //// + StartedHomeActions: bool = false, + /// + /// Member has completed Server Guide new member actions + /// + /// @remarks + /// This value is not editable + //// + CompletedHomeActions: bool = false, + /// + /// Member's username, display name, or nickname is blocked by AutoMod + /// + /// @remarks + /// This value is not editable + //// + AutomodQuarantinedUsername: bool = false, + _pad: u1 = 0, + /// + /// Member has dismissed the DM settings upsell + /// + /// @remarks + /// This value is not editable + //// + DmSettingsUpsellAcknowledged: bool = false, + _pad2: u6 = 0, +}; + +/// https://discord.com/developers/docs/resources/channel#channels-resource +pub const ChannelFlags = packed struct { + pub fn toRaw(self: ChannelFlags) u32 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u32) ChannelFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u32)); + } + + None: bool = false, + /// this thread is pinned to the top of its parent `GUILD_FORUM` channel + Pinned: bool = false, + _pad: u3 = 0, + /// Whether a tag is required to be specified when creating a thread in a `GUILD_FORUM` or a GUILD_MEDIA channel. Tags are specified in the `applied_tags` field. + RequireTag: bool = false, + _pad1: u11 = 0, + /// When set hides the embedded media download options. Available only for media channels. + HideMediaDownloadOptions: bool = false, + _pad2: u14, +}; + +/// https://discord.com/developers/docs/topics/permissions#role-object-role-flags +pub const RoleFlags = packed struct { + pub fn toRaw(self: RoleFlags) u2 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u2) RoleFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u2)); + } + + None: bool = false, + /// Role can be selected by members in an onboarding prompt + InPrompt: bool = false, +}; + +pub const AttachmentFlags = packed struct { + pub fn toRaw(self: AttachmentFlags) u8 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u8) AttachmentFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u8)); + } + + None: bool = false, + _pad: u1 = 0, + /// This attachment has been edited using the remix feature on mobile + IsRemix: bool = false, + _pad1: u5 = 0, +}; + +/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-flags +pub const SkuFlags = packed struct { + pub fn toRaw(self: SkuFlags) u16 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u16) SkuFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u16)); + } + + _pad: u2 = 0, + /// SKU is available for purchase + Available: bool = false, + _pad1: u5 = 0, + /// Recurring SKU that can be purchased by a user and applied to a single server. Grants access to every user in that server. + GuildSubscription: bool = false, + /// Recurring SKU purchased by a user for themselves. Grants access to the purchasing user in every server. + UserSubscription: bool = false, + _pad2: u6, +}; + +/// https://discord.com/developers/docs/resources/channel#message-object-message-flags +pub const MessageFlags = packed struct { + pub fn toRaw(self: MessageFlags) u16 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u16) MessageFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u16)); + } + + /// This message has been published to subscribed channels (via Channel Following) + Crossposted: bool = false, + /// This message originated from a message in another channel (via Channel Following) + IsCrosspost: bool = false, + /// Do not include any embeds when serializing this message + SuppressEmbeds: bool = false, + /// The source message for this crosspost has been deleted (via Channel Following) + SourceMessageDeleted: bool = false, + /// This message came from the urgent message system + Urgent: bool = false, + /// This message has an associated thread, with the same id as the message + HasThread: bool = false, + /// This message is only visible to the user who invoked the Interaction + Ephemeral: bool = false, + /// This message is an Interaction Response and the bot is "thinking" + Loading: bool = false, + /// This message failed to mention some roles and add their members to the thread + FailedToMentionSomeRolesInThread: bool = false, + _pad: u4 = 0, + /// This message will not trigger push and desktop notifications + SuppressNotifications: bool = false, + /// This message is a voice message + IsVoiceMessage: bool = false, + _pad1: u1 = 0, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-flags +pub const ActivityFlags = packed struct { + pub fn toRaw(self: MessageFlags) u16 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u16) MessageFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u16)); + } + + Instance: bool = false, + Join: bool = false, + Spectate: bool = false, + JoinRequest: bool = false, + Sync: bool = false, + Play: bool = false, + PartyPrivacyFriends: bool = false, + PartyPrivacyVoiceChannel: bool = false, + Embedded: bool = false, + _pad: u7 = 0, +}; + +/// https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors +pub const IntegrationExpireBehaviors = enum(u4) { + RemoveRole = 0, + Kick = 1, +}; + +/// https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum +pub const TeamMembershipStates = enum(u4) { + Invited = 1, + Accepted = 2, +}; + +/// https://discord.com/developers/docs/topics/oauth2#application-application-flags +pub const ApplicationFlags = packed struct { + pub fn toRaw(self: ApplicationFlags) u32 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u32) ApplicationFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u32)); + } + + _pad: u5 = 0, + /// Indicates if an app uses the Auto Moderation API. + ApplicationAutoModerationRuleCreateBadge: bool = false, + _pad1: u6 = 0, + /// Intent required for bots in **100 or more servers* /// to receive 'presence_update' events + GatewayPresence: bool = false, + /// Intent required for bots in under 100 servers to receive 'presence_update' events + GatewayPresenceLimited: bool = false, + /// Intent required for bots in **100 or more servers* /// to receive member-related events like 'guild_member_add'. + GatewayGuildMembers: bool = false, + /// Intent required for bots in under 100 servers to receive member-related events like 'guild_member_add'. + GatewayGuildMembersLimited: bool = false, + /// Indicates unusual growth of an app that prevents verification + VerificationPendingGuildLimit: bool = false, + /// Indicates if an app is embedded within the Discord client (currently unavailable publicly) + Embedded: bool = false, + /// Intent required for bots in **100 or more servers* /// to receive [message content](https://support-dev.discord.com/hc/en-us/articles/4404772028055) + GatewayMessageContent: bool = false, + /// Intent required for bots in under 100 servers to receive [message content](https://support-dev.discord.com/hc/en-us/articles/4404772028055), found in Bot Settings + GatewayMessageContentLimited: bool = false, + _pad2: u4 = 0, + /// Indicates if an app has registered global application commands + ApplicationCommandBadge: bool = false, + _pad3: u7, +}; + +/// https://discord.com/developers/docs/interactions/message-components#component-types +pub const MessageComponentTypes = enum(u4) { + /// A container for other components + ActionRow = 1, + /// A button object + Button, + /// A select menu for picking from choices + SelectMenu, + /// A text input object + InputText, + /// Select menu for users + SelectMenuUsers, + /// Select menu for roles + SelectMenuRoles, + /// Select menu for users and roles + SelectMenuUsersAndRoles, + /// Select menu for channels + SelectMenuChannels, +}; + +pub const TextStyles = enum(u4) { + /// Intended for short single-line text + Short = 1, + /// Intended for much longer inputs + Paragraph = 2, +}; + +/// https://discord.com/developers/docs/interactions/message-components#buttons-button-styles +pub const ButtonStyles = enum(u4) { + /// A blurple button + Primary = 1, + /// A grey button + Secondary, + /// A green button + Success, + /// A red button + Danger, + /// A button that navigates to a URL + Link, + /// A blurple button to show a Premium item in the shop + Premium, +}; + +/// https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types +pub const AllowedMentionsTypes = enum { + /// Controls role mentions + roles, + /// Controls user mentions + users, + /// Controls \@everyone and \@here mentions + everyone, +}; + +/// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types +pub const WebhookTypes = enum(u4) { + /// Incoming Webhooks can post messages to channels with a generated token + Incoming = 1, + /// Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels + ChannelFollower, + /// Application webhooks are webhooks used with Interactions + Application, +}; + +/// https://discord.com/developers/docs/resources/channel#embed-object-embed-types +/// perhaps union? +pub const EmbedTypes = enum { + rich, + image, + video, + gifv, + article, + link, + poll_res, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level +pub const DefaultMessageNotificationLevels = enum { + /// Members will receive notifications for all messages by default + AllMessages, + /// Members will receive notifications only for messages that \@mention them by default + OnlyMentions, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level +pub const ExplicitContentFilterLevels = enum { + /// Media content will not be scanned + Disabled, + /// Media content sent by members without roles will be scanned + MembersWithoutRoles, + /// Media content sent by all members will be scanned + AllMembers, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-verification-level +pub const VerificationLevels = enum { + /// Unrestricted + None, + /// Must have verified email on account + Low, + /// Must be registered on Discord for longer than 5 minutes + Medium, + /// Must be a member of the server for longer than 10 minutes + High, + /// Must have a verified phone number + VeryHigh, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-guild-features +pub const GuildFeatures = enum { + /// Guild has access to set an invite splash background + INVITE_SPLASH, + /// Guild has access to set a vanity URL + VANITY_URL, + /// Guild is verified + VERIFIED, + /// Guild is partnered + PARTNERED, + /// Guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates + COMMUNITY, + /// Guild has enabled monetization. + CREATOR_MONETIZABLE_PROVISIONAL, + /// Guild has enabled the role subscription promo page. + CREATOR_STORE_PAGE, + /// Guild has been set as a support server on the App Directory + DEVELOPER_SUPPORT_SERVER, + /// Guild has access to create news channels + NEWS, + /// Guild is able to be discovered in the directory + DISCOVERABLE, + /// Guild is able to be featured in the directory + FEATURABLE, + /// Guild has access to set an animated guild icon + ANIMATED_ICON, + /// Guild has access to set a guild banner image + BANNER, + /// Guild has enabled the welcome screen + WELCOME_SCREEN_ENABLED, + /// Guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) + MEMBER_VERIFICATION_GATE_ENABLED, + /// Guild can be previewed before joining via Membership Screening or the directory + PREVIEW_ENABLED, + /// Guild has enabled ticketed events + TICKETED_EVENTS_ENABLED, + /// Guild has increased custom sticker slots + MORE_STICKERS, + /// Guild is able to set role icons + ROLE_ICONS, + /// Guild has role subscriptions that can be purchased. + ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE, + /// Guild has enabled role subscriptions. + ROLE_SUBSCRIPTIONS_ENABLED, + /// Guild has set up auto moderation rules + AUTO_MODERATION, + /// Guild has paused invites, preventing new users from joining + INVITES_DISABLED, + /// Guild has access to set an animated guild banner image + ANIMATED_BANNER, + /// Guild has disabled alerts for join raids in the configured safety alerts channel + RAID_ALERTS_DISABLED, + /// Guild is using the old permissions configuration behavior + APPLICATION_COMMAND_PERMISSIONS_V2, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-mfa-level +pub const MfaLevels = enum { + /// Guild has no MFA/2FA requirement for moderation actions + None, + /// Guild has a 2FA requirement for moderation actions + Elevated, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags +pub const SystemChannelFlags = packed struct { + pub fn toRaw(self: SystemChannelFlags) u8 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u8) SystemChannelFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u8)); + } + + /// Suppress member join notifications + SuppressJoinNotifications: bool = false, + /// Suppress server boost notifications + SuppressPremiumSubscriptions: bool = false, + /// Suppress server setup tips + SuppressGuildReminderNotifications: bool = false, + /// Hide member join sticker reply buttons + SuppressJoinNotificationReplies: bool = false, + _pad: u4 = 0, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-premium-tier +pub const PremiumTiers = enum { + /// Guild has not unlocked any Server Boost perks + None, + /// Guild has unlocked Server Boost level 1 perks + Tier1, + /// Guild has unlocked Server Boost level 2 perks + Tier2, + /// Guild has unlocked Server Boost level 3 perks + Tier3, +}; + +/// https://discord.com/developers/docs/resources/guild#guild-object-guild-nsfw-level +pub const GuildNsfwLevel = enum { + Default, + Explicit, + Safe, + AgeRestricted, +}; + +/// https://discord.com/developers/docs/resources/channel#channel-object-channel-types +pub const ChannelTypes = packed struct { + pub fn toRaw(self: ChannelTypes) u32 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u32) ChannelTypes { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u32)); + } + + /// A text channel within a server + GuildText: bool = false, + /// A direct message between users + DM: bool = false, + /// A voice channel within a server + GuildVoice: bool = false, + /// A direct message between multiple users + GroupDm: bool = false, + /// An organizational category that contains up to 50 channels + GuildCategory: bool = false, + /// A channel that users can follow and crosspost into their own server + GuildAnnouncement: bool = false, + _pad: u4 = 0, + /// A temporary sub-channel within a GUILD_ANNOUNCEMENT channel + AnnouncementThread: bool = false, + /// A temporary sub-channel within a GUILD_TEXT or GUILD_FORUM channel + PublicThread: bool = false, + /// A temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission + PrivateThread: bool = false, + /// A voice channel for hosting events with an audience + GuildStageVoice: bool = false, + /// A channel in a hub containing the listed servers + GuildDirectory: bool = false, + /// A channel which can only contains threads + GuildForum: bool = false, + /// Channel that can only contain threads, similar to GUILD_FORUM channels + GuildMedia: bool = false, + _pad1: u15 = 0, +}; + +pub const OverwriteTypes = enum { + Role, + Member, +}; + +pub const VideoQualityModes = enum(u4) { + /// Discord chooses the quality for optimal performance + Auto = 1, + /// 720p + Full, +}; + +/// https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types +pub const ActivityTypes = enum(u4) { + Playing = 0, + Streaming = 1, + Listening = 2, + Watching = 3, + Custom = 4, + Competing = 5, +}; + +/// https://discord.com/developers/docs/resources/channel#message-object-message-types +pub const MessageTypes = enum(u8) { + Default, + RecipientAdd, + RecipientRemove, + Call, + ChannelNameChange, + ChannelIconChange, + ChannelPinnedMessage, + UserJoin, + GuildBoost, + GuildBoostTier1, + GuildBoostTier2, + GuildBoostTier3, + ChannelFollowAdd, + GuildDiscoveryDisqualified = 14, + GuildDiscoveryRequalified, + GuildDiscoveryGracePeriodInitialWarning, + GuildDiscoveryGracePeriodFinalWarning, + ThreadCreated, + Reply, + ChatInputCommand, + ThreadStarterMessage, + GuildInviteReminder, + ContextMenuCommand, + AutoModerationAction, + RoleSubscriptionPurchase, + InteractionPremiumUpsell, + StageStart, + StageEnd, + StageSpeaker, + StageTopic = 31, + GuildApplicationPremiumSubscription, + GuildIncidentAlertModeEnabled = 36, + GuildIncidentAlertModeDisabled, + GuildIncidentReportRaid, + GuildIncidentReportFalseAlarm, + PurchaseNotification = 44, + PollResult = 46, +}; + +/// https://discord.com/developers/docs/resources/channel#message-object-message-activity-types +pub const MessageActivityTypes = enum(u4) { + Join = 1, + Spectate = 2, + Listen = 3, + JoinRequest = 5, +}; + +/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types +pub const StickerTypes = enum(u4) { + /// an official sticker in a pack + Standard = 1, + /// a sticker uploaded to a guild for the guild's members + Guild = 2, +}; + +/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types +pub const StickerFormatTypes = enum(u4) { + Png = 1, + APng, + Lottie, + Gif, +}; + +/// https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype +pub const InteractionTypes = enum(u4) { + Ping = 1, + ApplicationCommand = 2, + MessageComponent = 3, + ApplicationCommandAutocomplete = 4, + ModalSubmit = 5, +}; + +/// https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype +pub const ApplicationCommandOptionTypes = enum(u4) { + SubCommand = 1, + SubCommandGroup, + String, + Integer, + Boolean, + User, + Channel, + Role, + Mentionable, + Number, + Attachment, +}; + +/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events +pub const AuditLogEvents = enum(u4) { + /// Server settings were updated + GuildUpdate = 1, + /// Channel was created + ChannelCreate = 10, + /// Channel settings were updated + ChannelUpdate, + /// Channel was deleted + ChannelDelete, + /// Permission overwrite was added to a channel + ChannelOverwriteCreate, + /// Permission overwrite was updated for a channel + ChannelOverwriteUpdate, + /// Permission overwrite was deleted from a channel + ChannelOverwriteDelete, + /// Member was removed from server + MemberKick = 20, + /// Members were pruned from server + MemberPrune, + /// Member was banned from server + MemberBanAdd, + /// Server ban was lifted for a member + MemberBanRemove, + /// Member was updated in server + MemberUpdate, + /// Member was added or removed from a role + MemberRoleUpdate, + /// Member was moved to a different voice channel + MemberMove, + /// Member was disconnected from a voice channel + MemberDisconnect, + /// Bot user was added to server + BotAdd, + /// Role was created + RoleCreate = 30, + /// Role was edited + RoleUpdate, + /// Role was deleted + RoleDelete, + /// Server invite was created + InviteCreate = 40, + /// Server invite was updated + InviteUpdate, + /// Server invite was deleted + InviteDelete, + /// Webhook was created + WebhookCreate = 50, + /// Webhook properties or channel were updated + WebhookUpdate, + /// Webhook was deleted + WebhookDelete, + /// Emoji was created + EmojiCreate = 60, + /// Emoji name was updated + EmojiUpdate, + /// Emoji was deleted + EmojiDelete, + /// Single message was deleted + MessageDelete = 72, + /// Multiple messages were deleted + MessageBulkDelete, + /// Messaged was pinned to a channel + MessagePin, + /// Message was unpinned from a channel + MessageUnpin, + /// App was added to server + IntegrationCreate = 80, + /// App was updated (as an example, its scopes were updated) + IntegrationUpdate, + /// App was removed from server + IntegrationDelete, + /// Stage instance was created (stage channel becomes live) + StageInstanceCreate, + /// Stage instace details were updated + StageInstanceUpdate, + /// Stage instance was deleted (stage channel no longer live) + StageInstanceDelete, + /// Sticker was created + StickerCreate = 90, + /// Sticker details were updated + StickerUpdate, + /// Sticker was deleted + StickerDelete, + /// Event was created + GuildScheduledEventCreate = 100, + /// Event was updated + GuildScheduledEventUpdate, + /// Event was cancelled + GuildScheduledEventDelete, + /// Thread was created in a channel + ThreadCreate = 110, + /// Thread was updated + ThreadUpdate, + /// Thread was deleted + ThreadDelete, + /// Permissions were updated for a command + ApplicationCommandPermissionUpdate = 121, + /// Auto moderation rule was created + AutoModerationRuleCreate = 140, + /// Auto moderation rule was updated + AutoModerationRuleUpdate, + /// Auto moderation rule was deleted + AutoModerationRuleDelete, + /// Message was blocked by AutoMod according to a rule. + AutoModerationBlockMessage, + /// Message was flagged by AutoMod + AudoModerationFlagMessage, + /// Member was timed out by AutoMod + AutoModerationMemberTimedOut, + /// Creator monetization request was created + CreatorMonetizationRequestCreated = 150, + /// Creator monetization terms were accepted + CreatorMonetizationTermsAccepted, + /// Guild Onboarding Question was created + OnBoardingPromptCreate = 163, + /// Guild Onboarding Question was updated + OnBoardingPromptUpdate, + /// Guild Onboarding Question was deleted + OnBoardingPromptDelete, + /// Guild Onboarding was created + OnBoardingCreate, + /// Guild Onboarding was updated + OnBoardingUpdate, + /// Guild Server Guide was created + HomeSettingsCreate = 190, + /// Guild Server Guide was updated + HomeSettingsUpdate, +}; + +pub const ScheduledEventPrivacyLevel = enum(u4) { + /// the scheduled event is only accessible to guild members + GuildOnly = 2, +}; + +pub const ScheduledEventEntityType = enum(u4) { + StageInstance = 1, + Voice, + External, +}; + +pub const ScheduledEventStatus = enum(u4) { + Scheduled = 1, + Active, + Completed, + Canceled, +}; + +/// https://discord.com/developers/docs/resources/invite#invite-object-target-user-types +pub const TargetTypes = enum(u4) { + Stream = 1, + EmbeddedApplication, +}; + +pub const ApplicationCommandTypes = enum(u4) { + /// A text-based command that shows up when a user types `/` + ChatInput = 1, + /// A UI-based command that shows up when you right click or tap on a user + User, + /// A UI-based command that shows up when you right click or tap on a message + Message, + /// A UI-based command that represents the primary way to invoke an app's Activity + PrimaryEntryPoint, +}; + +pub const ApplicationCommandPermissionTypes = enum(u4) { + Role = 1, + User, + Channel, +}; + +/// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags +pub const BitwisePermissionFlags = packed struct { + pub fn toRaw(self: BitwisePermissionFlags) u64 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u64) BitwisePermissionFlags { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u64)); + } + + /// Allows creation of instant invites + CREATE_INSTANT_INVITE: bool = false, + /// Allows kicking members + KICK_MEMBERS: bool = false, + /// Allows banning members + BAN_MEMBERS: bool = false, + /// Allows all permissions and bypasses channel permission overwrites + ADMINISTRATOR: bool = false, + /// Allows management and editing of channels + MANAGE_CHANNELS: bool = false, + /// Allows management and editing of the guild + MANAGE_GUILD: bool = false, + /// Allows for the addition of reactions to messages + ADD_REACTIONS: bool = false, + /// Allows for viewing of audit logs + VIEW_AUDIT_LOG: bool = false, + /// Allows for using priority speaker in a voice channel + PRIORITY_SPEAKER: bool = false, + /// Allows the user to go live + STREAM: bool = false, + /// Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels + VIEW_CHANNEL: bool = false, + /// Allows for sending messages in a channel. (does not allow sending messages in threads) + SEND_MESSAGES: bool = false, + /// Allows for sending of /tts messages + SEND_TTS_MESSAGES: bool = false, + /// Allows for deletion of other users messages + MANAGE_MESSAGES: bool = false, + /// Links sent by users with this permission will be auto-embedded + EMBED_LINKS: bool = false, + /// Allows for uploading images and files + ATTACH_FILES: bool = false, + /// Allows for reading of message history + READ_MESSAGE_HISTORY: bool = false, + /// Allows for using the \@everyone tag to notify all users in a channel, and the \@here tag to notify all online users in a channel + MENTION_EVERYONE: bool = false, + /// Allows the usage of custom emojis from other servers + USE_EXTERNAL_EMOJIS: bool = false, + /// Allows for viewing guild insights + VIEW_GUILD_INSIGHTS: bool = false, + /// Allows for joining of a voice channel + CONNECT: bool = false, + /// Allows for speaking in a voice channel + SPEAK: bool = false, + /// Allows for muting members in a voice channel + MUTE_MEMBERS: bool = false, + /// Allows for deafening of members in a voice channel + DEAFEN_MEMBERS: bool = false, + /// Allows for moving of members between voice channels + MOVE_MEMBERS: bool = false, + /// Allows for using voice-activity-detection in a voice channel + USE_VAD: bool = false, + /// Allows for modification of own nickname + CHANGE_NICKNAME: bool = false, + /// Allows for modification of other users nicknames + MANAGE_NICKNAMES: bool = false, + /// Allows management and editing of roles + MANAGE_ROLES: bool = false, + /// Allows management and editing of webhooks + MANAGE_WEBHOOKS: bool = false, + /// Allows for editing and deleting emojis, stickers, and soundboard sounds created by all users + MANAGE_GUILD_EXPRESSIONS: bool = false, + /// Allows members to use application commands in text channels + USE_SLASH_COMMANDS: bool = false, + /// Allows for requesting to speak in stage channels. + REQUEST_TO_SPEAK: bool = false, + /// Allows for editing and deleting scheduled events created by all users + MANAGE_EVENTS: bool = false, + /// Allows for deleting and archiving threads, and viewing all private threads + MANAGE_THREADS: bool = false, + /// Allows for creating public and announcement threads + CREATE_PUBLIC_THREADS: bool = false, + /// Allows for creating private threads + CREATE_PRIVATE_THREADS: bool = false, + /// Allows the usage of custom stickers from other servers + USE_EXTERNAL_STICKERS: bool = false, + /// Allows for sending messages in threads + SEND_MESSAGES_IN_THREADS: bool = false, + /// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. + USE_EMBEDDED_ACTIVITIES: bool = false, + /// Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels + MODERATE_MEMBERS: bool = false, + /// Allows for viewing role subscription insights. + VIEW_CREATOR_MONETIZATION_ANALYTICS: bool = false, + /// Allows for using soundboard in a voice channel. + USE_SOUNDBOARD: bool = false, + /// Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user + CREATE_GUILD_EXPRESSIONS: bool = false, + /// Allows for creating scheduled events, and editing and deleting those created by the current user + CREATE_EVENTS: bool = false, + /// Allows the usage of custom soundboards sounds from other servers + USE_EXTERNAL_SOUNDS: bool = false, + /// Allows sending voice messages + SEND_VOICE_MESSAGES: bool = false, + /// Allows sending polls + SEND_POLLS: bool = false, + /// Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server. + USE_EXTERNAL_APPS: bool = false, + _pad: u15 = 0, +}; + +pub const PermissionStrings = BitwisePermissionFlags; + +/// https://discord.com/developers/docs/topics/opcodes-and-status-codes#opcodes-and-status-codes +pub const GatewayCloseEventCodes = enum(u16) { + /// A normal closure of the gateway. You may attempt to reconnect. + NormalClosure = 1000, + /// We're not sure what went wrong. Try reconnecting? + UnknownError = 4000, + /// You sent an invalid [Gateway opcode](https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes) or an invalid payload for an opcode. Don't do that! + UnknownOpcode, + /// You sent an invalid [payload](https://discord.com/developers/docs/topics/gateway#sending-payloads) to us. Don't do that! + DecodeError, + /// You sent us a payload prior to [identifying](https://discord.com/developers/docs/topics/gateway-events#identify), or this session has been invalidated. + NotAuthenticated, + /// The account token sent with your [identify payload](https://discord.com/developers/docs/topics/gateway-events#identify) is incorrect. + AuthenticationFailed, + /// You sent more than one identify payload. Don't do that! + AlreadyAuthenticated, + /// The sequence sent when [resuming](https://discord.com/developers/docs/topics/gateway-events#resume) the session was invalid. Reconnect and start a new session. + InvalidSeq = 4007, + /// Woah nelly! You're sending payloads to us too quickly. Slow it down! You will be disconnected on receiving this. + RateLimited, + /// Your session timed out. Reconnect and start a new one. + SessionTimedOut, + /// You sent us an invalid [shard when identifying](https://discord.com/developers/docs/topics/gateway#sharding). + InvalidShard, + /// The session would have handled too many guilds - you are required to [shard](https://discord.com/developers/docs/topics/gateway#sharding) your connection in order to connect. + ShardingRequired, + /// You sent an invalid version for the gateway. + InvalidApiVersion, + /// You sent an invalid intent for a [Gateway Intent](https://discord.com/developers/docs/topics/gateway#gateway-intents). You may have incorrectly calculated the bitwise value. + InvalidIntents, + /// You sent a disallowed intent for a [Gateway Intent](https://discord.com/developers/docs/topics/gateway#gateway-intents). You may have tried to specify an intent that you [have not enabled or are not approved for](https://discord.com/developers/docs/topics/gateway#privileged-intents). + DisallowedIntents, +}; + +/// https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes +pub const GatewayOpcodes = enum(u4) { + /// An event was dispatched. + Dispatch, + /// Fired periodically by the client to keep the connection alive. + Heartbeat, + /// Starts a new session during the initial handshake. + Identify, + /// Update the client's presence. + PresenceUpdate, + /// Used to join/leave or move between voice channels. + VoiceStateUpdate, + /// Resume a previous session that was disconnected. + Resume = 6, + /// You should attempt to reconnect and resume immediately. + Reconnect, + /// Request information about offline guild members in a large guild. + RequestGuildMembers, + /// The session has been invalidated. You should reconnect and identify/resume accordingly. + InvalidSession, + /// Sent immediately after connecting, contains the `heartbeat_interval` to use. + Hello, + /// Sent in response to receiving a heartbeat to acknowledge that it has been received. + HeartbeatACK, +}; + +pub const GatewayDispatchEventNames = union(enum) { + APPLICATION_COMMAND_PERMISSIONS_UPDATE, + AUTO_MODERATION_RULE_CREATE, + AUTO_MODERATION_RULE_UPDATE, + AUTO_MODERATION_RULE_DELETE, + AUTO_MODERATION_ACTION_EXECUTION, + CHANNEL_CREATE, + CHANNEL_UPDATE, + CHANNEL_DELETE, + CHANNEL_PINS_UPDATE, + THREAD_CREATE, + THREAD_UPDATE, + THREAD_DELETE, + THREAD_LIST_SYNC, + THREAD_MEMBER_UPDATE, + THREAD_MEMBERS_UPDATE, + GUILD_AUDIT_LOG_ENTRY_CREATE, + GUILD_CREATE, + GUILD_UPDATE, + GUILD_DELETE, + GUILD_BAN_ADD, + GUILD_BAN_REMOVE, + GUILD_EMOJIS_UPDATE, + GUILD_STICKERS_UPDATE, + GUILD_INTEGRATIONS_UPDATE, + GUILD_MEMBER_ADD, + GUILD_MEMBER_REMOVE, + GUILD_MEMBER_UPDATE, + GUILD_MEMBERS_CHUNK, + GUILD_ROLE_CREATE, + GUILD_ROLE_UPDATE, + GUILD_ROLE_DELETE, + GUILD_SCHEDULED_EVENT_CREATE, + GUILD_SCHEDULED_EVENT_UPDATE, + GUILD_SCHEDULED_EVENT_DELETE, + GUILD_SCHEDULED_EVENT_USER_ADD, + GUILD_SCHEDULED_EVENT_USER_REMOVE, + INTEGRATION_CREATE, + INTEGRATION_UPDATE, + INTEGRATION_DELETE, + INTERACTION_CREATE, + INVITE_CREATE, + INVITE_DELETE, + MESSAGE_CREATE, + MESSAGE_UPDATE, + MESSAGE_DELETE, + MESSAGE_DELETE_BULK, + MESSAGE_REACTION_ADD, + MESSAGE_REACTION_REMOVE, + MESSAGE_REACTION_REMOVE_ALL, + MESSAGE_REACTION_REMOVE_EMOJI, + PRESENCE_UPDATE, + STAGE_INSTANCE_CREATE, + STAGE_INSTANCE_UPDATE, + STAGE_INSTANCE_DELETE, + TYPING_START, + USER_UPDATE, + VOICE_CHANNEL_EFFECT_SEND, + VOICE_STATE_UPDATE, + VOICE_SERVER_UPDATE, + WEBHOOKS_UPDATE, + ENTITLEMENT_CREATE, + ENTITLEMENT_UPDATE, + ENTITLEMENT_DELETE, + MESSAGE_POLL_VOTE_ADD, + MESSAGE_POLL_VOTE_REMOVE, + + READY, + RESUMED, +}; + +/// https://discord.com/developers/docs/topics/gateway#list-of-intents +pub const GatewayIntents = packed struct { + pub fn toRaw(self: GatewayIntents) u32 { + return @bitCast(self); + } + + pub fn fromRaw(raw: u32) GatewayIntents { + return @bitCast(raw); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + return @bitCast(value.number.cast(u32)); + } + /// + /// - GUILD_CREATE + /// - GUILD_UPDATE + /// - GUILD_DELETE + /// - GUILD_ROLE_CREATE + /// - GUILD_ROLE_UPDATE + /// - GUILD_ROLE_DELETE + /// - CHANNEL_CREATE + /// - CHANNEL_UPDATE + /// - CHANNEL_DELETE + /// - CHANNEL_PINS_UPDATE + /// - THREAD_CREATE + /// - THREAD_UPDATE + /// - THREAD_DELETE + /// - THREAD_LIST_SYNC + /// - THREAD_MEMBER_UPDATE + /// - THREAD_MEMBERS_UPDATE + /// - STAGE_INSTANCE_CREATE + /// - STAGE_INSTANCE_UPDATE + /// - STAGE_INSTANCE_DELETE + //// + Guilds: bool = false, + /// + /// - GUILD_MEMBER_ADD + /// - GUILD_MEMBER_UPDATE + /// - GUILD_MEMBER_REMOVE + /// - THREAD_MEMBERS_UPDATE + /// + /// This is a privileged intent. + //// + GuildMembers: bool = false, + /// + /// - GUILD_AUDIT_LOG_ENTRY_CREATE + /// - GUILD_BAN_ADD + /// - GUILD_BAN_REMOVE + //// + GuildModeration: bool = false, + /// + /// - GUILD_EMOJIS_UPDATE + /// - GUILD_STICKERS_UPDATE + //// + GuildEmojisAndStickers: bool = false, + /// + /// - GUILD_INTEGRATIONS_UPDATE + /// - INTEGRATION_CREATE + /// - INTEGRATION_UPDATE + /// - INTEGRATION_DELETE + //// + GuildIntegrations: bool = false, + /// + /// - WEBHOOKS_UPDATE + //// + GuildWebhooks: bool = false, + /// + /// - INVITE_CREATE + /// - INVITE_DELETE + //// + GuildInvites: bool = false, + /// + /// - VOICE_STATE_UPDATE + /// - VOICE_CHANNEL_EFFECT_SEND + //// + GuildVoiceStates: bool = false, + /// + /// - PRESENCE_UPDATE + /// + /// This is a privileged intent. + //// + GuildPresences: bool = false, + /// + /// - MESSAGE_CREATE + /// - MESSAGE_UPDATE + /// - MESSAGE_DELETE + /// - MESSAGE_DELETE_BULK + /// + /// The messages do not contain content by default. + /// If you want to receive their content too, you need to turn on the privileged `MESSAGE_CONTENT` intent. */ + GuildMessages: bool = false, + /// + /// - MESSAGE_REACTION_ADD + /// - MESSAGE_REACTION_REMOVE + /// - MESSAGE_REACTION_REMOVE_ALL + /// - MESSAGE_REACTION_REMOVE_EMOJI + //// + GuildMessageReactions: bool = false, + /// + /// - TYPING_START + //// + GuildMessageTyping: bool = false, + /// + /// - CHANNEL_CREATE + /// - MESSAGE_CREATE + /// - MESSAGE_UPDATE + /// - MESSAGE_DELETE + /// - CHANNEL_PINS_UPDATE + //// + DirectMessages: bool = false, + /// + /// - MESSAGE_REACTION_ADD + /// - MESSAGE_REACTION_REMOVE + /// - MESSAGE_REACTION_REMOVE_ALL + /// - MESSAGE_REACTION_REMOVE_EMOJI + //// + DirectMessageReactions: bool = false, + /// + /// - TYPING_START + //// + DirectMessageTyping: bool = false, + /// + /// This intent will add all content related values to message events. + /// + /// This is a privileged intent. + //// + MessageContent: bool = false, + /// + /// - GUILD_SCHEDULED_EVENT_CREATE + /// - GUILD_SCHEDULED_EVENT_UPDATE + /// - GUILD_SCHEDULED_EVENT_DELETE + /// - GUILD_SCHEDULED_EVENT_USER_ADD this is experimental and unstable. + /// - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable. + //// + GuildScheduledEvents: bool = false, + _pad: u4 = 0, + /// + /// - AUTO_MODERATION_RULE_CREATE + /// - AUTO_MODERATION_RULE_UPDATE + /// - AUTO_MODERATION_RULE_DELETE + //// + AutoModerationConfiguration: bool = false, + /// + /// - AUTO_MODERATION_ACTION_EXECUTION + //// + AutoModerationExecution: bool = false, + _pad2: u3 = 0, + /// + /// - MESSAGE_POLL_VOTE_ADD + /// - MESSAGE_POLL_VOTE_REMOVE + //// + GuildMessagePolls: bool = false, + /// + /// - MESSAGE_POLL_VOTE_ADD + /// - MESSAGE_POLL_VOTE_REMOVE + //// + DirectMessagePolls: bool = false, + _pad3: u4 = 0, +}; + +/// https://discord.com/developers/docs/topics/gateway#list-of-intents +pub const Intents = GatewayIntents; + +/// https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionresponsetype +pub const InteractionResponseTypes = enum(u4) { + /// ACK a `Ping` + Pong = 1, + /// Respond to an interaction with a message + ChannelMessageWithSource = 4, + /// ACK an interaction and edit a response later, the user sees a loading state + DeferredChannelMessageWithSource = 5, + /// For components, ACK an interaction and edit the original message later; the user does not see a loading state + DeferredUpdateMessage = 6, + /// For components, edit the message the component was attached to + UpdateMessage = 7, + /// For Application Command Options, send an autocomplete result + ApplicationCommandAutocompleteResult = 8, + /// For Command or Component interactions, send a Modal response + Modal = 9, + /// + /// Respond to an interaction with an upgrade button, only available for apps with monetization enabled + /// + /// @deprecated You should migrate to the premium button components + PremiumRequired = 10, + /// + /// Launch the Activity associated with the app. + /// + /// @remarks + /// Only available for apps with Activities enabled + LaunchActivity = 12, +}; + +pub const SortOrderTypes = enum { + /// Sort forum posts by activity + LatestActivity, + /// Sort forum posts by creation time (from most recent to oldest) + CreationDate, +}; + +pub const ForumLayout = enum(u4) { + /// No default has been set for forum channel. + NotSet = 0, + /// Display posts as a list. + ListView = 1, + /// Display posts as a collection of tiles. + GalleryView = 2, +}; + +/// https://discord.com/developers/docs/reference#image-formatting +/// json is only for stickers +pub const ImageFormat = union(enum) { + jpg, + jpeg, + png, + webp, + gif, + json, +}; + +/// https://discord.com/developers/docs/reference#image-formatting +pub const ImageSize = isize; + +pub const Locales = enum { + id, + da, + de, + @"en-GB", + @"en-US", + @"es-ES", + @"es-419", + fr, + hr, + it, + lt, + hu, + nl, + no, + pl, + @"pt-BR", + ro, + fi, + @"sv-SE", + vi, + tr, + cs, + el, + bg, + ru, + uk, + hi, + th, + @"zh-CN", + ja, + @"zh-TW", + ko, +}; + +/// https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes +pub const OAuth2Scope = enum { + /// + /// Allows your app to fetch data from a user's "Now Playing/Recently Played" list + /// + /// @remarks + /// This scope is not currently available for apps + /// + @"activities.read", + /// + /// Allows your app to update a user's activity + /// + /// @remarks + /// This scope not currently available for apps. + /// + @"activities.write", + /// Allows your app to read build data for a user's applications + @"applications.builds.read", + /// + /// Allows your app to upload/update builds for a user's applications + /// + /// @remarks + ///This scope requires approval to be used + /// + @"applications.builds.upload", + /// Allows your app to add commands to a guild - included by default with the `bot` scope + @"applications.commands", + /// + /// Allows your app to update its Application Commands via this bearer token + /// + /// @remarks + /// This scope can only be used when using a [Client Credential Grant](https://discord.com/developers/docs/topics/oauth2#client-credentials-grant) + /// + @"applications.commands.update", + /// Allows your app to update permissions for its commands in a guild a user has permissions to + @"applications.commands.permissions.update", + /// Allows your app to read entitlements for a user's applications + @"applications.entitlements", + /// Allows your app to read and update store data (SKUs, store listings, achievements, etc.) for a user's applications + @"applications.store.update", + /// For oauth2 bots, this puts the bot in the user's selected guild by default + bot, + /// Allows requests to [/users/@me/connections](https://discord.com/developers/docs/resources/user#get-user-connections) + connections, + /// + /// Allows your app to see information about the user's DMs and group DMs + /// + /// @remarks + ///This scope requires approval to be used + /// + @"dm_channels.read", + /// Adds the `email` filed to [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) + email, + /// Allows your app to join users to a group dm + @"gdm.join", + /// Allows requests to [/users/@me/guilds](https://discord.com/developers/docs/resources/user#get-current-user-guilds) + guilds, + /// Allows requests to [/guilds/{guild.id};/members/{user.id};](https://discord.com/developers/docs/resources/guild#add-guild-member) + @"guilds.join", + /// Allows requests to [/users/@me/guilds/{guild.id};/member](https://discord.com/developers/docs/resources/user#get-current-user-guild-member) + @"guilds.members.read", + /// + /// Allows requests to [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) + /// + /// @remarks + /// The return object from [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) + /// does NOT contain the `email` field unless the scope `email` is also used + /// + identify, + /// + /// For local rpc server api access, this allows you to read messages from all client channels + /// (otherwise restricted to channels/guilds your app creates) + /// + @"messages.read", + /// + /// Allows your app to know a user's friends and implicit relationships + /// + /// @remarks + ///This scope requires approval to be used + /// + @"relationships.read", + /// Allows your app to update a user's connection and metadata for the app + @"role_connections.write", + /// + ///For local rpc server access, this allows you to control a user's local client + /// + /// @remarks + ///This scope requires approval to be used + /// + rpc, + /// + /// For local rpc server access, this allows you to update a user's activity + /// + /// @remarks + ///This scope requires approval to be used + /// + @"rpc.activities.write", + /// + /// For local rpc server api access, this allows you to receive notifications pushed out to the user + /// + /// @remarks + ///This scope requires approval to be used + /// + @"rpc.notifications.read", + /// + /// For local rpc server access, this allows you to read a user's voice settings and listen for voice events + /// + /// @remarks + ///This scope requires approval to be used + /// + @"rpc.voice.read", + /// + /// For local rpc server access, this allows you to update a user's voice settings + /// + /// @remarks + ///This scope requires approval to be used + /// + @"rpc.voice.write", + /// + /// Allows your app to connect to voice on user's behalf and see all the voice members + /// + /// @remarks + ///This scope requires approval to be used + /// + voice, + /// Generate a webhook that is returned in the oauth token response for authorization code grants + @"webhook.incoming", +}; + +/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types +pub const InteractionContextType = enum { + /// Interaction can be used within servers + Guild, + /// Interaction can be used within DMs with the app's bot user + BotDm, + /// Interaction can be used within Group DMs and DMs other than the app's bot user + PrivateChannel, +}; diff --git a/src/structures/snowflake.zig b/src/structures/snowflake.zig new file mode 100644 index 0000000..9b5f97b --- /dev/null +++ b/src/structures/snowflake.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const zjson = @import("json"); + +pub const Snowflake = enum(u64) { + _, + + pub fn into(self: Snowflake) u64 { + return @intFromEnum(self); + } + + pub fn fromMaybe(raw: ?[]const u8) std.fmt.ParseIntError!?Snowflake { + if (raw) |id| return @enumFromInt(try std.fmt.parseInt(u64, id, 10)); + return null; + } + + pub fn fromRaw(raw: []const u8) std.fmt.ParseIntError!Snowflake { + return @enumFromInt(try std.fmt.parseInt(u64, raw, 10)); + } + + pub fn fromMany(allocator: std.mem.Allocator, many: [][]const u8) ![]Snowflake { + var array = std.ArrayList(Snowflake).init(allocator); + + for (many) |id| + try array.append(try Snowflake.fromRaw(id)); + + return array.toOwnedSlice(); + } + + pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() { + if (value.is(.string)) + return Snowflake.fromRaw(value.string) catch unreachable; + unreachable; + } +}; diff --git a/src/structures/team.zig b/src/structures/team.zig new file mode 100644 index 0000000..9119c91 --- /dev/null +++ b/src/structures/team.zig @@ -0,0 +1,38 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const TeamMembershipStates = @import("shared.zig").TeamMembershipStates; + +/// https://discord.com/developers/docs/topics/teams#data-models-team-object +pub const Team = struct { + /// Hash of the image of the team's icon + icon: ?[]const u8, + /// Unique ID of the team + id: Snowflake, + /// Members of the team + members: []TeamMember, + /// User ID of the current team owner + owner_user_id: Snowflake, + /// Name of the team + name: []const u8, +}; + +/// https://discord.com/developers/docs/topics/teams#data-models-team-members-object +pub const TeamMember = struct { + /// The user's membership state on the team + membership_state: TeamMembershipStates, + /// The id of the parent team of which they are a member + team_id: Snowflake, + /// The avatar, discriminator, id, username, and global_name of the user + /// TODO: needs fixing + user: struct { + /// Unique ID of the user + id: Snowflake, + /// The user's username, not unique across the platform + username: []const u8, + /// The user's display name, if it is set. For bots, this is the application name + global_name: []const u8, + /// The user's discord-tag + discriminator: []const u8, + /// The user's avatar hash + avatar: []const u8, + }, +}; diff --git a/src/structures/thread.zig b/src/structures/thread.zig new file mode 100644 index 0000000..4cc1acd --- /dev/null +++ b/src/structures/thread.zig @@ -0,0 +1,55 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const Channel = @import("channel.zig").Channel; + +pub const ThreadMetadata = struct { + /// Whether the thread is archived + archived: bool, + /// Duration in minutes to automatically archive the thread after recent activity + auto_archive_duration: isize, + /// When a thread is locked, only users with `MANAGE_THREADS` can unarchive it + locked: bool, + /// whether non-moderators can add other non-moderators to a thread; only available on private threads + invitable: ?bool, + /// Timestamp when the thread's archive status was last changed, used for calculating recent activity + archive_timestamp: []const u8, + /// Timestamp when the thread was created; only populated for threads created after 2022-01-09 + create_timestamp: ?[]const u8, +}; + +pub const ThreadMember = struct { + /// Any user-thread settings, currently only used for notifications + flags: isize, + /// The id of the thread + id: Snowflake, + /// The id of the user + user_id: Snowflake, + /// The time the current user last joined the thread + join_timestamp: []const u8, +}; + +pub const ListActiveThreads = struct { + /// The active threads + threads: []Channel, + /// A thread member object for each returned thread the current user has joined + members: []ThreadMember, +}; + +pub const ListArchivedThreads = struct { + /// The active threads + threads: []Channel, + /// A thread member object for each returned thread the current user has joined + members: []ThreadMember, + /// Whether there are potentially additional threads that could be returned on a subsequent call + has_more: bool, +}; + +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, + /// 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 + members: []ThreadMember, +}; diff --git a/src/structures/types.zig b/src/structures/types.zig new file mode 100644 index 0000000..7cda7e7 --- /dev/null +++ b/src/structures/types.zig @@ -0,0 +1,46 @@ +const GatewayDispatchEventNames = @import("shared.zig").GatewayDispatchEventNames; + +pub usingnamespace @import("shared.zig"); +pub usingnamespace @import("partial.zig"); +pub usingnamespace @import("snowflake.zig"); +pub usingnamespace @import("events.zig"); + +pub usingnamespace @import("application.zig"); +pub usingnamespace @import("attachment.zig"); +pub usingnamespace @import("auditlog.zig"); +pub usingnamespace @import("automod.zig"); +pub usingnamespace @import("channel.zig"); +pub usingnamespace @import("command.zig"); +pub usingnamespace @import("embed.zig"); +pub usingnamespace @import("emoji.zig"); +pub usingnamespace @import("gateway.zig"); +pub usingnamespace @import("guild.zig"); +pub usingnamespace @import("integration.zig"); +pub usingnamespace @import("integration.zig"); +pub usingnamespace @import("invite.zig"); +pub usingnamespace @import("member.zig"); +pub usingnamespace @import("message.zig"); +pub usingnamespace @import("monetization.zig"); +pub usingnamespace @import("oauth.zig"); +pub usingnamespace @import("poll.zig"); +pub usingnamespace @import("role.zig"); +pub usingnamespace @import("scheduled_event.zig"); +pub usingnamespace @import("team.zig"); +pub usingnamespace @import("thread.zig"); +pub usingnamespace @import("user.zig"); +pub usingnamespace @import("webhook.zig"); + +/// https://discord.com/developers/docs/topics/gateway#payloads-gateway-payload-structure +pub fn GatewayPayload(comptime T: type) type { + return struct { + /// opcode for the payload + op: isize, + /// Event data + d: ?T, + /// Sequence isize, used for resuming sessions and heartbeats + s: ?isize, + /// The event name for this payload + t: ?[]const u8, + // t: ?GatewayDispatchEventNames, + }; +} diff --git a/src/structures/user.zig b/src/structures/user.zig new file mode 100644 index 0000000..1eb447a --- /dev/null +++ b/src/structures/user.zig @@ -0,0 +1,144 @@ +const PremiumTypes = @import("shared.zig").PremiumTypes; +const Snowflake = @import("snowflake.zig").Snowflake; +const Application = @import("application.zig").Application; +const Record = @import("json").Record; +const OAuth2Scope = @import("shared.zig").OAuth2Scope; +const Integration = @import("integration.zig").Integration; +const Partial = @import("partial.zig").Partial; + +/// https://discord.com/developers/docs/resources/user#user-object +pub const User = struct { + /// The user's username, not unique across the platform + username: []const u8, + /// The user's display name, if it is set. For bots, this is the application name + global_name: ?[]const u8, + /// The user's chosen language option + locale: ?[]const u8, + /// The flags on a user's account + flags: ?isize, + /// The type of Nitro subscription on a user's account + premium_type: ?PremiumTypes, + /// The public flags on a user's account + public_flags: ?isize, + /// the user's banner color encoded as an integer representation of hexadecimal color code + accent_color: ?isize, + /// The user's id + id: Snowflake, + /// The user's discord-tag + discriminator: []const u8, + /// The user's avatar hash + avatar: ?[]const u8, + /// Whether the user belongs to an OAuth2 application + bot: ?bool, + ///Whether the user is an Official System user (part of the urgent message system) + system: ?bool, + /// Whether the user has two factor enabled on their account + mfa_enabled: ?bool, + /// Whether the email on this account has been verified + verified: ?bool, + /// The user's email + email: ?[]const u8, + /// the user's banner, or null if unset + banner: ?[]const u8, + /// data for the user's avatar decoration + avatar_decoration_data: ?AvatarDecorationData, + clan: ?[]const u8, +}; + +/// https://discord.com/developers/docs/resources/user#avatar-decoration-data-object +pub const AvatarDecorationData = struct { + /// the avatar decoration hash + asset: []const u8, + /// id of the avatar decoration's SKU + sku_id: Snowflake, +}; + +/// TODO: implement +pub const TokenExchange = null; + +pub const TokenRevocation = struct { + /// The access token to revoke + token: []const u8, + /// Optional, the type of token you are using for the revocation + token_type_hint: ?"access_token' | 'refresh_token", +}; + +/// https://discord.com/developers/docs/topics/oauth2#get-current-authorization-information-response-structure +pub const CurrentAuthorization = struct { + application: Application, + /// the scopes the user has authorized the application for + scopes: []OAuth2Scope, + /// when the access token expires + expires: []const u8, + /// the user who has authorized, if the user has authorized with the `identify` scope + user: ?User, +}; + +/// https://discord.com/developers/docs/resources/user#connection-object-connection-structure +pub const Connection = struct { + /// id of the connection account + id: Snowflake, + /// the username of the connection account + name: []const u8, + /// the service of this connection + type: ConnectionServiceType, + /// whether the connection is revoked + revoked: ?bool, + /// an array of partial server integrations + integrations: []?Partial(Integration), + /// whether the connection is verified + verified: bool, + /// whether friend sync is enabled for this connection + friend_sync: bool, + /// whether activities related to this connection will be shown in presence updates + show_activity: bool, + /// whether this connection has a corresponding third party OAuth2 token + two_way_link: bool, + /// visibility of this connection + visibility: ConnectionVisibility, +}; + +/// https://discord.com/developers/docs/resources/user#connection-object-services +pub const ConnectionServiceType = enum { + @"amazon-music", + battlenet, + @"Bungie.net", + domain, + ebay, + epicgames, + facebook, + github, + instagram, + leagueoflegends, + paypal, + playstation, + reddit, + riotgames, + roblox, + spotify, + skype, + steam, + tiktok, + twitch, + twitter, + xbox, + youtube, +}; + +//https://discord.com/developers/docs/resources/user#connection-object-visibility-types +pub const ConnectionVisibility = enum(u4) { + /// invisible to everyone except the user themselves + None = 0, + /// visible to everyone + Everyone = 1, +}; + +/// https://discord.com/developers/docs/resources/user#application-role-connection-object-application-role-connection-structure +pub const ApplicationRoleConnection = struct { + /// the vanity name of the platform a bot has connected (max 50 characters) + platform_name: ?[]const u8, + /// the username on the platform a bot has connected (max 100 characters) + platform_username: ?[]const u8, + /// object mapping application role connection metadata keys to their stringified value (max 100 characters) for the user on the platform a bot has connected + metadata: []Record([]const u8), +}; diff --git a/src/structures/webhook.zig b/src/structures/webhook.zig new file mode 100644 index 0000000..451400b --- /dev/null +++ b/src/structures/webhook.zig @@ -0,0 +1,74 @@ +const Snowflake = @import("snowflake.zig").Snowflake; +const WebhookTypes = @import("shared.zig").WebhookTypes; +const User = @import("user.zig").User; +const Guild = @import("guild.zig").Guild; +const Channel = @import("channel.zig").Channel; +const Partial = @import("partial.zig").Partial; + +/// https://discord.com/developers/docs/topics/gateway#webhooks-update-webhook-update-event-fields +pub const WebhookUpdate = struct { + /// id of the guild + guild_id: Snowflake, + /// id of the channel + channel_id: Snowflake, +}; + +/// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure +/// TODO: implement +pub const Webhook = null; + +pub const IncomingWebhook = struct { + /// The type of the webhook + type: WebhookTypes, + /// The secure token of the webhook (returned for Incoming Webhooks) + token: ?[]const u8, + /// The url used for executing the webhook (returned by the webhooks OAuth2 flow) + url: ?[]const u8, + + /// The id of the webhook + id: Snowflake, + /// The guild id this webhook is for + guild_id: ?Snowflake, + /// The channel id this webhook is for + channel_id: Snowflake, + /// The user this webhook was created by (not returned when getting a webhook with its token) + user: ?User, + /// The default name of the webhook + name: ?[]const u8, + /// The default user avatar hash of the webhook + avatar: ?[]const u8, + /// The bot/OAuth2 application that created this webhook + application_id: ?Snowflake, + /// The guild of the channel that this webhook is following (returned for Channel Follower Webhooks) + source_guild: ?Partial(Guild), + /// The channel that this webhook is following (returned for Channel Follower Webhooks) + source_channel: ?Partial(Channel), +}; + +pub const ApplicationWebhook = struct { + /// The type of the webhook + type: WebhookTypes.Application, + /// The secure token of the webhook (returned for Incoming Webhooks) + token: ?[]const u8, + /// The url used for executing the webhook (returned by the webhooks OAuth2 flow) + url: ?[]const u8, + + /// The id of the webhook + id: Snowflake, + /// The guild id this webhook is for + guild_id: ?Snowflake, + /// The channel id this webhook is for + channel_id: ?Snowflake, + /// The user this webhook was created by (not returned when getting a webhook with its token) + user: ?User, + /// The default name of the webhook + name: ?[]const u8, + /// The default user avatar hash of the webhook + avatar: ?[]const u8, + /// The bot/OAuth2 application that created this webhook + application_id: ?Snowflake, + /// The guild of the channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides + source_guild: ?Partial(Guild), + /// The channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides + source_channel: ?Partial(Channel), +}; diff --git a/src/test.zig b/src/test.zig index 4076668..cd75a87 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1,5 +1,4 @@ const Discord = @import("discord.zig"); - const Shard = Discord.Shard; const Internal = Discord.Internal; const FetchReq = Discord.FetchReq; @@ -22,7 +21,7 @@ fn message_create(session: *Shard, message: Discord.Message) fmt.AllocPrintError const payload: Discord.Partial(Discord.CreateMessage) = .{ .content = "Hi, I'm hang man, your personal assistant" }; const json = std.json.stringifyAlloc(session.allocator, payload, .{}) catch unreachable; defer session.allocator.free(json); - const path = try fmt.allocPrint(session.allocator, "/channels/{d}/messages", .{message.channel_id.value()}); + const path = try fmt.allocPrint(session.allocator, "/channels/{d}/messages", .{message.channel_id.into()}); _ = req.makeRequest(.POST, path, json) catch unreachable; }; @@ -33,7 +32,7 @@ pub fn main() !void { var handler = Discord.init(tsa.allocator()); try handler.start(.{ - .token = std.posix.getenv("TOKEN") orelse unreachable, + .token = std.posix.getenv("DISCORD_TOKEN") orelse unreachable, .intents = Intents.fromRaw(37379), .run = .{ .message_create = &message_create, diff --git a/src/types.zig b/src/types.zig deleted file mode 100644 index 9897b17..0000000 --- a/src/types.zig +++ /dev/null @@ -1,5154 +0,0 @@ -const std = @import("std"); -const AutoArrayHashMap = std.AutoArrayHashMap; -const Snowflake = @import("shared.zig").Snowflake; - -pub fn Omit(comptime T: type, comptime field_names: anytype) type { - const info = @typeInfo(T); - switch (info) { - .@"struct" => |s| { - comptime var fields: []const std.builtin.Type.StructField = &[_]std.builtin.Type.StructField{}; - outer: inline for (s.fields) |field| { - if (field.is_comptime) { - @compileError("Cannot make Omit of " ++ @typeName(T) ++ ", it has a comptime field " ++ field.name); - } - - inline for (field_names) |lookup| { - if (std.mem.eql(u8, field.name, lookup)) { - continue :outer; - } - } - - fields = fields ++ .{field}; - } - const ti: std.builtin.Type = .{ .@"struct" = .{ - .backing_integer = s.backing_integer, - .decls = &[_]std.builtin.Type.Declaration{}, - .fields = fields, - .is_tuple = s.is_tuple, - .layout = s.layout, - } }; - return @Type(ti); - }, - else => @compileError("Cannot make Omit of " ++ @typeName(T) ++ - ", the type must be a struct"), - } -} - -pub fn Partial(comptime T: type) type { - const info = @typeInfo(T); - switch (info) { - .@"struct" => |s| { - comptime var fields: []const std.builtin.Type.StructField = &[_]std.builtin.Type.StructField{}; - inline for (s.fields) |field| { - if (field.is_comptime) { - @compileError("Cannot make Partial of " ++ @typeName(T) ++ ", it has a comptime field " ++ field.name); - } - const optional_type = switch (@typeInfo(field.type)) { - .optional => field.type, - else => ?field.type, - }; - const default_value: optional_type = null; - const aligned_ptr: *align(field.alignment) const anyopaque = @alignCast(@ptrCast(&default_value)); - const optional_field: [1]std.builtin.Type.StructField = [_]std.builtin.Type.StructField{.{ - .alignment = field.alignment, - .default_value = aligned_ptr, - .is_comptime = false, - .name = field.name, - .type = optional_type, - }}; - fields = fields ++ optional_field; - } - const partial_type_info: std.builtin.Type = .{ .@"struct" = .{ - .backing_integer = s.backing_integer, - .decls = &[_]std.builtin.Type.Declaration{}, - .fields = fields, - .is_tuple = s.is_tuple, - .layout = s.layout, - } }; - return @Type(partial_type_info); - }, - else => @compileError("Cannot make Partial of " ++ @typeName(T) ++ - ", the type must be a struct"), - } - unreachable; -} - -test "partial" { - const PartialObject = Partial(struct { - foo: []const u8, - bar: ?[]const u8, - baz: u32, - }); - const part = PartialObject{}; - try std.testing.expectEqual(@as(?[]const u8, null), part.foo); - try std.testing.expectEqual(@as(?[]const u8, null), part.bar); - try std.testing.expectEqual(@as(?u32, null), part.baz); -} - -pub const PresenceStatus = enum { - online, - dnd, - idle, - offline, -}; - -/// https://discord.com/developers/docs/resources/user#user-object-premium-types -pub const PremiumTypes = enum { - None, - NitroClassic, - Nitro, - NitroBasic, -}; - -/// https://discord.com/developers/docs/resources/user#user-object-user-flags -pub const UserFlags = packed struct { - pub fn toRaw(self: UserFlags) u26 { - return @as(u26, @bitCast(self)); - } - - pub fn fromRaw(raw: u26) UserFlags { - return @as(UserFlags, @bitCast(raw)); - } - - DiscordEmployee: bool = false, - PartneredServerOwner: bool = false, - HypeSquadEventsMember: bool = false, - BugHunterLevel1: bool = false, - _pad: u3 = 0, - HouseBravery: bool = false, - HouseBrilliance: bool = false, - HouseBalance: bool = false, - EarlySupporter: bool = false, - TeamUser: bool = false, - _pad2: u4 = 0, - BugHunterLevel2: bool = false, - _pad3: u1 = 0, - VerifiedBot: bool = false, - EarlyVerifiedBotDeveloper: bool = false, - DiscordCertifiedModerator: bool = false, - BotHttpInteractions: bool = false, - _pad4: u3 = 0, - ActiveDeveloper: bool = false, -}; - -pub const MemberFlags = packed struct { - pub fn toRaw(self: MemberFlags) u10 { - return @as(u10, @bitCast(self)); - } - - pub fn fromRaw(raw: u10) MemberFlags { - return @as(MemberFlags, @bitCast(raw)); - } - - /// - /// Member has left and rejoined the guild - /// - /// @remarks - /// This value is not editable - ////Message - DidRejoin: bool = false, - /// - /// Member has completed onboarding - /// - /// @remarks - /// This value is not editable - //// - CompletedOnboarding: bool = false, - /// Member is exempt from guild verification requirements - BypassesVerification: bool = false, - /// - /// Member has started onboarding - /// - /// @remarks - /// This value is not editable - //// - StartedOnboarding: bool = false, - /// - /// Member is a guest and can only access the voice channel they were invited to - /// - /// @remarks - /// This value is not editable - //// - IsGuest: bool = false, - /// - /// Member has started Server Guide new member actions - /// - /// @remarks - /// This value is not editable - //// - StartedHomeActions: bool = false, - /// - /// Member has completed Server Guide new member actions - /// - /// @remarks - /// This value is not editable - //// - CompletedHomeActions: bool = false, - /// - /// Member's username, display name, or nickname is blocked by AutoMod - /// - /// @remarks - /// This value is not editable - //// - AutomodQuarantinedUsername: bool = false, - _pad: u1 = 0, - /// - /// Member has dismissed the DM settings upsell - /// - /// @remarks - /// This value is not editable - //// - DmSettingsUpsellAcknowledged: bool = false, -}; - -/// https://discord.com/developers/docs/resources/channel#channels-resource -pub const ChannelFlags = packed struct { - pub fn toRaw(self: ChannelFlags) u18 { - return @as(u18, @bitCast(self)); - } - - pub fn fromRaw(raw: u18) ChannelFlags { - return @as(ChannelFlags, @bitCast(raw)); - } - - None: bool = false, - /// this thread is pinned to the top of its parent `GUILD_FORUM` channel - Pinned: bool = false, - _pad: u3 = 0, - /// Whether a tag is required to be specified when creating a thread in a `GUILD_FORUM` or a GUILD_MEDIA channel. Tags are specified in the `applied_tags` field. - RequireTag: bool = false, - _pad1: u11 = 0, - /// When set hides the embedded media download options. Available only for media channels. - HideMediaDownloadOptions: bool = false, -}; - -/// https://discord.com/developers/docs/topics/permissions#role-object-role-flags -pub const RoleFlags = packed struct { - pub fn toRaw(self: RoleFlags) u2 { - return @as(u2, @bitCast(self)); - } - - pub fn fromRaw(raw: u2) RoleFlags { - return @as(RoleFlags, @bitCast(raw)); - } - - None: bool = false, - /// Role can be selected by members in an onboarding prompt - InPrompt: bool = false, -}; - -pub const AttachmentFlags = packed struct { - pub fn toRaw(self: AttachmentFlags) u3 { - return @as(u3, @bitCast(self)); - } - - pub fn fromRaw(raw: u3) AttachmentFlags { - return @as(AttachmentFlags, @bitCast(raw)); - } - - None: bool = false, - _pad: u1 = 0, - /// This attachment has been edited using the remix feature on mobile - IsRemix: bool = false, -}; - -/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-flags -pub const SkuFlags = packed struct { - pub fn toRaw(self: SkuFlags) u10 { - return @as(u10, @bitCast(self)); - } - - pub fn fromRaw(raw: u10) SkuFlags { - return @as(SkuFlags, @bitCast(raw)); - } - - _pad: u2 = 0, - /// SKU is available for purchase - Available: bool = false, - _pad1: u5 = 0, - /// Recurring SKU that can be purchased by a user and applied to a single server. Grants access to every user in that server. - GuildSubscription: bool = false, - /// Recurring SKU purchased by a user for themselves. Grants access to the purchasing user in every server. - UserSubscription: bool = false, -}; - -/// https://discord.com/developers/docs/resources/channel#message-object-message-flags -pub const MessageFlags = packed struct { - pub fn toRaw(self: MessageFlags) u15 { - return @as(u15, @bitCast(self)); - } - - pub fn fromRaw(raw: u15) MessageFlags { - return @as(MessageFlags, @bitCast(raw)); - } - - /// This message has been published to subscribed channels (via Channel Following) - Crossposted: bool = false, - /// This message originated from a message in another channel (via Channel Following) - IsCrosspost: bool = false, - /// Do not include any embeds when serializing this message - SuppressEmbeds: bool = false, - /// The source message for this crosspost has been deleted (via Channel Following) - SourceMessageDeleted: bool = false, - /// This message came from the urgent message system - Urgent: bool = false, - /// This message has an associated thread, with the same id as the message - HasThread: bool = false, - /// This message is only visible to the user who invoked the Interaction - Ephemeral: bool = false, - /// This message is an Interaction Response and the bot is "thinking" - Loading: bool = false, - /// This message failed to mention some roles and add their members to the thread - FailedToMentionSomeRolesInThread: bool = false, - _pad: u4 = 0, - /// This message will not trigger push and desktop notifications - SuppressNotifications: bool = false, - /// This message is a voice message - IsVoiceMessage: bool = false, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-flags -pub const ActivityFlags = packed struct { - Instance: bool = false, - Join: bool = false, - Spectate: bool = false, - JoinRequest: bool = false, - Sync: bool = false, - Play: bool = false, - PartyPrivacyFriends: bool = false, - PartyPrivacyVoiceChannel: bool = false, - Embedded: bool = false, -}; - -/// https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors -pub const IntegrationExpireBehaviors = enum(u4) { - RemoveRole = 0, - Kick = 1, -}; - -/// https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum -pub const TeamMembershipStates = enum(u4) { - Invited = 1, - Accepted = 2, -}; - -/// https://discord.com/developers/docs/topics/oauth2#application-application-flags -pub const ApplicationFlags = packed struct { - pub fn toRaw(self: ApplicationFlags) u32 { - return @as(u32, @bitCast(self)); - } - - pub fn fromRaw(raw: u32) ApplicationFlags { - return @as(ApplicationFlags, @bitCast(raw)); - } - - _pad: u5 = 0, - /// Indicates if an app uses the Auto Moderation API. - ApplicationAutoModerationRuleCreateBadge: bool = false, - _pad1: u6 = 0, - /// Intent required for bots in **100 or more servers* /// to receive 'presence_update' events - GatewayPresence: bool = false, - /// Intent required for bots in under 100 servers to receive 'presence_update' events - GatewayPresenceLimited: bool = false, - /// Intent required for bots in **100 or more servers* /// to receive member-related events like 'guild_member_add'. - GatewayGuildMembers: bool = false, - /// Intent required for bots in under 100 servers to receive member-related events like 'guild_member_add'. - GatewayGuildMembersLimited: bool = false, - /// Indicates unusual growth of an app that prevents verification - VerificationPendingGuildLimit: bool = false, - /// Indicates if an app is embedded within the Discord client (currently unavailable publicly) - Embedded: bool = false, - /// Intent required for bots in **100 or more servers* /// to receive [message content](https://support-dev.discord.com/hc/en-us/articles/4404772028055) - GatewayMessageContent: bool = false, - /// Intent required for bots in under 100 servers to receive [message content](https://support-dev.discord.com/hc/en-us/articles/4404772028055), found in Bot Settings - GatewayMessageContentLimited: bool = false, - _pad2: u4 = 0, - /// Indicates if an app has registered global application commands - ApplicationCommandBadge: bool = false, - _pad3: u7, -}; - -/// https://discord.com/developers/docs/interactions/message-components#component-types -pub const MessageComponentTypes = enum(u4) { - /// A container for other components - ActionRow = 1, - /// A button object - Button, - /// A select menu for picking from choices - SelectMenu, - /// A text input object - InputText, - /// Select menu for users - SelectMenuUsers, - /// Select menu for roles - SelectMenuRoles, - /// Select menu for users and roles - SelectMenuUsersAndRoles, - /// Select menu for channels - SelectMenuChannels, -}; - -pub const TextStyles = enum(u4) { - /// Intended for short single-line text - Short = 1, - /// Intended for much longer inputs - Paragraph = 2, -}; - -/// https://discord.com/developers/docs/interactions/message-components#buttons-button-styles -pub const ButtonStyles = enum(u4) { - /// A blurple button - Primary = 1, - /// A grey button - Secondary, - /// A green button - Success, - /// A red button - Danger, - /// A button that navigates to a URL - Link, - /// A blurple button to show a Premium item in the shop - Premium, -}; - -/// https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types -pub const AllowedMentionsTypes = enum { - /// Controls role mentions - roles, - /// Controls user mentions - users, - /// Controls \@everyone and \@here mentions - everyone, -}; - -/// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types -pub const WebhookTypes = enum(u4) { - /// Incoming Webhooks can post messages to channels with a generated token - Incoming = 1, - /// Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels - ChannelFollower, - /// Application webhooks are webhooks used with Interactions - Application, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-types -pub const EmbedTypes = union(enum) { - rich, - image, - video, - gifv, - article, - link, - poll_res, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level -pub const DefaultMessageNotificationLevels = enum { - /// Members will receive notifications for all messages by default - AllMessages, - /// Members will receive notifications only for messages that \@mention them by default - OnlyMentions, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level -pub const ExplicitContentFilterLevels = enum { - /// Media content will not be scanned - Disabled, - /// Media content sent by members without roles will be scanned - MembersWithoutRoles, - /// Media content sent by all members will be scanned - AllMembers, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-verification-level -pub const VerificationLevels = enum { - /// Unrestricted - None, - /// Must have verified email on account - Low, - /// Must be registered on Discord for longer than 5 minutes - Medium, - /// Must be a member of the server for longer than 10 minutes - High, - /// Must have a verified phone number - VeryHigh, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-guild-features -pub const GuildFeatures = enum { - /// Guild has access to set an invite splash background - INVITE_SPLASH, - /// Guild has access to set a vanity URL - VANITY_URL, - /// Guild is verified - VERIFIED, - /// Guild is partnered - PARTNERED, - /// Guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates - COMMUNITY, - /// Guild has enabled monetization. - CREATOR_MONETIZABLE_PROVISIONAL, - /// Guild has enabled the role subscription promo page. - CREATOR_STORE_PAGE, - /// Guild has been set as a support server on the App Directory - DEVELOPER_SUPPORT_SERVER, - /// Guild has access to create news channels - NEWS, - /// Guild is able to be discovered in the directory - DISCOVERABLE, - /// Guild is able to be featured in the directory - FEATURABLE, - /// Guild has access to set an animated guild icon - ANIMATED_ICON, - /// Guild has access to set a guild banner image - BANNER, - /// Guild has enabled the welcome screen - WELCOME_SCREEN_ENABLED, - /// Guild has enabled [Membership Screening](https://discord.com/developers/docs/resources/guild#membership-screening-object) - MEMBER_VERIFICATION_GATE_ENABLED, - /// Guild can be previewed before joining via Membership Screening or the directory - PREVIEW_ENABLED, - /// Guild has enabled ticketed events - TICKETED_EVENTS_ENABLED, - /// Guild has increased custom sticker slots - MORE_STICKERS, - /// Guild is able to set role icons - ROLE_ICONS, - /// Guild has role subscriptions that can be purchased. - ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE, - /// Guild has enabled role subscriptions. - ROLE_SUBSCRIPTIONS_ENABLED, - /// Guild has set up auto moderation rules - AUTO_MODERATION, - /// Guild has paused invites, preventing new users from joining - INVITES_DISABLED, - /// Guild has access to set an animated guild banner image - ANIMATED_BANNER, - /// Guild has disabled alerts for join raids in the configured safety alerts channel - RAID_ALERTS_DISABLED, - /// Guild is using the old permissions configuration behavior - APPLICATION_COMMAND_PERMISSIONS_V2, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-mfa-level -pub const MfaLevels = enum { - /// Guild has no MFA/2FA requirement for moderation actions - None, - /// Guild has a 2FA requirement for moderation actions - Elevated, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags -pub const SystemChannelFlags = packed struct { - pub fn toRaw(self: SystemChannelFlags) u4 { - return @as(u4, @bitCast(self)); - } - - pub fn fromRaw(raw: u4) SystemChannelFlags { - return @as(SystemChannelFlags, @bitCast(raw)); - } - - /// Suppress member join notifications - SuppressJoinNotifications: bool = false, - /// Suppress server boost notifications - SuppressPremiumSubscriptions: bool = false, - /// Suppress server setup tips - SuppressGuildReminderNotifications: bool = false, - /// Hide member join sticker reply buttons - SuppressJoinNotificationReplies: bool = false, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-premium-tier -pub const PremiumTiers = enum { - /// Guild has not unlocked any Server Boost perks - None, - /// Guild has unlocked Server Boost level 1 perks - Tier1, - /// Guild has unlocked Server Boost level 2 perks - Tier2, - /// Guild has unlocked Server Boost level 3 perks - Tier3, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object-guild-nsfw-level -pub const GuildNsfwLevel = enum { - Default, - Explicit, - Safe, - AgeRestricted, -}; - -/// https://discord.com/developers/docs/resources/channel#channel-object-channel-types -pub const ChannelTypes = packed struct { - pub fn toRaw(self: ChannelTypes) u17 { - return @as(u17, @bitCast(self)); - } - - pub fn fromRaw(raw: u17) ChannelTypes { - return @as(ChannelTypes, @bitCast(raw)); - } - - /// A text channel within a server - GuildText: bool = false, - /// A direct message between users - DM: bool = false, - /// A voice channel within a server - GuildVoice: bool = false, - /// A direct message between multiple users - GroupDm: bool = false, - /// An organizational category that contains up to 50 channels - GuildCategory: bool = false, - /// A channel that users can follow and crosspost into their own server - GuildAnnouncement: bool = false, - _pad: u4 = 0, - /// A temporary sub-channel within a GUILD_ANNOUNCEMENT channel - AnnouncementThread: bool = false, - /// A temporary sub-channel within a GUILD_TEXT or GUILD_FORUM channel - PublicThread: bool = false, - /// A temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission - PrivateThread: bool = false, - /// A voice channel for hosting events with an audience - GuildStageVoice: bool = false, - /// A channel in a hub containing the listed servers - GuildDirectory: bool = false, - /// A channel which can only contains threads - GuildForum: bool = false, - /// Channel that can only contain threads, similar to GUILD_FORUM channels - GuildMedia: bool = false, -}; - -pub const OverwriteTypes = enum { - Role, - Member, -}; - -pub const VideoQualityModes = enum(u4) { - /// Discord chooses the quality for optimal performance - Auto = 1, - /// 720p - Full, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types -pub const ActivityTypes = enum(u4) { - Playing = 0, - Streaming = 1, - Listening = 2, - Watching = 3, - Custom = 4, - Competing = 5, -}; - -/// https://discord.com/developers/docs/resources/channel#message-object-message-types -pub const MessageTypes = enum(u8) { - Default, - RecipientAdd, - RecipientRemove, - Call, - ChannelNameChange, - ChannelIconChange, - ChannelPinnedMessage, - UserJoin, - GuildBoost, - GuildBoostTier1, - GuildBoostTier2, - GuildBoostTier3, - ChannelFollowAdd, - GuildDiscoveryDisqualified = 14, - GuildDiscoveryRequalified, - GuildDiscoveryGracePeriodInitialWarning, - GuildDiscoveryGracePeriodFinalWarning, - ThreadCreated, - Reply, - ChatInputCommand, - ThreadStarterMessage, - GuildInviteReminder, - ContextMenuCommand, - AutoModerationAction, - RoleSubscriptionPurchase, - InteractionPremiumUpsell, - StageStart, - StageEnd, - StageSpeaker, - StageTopic = 31, - GuildApplicationPremiumSubscription, - GuildIncidentAlertModeEnabled = 36, - GuildIncidentAlertModeDisabled, - GuildIncidentReportRaid, - GuildIncidentReportFalseAlarm, - PurchaseNotification = 44, - PollResult = 46, -}; - -/// https://discord.com/developers/docs/resources/channel#message-object-message-activity-types -pub const MessageActivityTypes = enum(u4) { - Join = 1, - Spectate = 2, - Listen = 3, - JoinRequest = 5, -}; - -/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types -pub const StickerTypes = enum(u4) { - /// an official sticker in a pack - Standard = 1, - /// a sticker uploaded to a guild for the guild's members - Guild = 2, -}; - -/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types -pub const StickerFormatTypes = enum(u4) { - Png = 1, - APng, - Lottie, - Gif, -}; - -/// https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype -pub const InteractionTypes = enum(u4) { - Ping = 1, - ApplicationCommand = 2, - MessageComponent = 3, - ApplicationCommandAutocomplete = 4, - ModalSubmit = 5, -}; - -/// https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype -pub const ApplicationCommandOptionTypes = enum(u4) { - SubCommand = 1, - SubCommandGroup, - String, - Integer, - Boolean, - User, - Channel, - Role, - Mentionable, - Number, - Attachment, -}; - -/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events -pub const AuditLogEvents = enum(u4) { - /// Server settings were updated - GuildUpdate = 1, - /// Channel was created - ChannelCreate = 10, - /// Channel settings were updated - ChannelUpdate, - /// Channel was deleted - ChannelDelete, - /// Permission overwrite was added to a channel - ChannelOverwriteCreate, - /// Permission overwrite was updated for a channel - ChannelOverwriteUpdate, - /// Permission overwrite was deleted from a channel - ChannelOverwriteDelete, - /// Member was removed from server - MemberKick = 20, - /// Members were pruned from server - MemberPrune, - /// Member was banned from server - MemberBanAdd, - /// Server ban was lifted for a member - MemberBanRemove, - /// Member was updated in server - MemberUpdate, - /// Member was added or removed from a role - MemberRoleUpdate, - /// Member was moved to a different voice channel - MemberMove, - /// Member was disconnected from a voice channel - MemberDisconnect, - /// Bot user was added to server - BotAdd, - /// Role was created - RoleCreate = 30, - /// Role was edited - RoleUpdate, - /// Role was deleted - RoleDelete, - /// Server invite was created - InviteCreate = 40, - /// Server invite was updated - InviteUpdate, - /// Server invite was deleted - InviteDelete, - /// Webhook was created - WebhookCreate = 50, - /// Webhook properties or channel were updated - WebhookUpdate, - /// Webhook was deleted - WebhookDelete, - /// Emoji was created - EmojiCreate = 60, - /// Emoji name was updated - EmojiUpdate, - /// Emoji was deleted - EmojiDelete, - /// Single message was deleted - MessageDelete = 72, - /// Multiple messages were deleted - MessageBulkDelete, - /// Messaged was pinned to a channel - MessagePin, - /// Message was unpinned from a channel - MessageUnpin, - /// App was added to server - IntegrationCreate = 80, - /// App was updated (as an example, its scopes were updated) - IntegrationUpdate, - /// App was removed from server - IntegrationDelete, - /// Stage instance was created (stage channel becomes live) - StageInstanceCreate, - /// Stage instace details were updated - StageInstanceUpdate, - /// Stage instance was deleted (stage channel no longer live) - StageInstanceDelete, - /// Sticker was created - StickerCreate = 90, - /// Sticker details were updated - StickerUpdate, - /// Sticker was deleted - StickerDelete, - /// Event was created - GuildScheduledEventCreate = 100, - /// Event was updated - GuildScheduledEventUpdate, - /// Event was cancelled - GuildScheduledEventDelete, - /// Thread was created in a channel - ThreadCreate = 110, - /// Thread was updated - ThreadUpdate, - /// Thread was deleted - ThreadDelete, - /// Permissions were updated for a command - ApplicationCommandPermissionUpdate = 121, - /// Auto moderation rule was created - AutoModerationRuleCreate = 140, - /// Auto moderation rule was updated - AutoModerationRuleUpdate, - /// Auto moderation rule was deleted - AutoModerationRuleDelete, - /// Message was blocked by AutoMod according to a rule. - AutoModerationBlockMessage, - /// Message was flagged by AutoMod - AudoModerationFlagMessage, - /// Member was timed out by AutoMod - AutoModerationMemberTimedOut, - /// Creator monetization request was created - CreatorMonetizationRequestCreated = 150, - /// Creator monetization terms were accepted - CreatorMonetizationTermsAccepted, - /// Guild Onboarding Question was created - OnBoardingPromptCreate = 163, - /// Guild Onboarding Question was updated - OnBoardingPromptUpdate, - /// Guild Onboarding Question was deleted - OnBoardingPromptDelete, - /// Guild Onboarding was created - OnBoardingCreate, - /// Guild Onboarding was updated - OnBoardingUpdate, - /// Guild Server Guide was created - HomeSettingsCreate = 190, - /// Guild Server Guide was updated - HomeSettingsUpdate, -}; - -pub const ScheduledEventPrivacyLevel = enum(u4) { - /// the scheduled event is only accessible to guild members - GuildOnly = 2, -}; - -pub const ScheduledEventEntityType = enum(u4) { - StageInstance = 1, - Voice, - External, -}; - -pub const ScheduledEventStatus = enum(u4) { - Scheduled = 1, - Active, - Completed, - Canceled, -}; - -/// https://discord.com/developers/docs/resources/invite#invite-object-target-user-types -pub const TargetTypes = enum(u4) { - Stream = 1, - EmbeddedApplication, -}; - -pub const ApplicationCommandTypes = enum(u4) { - /// A text-based command that shows up when a user types `/` - ChatInput = 1, - /// A UI-based command that shows up when you right click or tap on a user - User, - /// A UI-based command that shows up when you right click or tap on a message - Message, - /// A UI-based command that represents the primary way to invoke an app's Activity - PrimaryEntryPoint, -}; - -pub const ApplicationCommandPermissionTypes = enum(u4) { - Role = 1, - User, - Channel, -}; - -/// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags -pub const BitwisePermissionFlags = packed struct { - pub fn toRaw(self: BitwisePermissionFlags) u49 { - return @as(u49, @bitCast(self)); - } - - pub fn fromRaw(raw: u49) BitwisePermissionFlags { - return @as(BitwisePermissionFlags, @bitCast(raw)); - } - - /// Allows creation of instant invites - CREATE_INSTANT_INVITE: bool = false, - /// Allows kicking members - KICK_MEMBERS: bool = false, - /// Allows banning members - BAN_MEMBERS: bool = false, - /// Allows all permissions and bypasses channel permission overwrites - ADMINISTRATOR: bool = false, - /// Allows management and editing of channels - MANAGE_CHANNELS: bool = false, - /// Allows management and editing of the guild - MANAGE_GUILD: bool = false, - /// Allows for the addition of reactions to messages - ADD_REACTIONS: bool = false, - /// Allows for viewing of audit logs - VIEW_AUDIT_LOG: bool = false, - /// Allows for using priority speaker in a voice channel - PRIORITY_SPEAKER: bool = false, - /// Allows the user to go live - STREAM: bool = false, - /// Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels - VIEW_CHANNEL: bool = false, - /// Allows for sending messages in a channel. (does not allow sending messages in threads) - SEND_MESSAGES: bool = false, - /// Allows for sending of /tts messages - SEND_TTS_MESSAGES: bool = false, - /// Allows for deletion of other users messages - MANAGE_MESSAGES: bool = false, - /// Links sent by users with this permission will be auto-embedded - EMBED_LINKS: bool = false, - /// Allows for uploading images and files - ATTACH_FILES: bool = false, - /// Allows for reading of message history - READ_MESSAGE_HISTORY: bool = false, - /// Allows for using the \@everyone tag to notify all users in a channel, and the \@here tag to notify all online users in a channel - MENTION_EVERYONE: bool = false, - /// Allows the usage of custom emojis from other servers - USE_EXTERNAL_EMOJIS: bool = false, - /// Allows for viewing guild insights - VIEW_GUILD_INSIGHTS: bool = false, - /// Allows for joining of a voice channel - CONNECT: bool = false, - /// Allows for speaking in a voice channel - SPEAK: bool = false, - /// Allows for muting members in a voice channel - MUTE_MEMBERS: bool = false, - /// Allows for deafening of members in a voice channel - DEAFEN_MEMBERS: bool = false, - /// Allows for moving of members between voice channels - MOVE_MEMBERS: bool = false, - /// Allows for using voice-activity-detection in a voice channel - USE_VAD: bool = false, - /// Allows for modification of own nickname - CHANGE_NICKNAME: bool = false, - /// Allows for modification of other users nicknames - MANAGE_NICKNAMES: bool = false, - /// Allows management and editing of roles - MANAGE_ROLES: bool = false, - /// Allows management and editing of webhooks - MANAGE_WEBHOOKS: bool = false, - /// Allows for editing and deleting emojis, stickers, and soundboard sounds created by all users - MANAGE_GUILD_EXPRESSIONS: bool = false, - /// Allows members to use application commands in text channels - USE_SLASH_COMMANDS: bool = false, - /// Allows for requesting to speak in stage channels. - REQUEST_TO_SPEAK: bool = false, - /// Allows for editing and deleting scheduled events created by all users - MANAGE_EVENTS: bool = false, - /// Allows for deleting and archiving threads, and viewing all private threads - MANAGE_THREADS: bool = false, - /// Allows for creating public and announcement threads - CREATE_PUBLIC_THREADS: bool = false, - /// Allows for creating private threads - CREATE_PRIVATE_THREADS: bool = false, - /// Allows the usage of custom stickers from other servers - USE_EXTERNAL_STICKERS: bool = false, - /// Allows for sending messages in threads - SEND_MESSAGES_IN_THREADS: bool = false, - /// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. - USE_EMBEDDED_ACTIVITIES: bool = false, - /// Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels - MODERATE_MEMBERS: bool = false, - /// Allows for viewing role subscription insights. - VIEW_CREATOR_MONETIZATION_ANALYTICS: bool = false, - /// Allows for using soundboard in a voice channel. - USE_SOUNDBOARD: bool = false, - /// Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user - CREATE_GUILD_EXPRESSIONS: bool = false, - /// Allows for creating scheduled events, and editing and deleting those created by the current user - CREATE_EVENTS: bool = false, - /// Allows the usage of custom soundboards sounds from other servers - USE_EXTERNAL_SOUNDS: bool = false, - /// Allows sending voice messages - SEND_VOICE_MESSAGES: bool = false, - /// Allows sending polls - SEND_POLLS: bool = false, - /// Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server. - USE_EXTERNAL_APPS: bool = false, -}; - -pub const PermissionStrings = BitwisePermissionFlags; - -/// https://discord.com/developers/docs/topics/opcodes-and-status-codes#opcodes-and-status-codes -pub const GatewayCloseEventCodes = enum(u16) { - /// A normal closure of the gateway. You may attempt to reconnect. - NormalClosure = 1000, - /// We're not sure what went wrong. Try reconnecting? - UnknownError = 4000, - /// You sent an invalid [Gateway opcode](https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes) or an invalid payload for an opcode. Don't do that! - UnknownOpcode, - /// You sent an invalid [payload](https://discord.com/developers/docs/topics/gateway#sending-payloads) to us. Don't do that! - DecodeError, - /// You sent us a payload prior to [identifying](https://discord.com/developers/docs/topics/gateway-events#identify), or this session has been invalidated. - NotAuthenticated, - /// The account token sent with your [identify payload](https://discord.com/developers/docs/topics/gateway-events#identify) is incorrect. - AuthenticationFailed, - /// You sent more than one identify payload. Don't do that! - AlreadyAuthenticated, - /// The sequence sent when [resuming](https://discord.com/developers/docs/topics/gateway-events#resume) the session was invalid. Reconnect and start a new session. - InvalidSeq = 4007, - /// Woah nelly! You're sending payloads to us too quickly. Slow it down! You will be disconnected on receiving this. - RateLimited, - /// Your session timed out. Reconnect and start a new one. - SessionTimedOut, - /// You sent us an invalid [shard when identifying](https://discord.com/developers/docs/topics/gateway#sharding). - InvalidShard, - /// The session would have handled too many guilds - you are required to [shard](https://discord.com/developers/docs/topics/gateway#sharding) your connection in order to connect. - ShardingRequired, - /// You sent an invalid version for the gateway. - InvalidApiVersion, - /// You sent an invalid intent for a [Gateway Intent](https://discord.com/developers/docs/topics/gateway#gateway-intents). You may have incorrectly calculated the bitwise value. - InvalidIntents, - /// You sent a disallowed intent for a [Gateway Intent](https://discord.com/developers/docs/topics/gateway#gateway-intents). You may have tried to specify an intent that you [have not enabled or are not approved for](https://discord.com/developers/docs/topics/gateway#privileged-intents). - DisallowedIntents, -}; - -/// https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes -pub const GatewayOpcodes = enum(u4) { - /// An event was dispatched. - Dispatch, - /// Fired periodically by the client to keep the connection alive. - Heartbeat, - /// Starts a new session during the initial handshake. - Identify, - /// Update the client's presence. - PresenceUpdate, - /// Used to join/leave or move between voice channels. - VoiceStateUpdate, - /// Resume a previous session that was disconnected. - Resume = 6, - /// You should attempt to reconnect and resume immediately. - Reconnect, - /// Request information about offline guild members in a large guild. - RequestGuildMembers, - /// The session has been invalidated. You should reconnect and identify/resume accordingly. - InvalidSession, - /// Sent immediately after connecting, contains the `heartbeat_interval` to use. - Hello, - /// Sent in response to receiving a heartbeat to acknowledge that it has been received. - HeartbeatACK, -}; - -pub const GatewayDispatchEventNames = union(enum) { - APPLICATION_COMMAND_PERMISSIONS_UPDATE, - AUTO_MODERATION_RULE_CREATE, - AUTO_MODERATION_RULE_UPDATE, - AUTO_MODERATION_RULE_DELETE, - AUTO_MODERATION_ACTION_EXECUTION, - CHANNEL_CREATE, - CHANNEL_UPDATE, - CHANNEL_DELETE, - CHANNEL_PINS_UPDATE, - THREAD_CREATE, - THREAD_UPDATE, - THREAD_DELETE, - THREAD_LIST_SYNC, - THREAD_MEMBER_UPDATE, - THREAD_MEMBERS_UPDATE, - GUILD_AUDIT_LOG_ENTRY_CREATE, - GUILD_CREATE, - GUILD_UPDATE, - GUILD_DELETE, - GUILD_BAN_ADD, - GUILD_BAN_REMOVE, - GUILD_EMOJIS_UPDATE, - GUILD_STICKERS_UPDATE, - GUILD_INTEGRATIONS_UPDATE, - GUILD_MEMBER_ADD, - GUILD_MEMBER_REMOVE, - GUILD_MEMBER_UPDATE, - GUILD_MEMBERS_CHUNK, - GUILD_ROLE_CREATE, - GUILD_ROLE_UPDATE, - GUILD_ROLE_DELETE, - GUILD_SCHEDULED_EVENT_CREATE, - GUILD_SCHEDULED_EVENT_UPDATE, - GUILD_SCHEDULED_EVENT_DELETE, - GUILD_SCHEDULED_EVENT_USER_ADD, - GUILD_SCHEDULED_EVENT_USER_REMOVE, - INTEGRATION_CREATE, - INTEGRATION_UPDATE, - INTEGRATION_DELETE, - INTERACTION_CREATE, - INVITE_CREATE, - INVITE_DELETE, - MESSAGE_CREATE, - MESSAGE_UPDATE, - MESSAGE_DELETE, - MESSAGE_DELETE_BULK, - MESSAGE_REACTION_ADD, - MESSAGE_REACTION_REMOVE, - MESSAGE_REACTION_REMOVE_ALL, - MESSAGE_REACTION_REMOVE_EMOJI, - PRESENCE_UPDATE, - STAGE_INSTANCE_CREATE, - STAGE_INSTANCE_UPDATE, - STAGE_INSTANCE_DELETE, - TYPING_START, - USER_UPDATE, - VOICE_CHANNEL_EFFECT_SEND, - VOICE_STATE_UPDATE, - VOICE_SERVER_UPDATE, - WEBHOOKS_UPDATE, - ENTITLEMENT_CREATE, - ENTITLEMENT_UPDATE, - ENTITLEMENT_DELETE, - MESSAGE_POLL_VOTE_ADD, - MESSAGE_POLL_VOTE_REMOVE, - - READY, - RESUMED, -}; - -/// https://discord.com/developers/docs/topics/gateway#list-of-intents -pub const GatewayIntents = packed struct { - pub fn toRaw(self: GatewayIntents) u28 { - return @as(u28, @bitCast(self)); - } - - pub fn fromRaw(raw: u28) GatewayIntents { - return @as(Intents, @bitCast(raw)); - } - - pub fn jsonStringify(self: GatewayIntents, options: std.json.StringifyOptions, writer: anytype) !void { - _ = options; - try writer.print("{}", .{self.toRaw()}); - } - /// - /// - GUILD_CREATE - /// - GUILD_UPDATE - /// - GUILD_DELETE - /// - GUILD_ROLE_CREATE - /// - GUILD_ROLE_UPDATE - /// - GUILD_ROLE_DELETE - /// - CHANNEL_CREATE - /// - CHANNEL_UPDATE - /// - CHANNEL_DELETE - /// - CHANNEL_PINS_UPDATE - /// - THREAD_CREATE - /// - THREAD_UPDATE - /// - THREAD_DELETE - /// - THREAD_LIST_SYNC - /// - THREAD_MEMBER_UPDATE - /// - THREAD_MEMBERS_UPDATE - /// - STAGE_INSTANCE_CREATE - /// - STAGE_INSTANCE_UPDATE - /// - STAGE_INSTANCE_DELETE - //// - Guilds: bool = false, - /// - /// - GUILD_MEMBER_ADD - /// - GUILD_MEMBER_UPDATE - /// - GUILD_MEMBER_REMOVE - /// - THREAD_MEMBERS_UPDATE - /// - /// This is a privileged intent. - //// - GuildMembers: bool = false, - /// - /// - GUILD_AUDIT_LOG_ENTRY_CREATE - /// - GUILD_BAN_ADD - /// - GUILD_BAN_REMOVE - //// - GuildModeration: bool = false, - /// - /// - GUILD_EMOJIS_UPDATE - /// - GUILD_STICKERS_UPDATE - //// - GuildEmojisAndStickers: bool = false, - /// - /// - GUILD_INTEGRATIONS_UPDATE - /// - INTEGRATION_CREATE - /// - INTEGRATION_UPDATE - /// - INTEGRATION_DELETE - //// - GuildIntegrations: bool = false, - /// - /// - WEBHOOKS_UPDATE - //// - GuildWebhooks: bool = false, - /// - /// - INVITE_CREATE - /// - INVITE_DELETE - //// - GuildInvites: bool = false, - /// - /// - VOICE_STATE_UPDATE - /// - VOICE_CHANNEL_EFFECT_SEND - //// - GuildVoiceStates: bool = false, - /// - /// - PRESENCE_UPDATE - /// - /// This is a privileged intent. - //// - GuildPresences: bool = false, - /// - /// - MESSAGE_CREATE - /// - MESSAGE_UPDATE - /// - MESSAGE_DELETE - /// - MESSAGE_DELETE_BULK - /// - /// The messages do not contain content by default. - /// If you want to receive their content too, you need to turn on the privileged `MESSAGE_CONTENT` intent. */ - GuildMessages: bool = false, - /// - /// - MESSAGE_REACTION_ADD - /// - MESSAGE_REACTION_REMOVE - /// - MESSAGE_REACTION_REMOVE_ALL - /// - MESSAGE_REACTION_REMOVE_EMOJI - //// - GuildMessageReactions: bool = false, - /// - /// - TYPING_START - //// - GuildMessageTyping: bool = false, - /// - /// - CHANNEL_CREATE - /// - MESSAGE_CREATE - /// - MESSAGE_UPDATE - /// - MESSAGE_DELETE - /// - CHANNEL_PINS_UPDATE - //// - DirectMessages: bool = false, - /// - /// - MESSAGE_REACTION_ADD - /// - MESSAGE_REACTION_REMOVE - /// - MESSAGE_REACTION_REMOVE_ALL - /// - MESSAGE_REACTION_REMOVE_EMOJI - //// - DirectMessageReactions: bool = false, - /// - /// - TYPING_START - //// - DirectMessageTyping: bool = false, - /// - /// This intent will add all content related values to message events. - /// - /// This is a privileged intent. - //// - MessageContent: bool = false, - /// - /// - GUILD_SCHEDULED_EVENT_CREATE - /// - GUILD_SCHEDULED_EVENT_UPDATE - /// - GUILD_SCHEDULED_EVENT_DELETE - /// - GUILD_SCHEDULED_EVENT_USER_ADD this is experimental and unstable. - /// - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable. - //// - GuildScheduledEvents: bool = false, - _pad: u4 = 0, - /// - /// - AUTO_MODERATION_RULE_CREATE - /// - AUTO_MODERATION_RULE_UPDATE - /// - AUTO_MODERATION_RULE_DELETE - //// - AutoModerationConfiguration: bool = false, - /// - /// - AUTO_MODERATION_ACTION_EXECUTION - //// - AutoModerationExecution: bool = false, - _pad2: u3 = 0, - /// - /// - MESSAGE_POLL_VOTE_ADD - /// - MESSAGE_POLL_VOTE_REMOVE - //// - GuildMessagePolls: bool = false, - /// - /// - MESSAGE_POLL_VOTE_ADD - /// - MESSAGE_POLL_VOTE_REMOVE - //// - DirectMessagePolls: bool = false, -}; - -/// https://discord.com/developers/docs/topics/gateway#list-of-intents -pub const Intents = GatewayIntents; - -/// https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionresponsetype -pub const InteractionResponseTypes = enum(u4) { - /// ACK a `Ping` - Pong = 1, - /// Respond to an interaction with a message - ChannelMessageWithSource = 4, - /// ACK an interaction and edit a response later, the user sees a loading state - DeferredChannelMessageWithSource = 5, - /// For components, ACK an interaction and edit the original message later; the user does not see a loading state - DeferredUpdateMessage = 6, - /// For components, edit the message the component was attached to - UpdateMessage = 7, - /// For Application Command Options, send an autocomplete result - ApplicationCommandAutocompleteResult = 8, - /// For Command or Component interactions, send a Modal response - Modal = 9, - /// - /// Respond to an interaction with an upgrade button, only available for apps with monetization enabled - /// - /// @deprecated You should migrate to the premium button components - PremiumRequired = 10, - /// - /// Launch the Activity associated with the app. - /// - /// @remarks - /// Only available for apps with Activities enabled - LaunchActivity = 12, -}; - -pub const SortOrderTypes = enum { - /// Sort forum posts by activity - LatestActivity, - /// Sort forum posts by creation time (from most recent to oldest) - CreationDate, -}; - -pub const ForumLayout = enum(u4) { - /// No default has been set for forum channel. - NotSet = 0, - /// Display posts as a list. - ListView = 1, - /// Display posts as a collection of tiles. - GalleryView = 2, -}; - -/// https://discord.com/developers/docs/reference#image-formatting -/// json is only for stickers -pub const ImageFormat = union(enum) { - jpg, - jpeg, - png, - webp, - gif, - json, -}; - -/// https://discord.com/developers/docs/reference#image-formatting -pub const ImageSize = isize; - -pub const Locales = enum { - id, - da, - de, - @"en-GB", - @"en-US", - @"es-ES", - @"es-419", - fr, - hr, - it, - lt, - hu, - nl, - no, - pl, - @"pt-BR", - ro, - fi, - @"sv-SE", - vi, - tr, - cs, - el, - bg, - ru, - uk, - hi, - th, - @"zh-CN", - ja, - @"zh-TW", - ko, -}; - -/// https://discord.com/developers/docs/resources/user#user-object -pub const User = struct { - /// The user's username, not unique across the platform - username: []const u8, - /// The user's display name, if it is set. For bots, this is the application name - global_name: ?[]const u8, - /// The user's chosen language option - locale: ?[]const u8, - /// The flags on a user's account - flags: ?isize, - /// The type of Nitro subscription on a user's account - premium_type: ?PremiumTypes, - /// The public flags on a user's account - public_flags: ?isize, - /// the user's banner color encoded as an integer representation of hexadecimal color code - accent_color: ?isize, - /// The user's id - id: Snowflake, - /// The user's discord-tag - discriminator: []const u8, - /// The user's avatar hash - avatar: ?[]const u8, - /// Whether the user belongs to an OAuth2 application - bot: ?bool, - ///Whether the user is an Official System user (part of the urgent message system) - system: ?bool, - /// Whether the user has two factor enabled on their account - mfa_enabled: ?bool, - /// Whether the email on this account has been verified - verified: ?bool, - /// The user's email - email: ?[]const u8, - /// the user's banner, or null if unset - banner: ?[]const u8, - /// data for the user's avatar decoration - avatar_decoration_data: ?AvatarDecorationData, - clan: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes -pub const OAuth2Scope = enum { - /// - /// Allows your app to fetch data from a user's "Now Playing/Recently Played" list - /// - /// @remarks - /// This scope is not currently available for apps - /// - @"activities.read", - /// - /// Allows your app to update a user's activity - /// - /// @remarks - /// This scope not currently available for apps. - /// - @"activities.write", - /// Allows your app to read build data for a user's applications - @"applications.builds.read", - /// - /// Allows your app to upload/update builds for a user's applications - /// - /// @remarks - ///This scope requires approval to be used - /// - @"applications.builds.upload", - /// Allows your app to add commands to a guild - included by default with the `bot` scope - @"applications.commands", - /// - /// Allows your app to update its Application Commands via this bearer token - /// - /// @remarks - /// This scope can only be used when using a [Client Credential Grant](https://discord.com/developers/docs/topics/oauth2#client-credentials-grant) - /// - @"applications.commands.update", - /// Allows your app to update permissions for its commands in a guild a user has permissions to - @"applications.commands.permissions.update", - /// Allows your app to read entitlements for a user's applications - @"applications.entitlements", - /// Allows your app to read and update store data (SKUs, store listings, achievements, etc.) for a user's applications - @"applications.store.update", - /// For oauth2 bots, this puts the bot in the user's selected guild by default - bot, - /// Allows requests to [/users/@me/connections](https://discord.com/developers/docs/resources/user#get-user-connections) - connections, - /// - /// Allows your app to see information about the user's DMs and group DMs - /// - /// @remarks - ///This scope requires approval to be used - /// - @"dm_channels.read", - /// Adds the `email` filed to [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) - email, - /// Allows your app to join users to a group dm - @"gdm.join", - /// Allows requests to [/users/@me/guilds](https://discord.com/developers/docs/resources/user#get-current-user-guilds) - guilds, - /// Allows requests to [/guilds/{guild.id};/members/{user.id};](https://discord.com/developers/docs/resources/guild#add-guild-member) - @"guilds.join", - /// Allows requests to [/users/@me/guilds/{guild.id};/member](https://discord.com/developers/docs/resources/user#get-current-user-guild-member) - @"guilds.members.read", - /// - /// Allows requests to [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) - /// - /// @remarks - /// The return object from [/users/@me](https://discord.com/developers/docs/resources/user#get-current-user) - /// does NOT contain the `email` field unless the scope `email` is also used - /// - identify, - /// - /// For local rpc server api access, this allows you to read messages from all client channels - /// (otherwise restricted to channels/guilds your app creates) - /// - @"messages.read", - /// - /// Allows your app to know a user's friends and implicit relationships - /// - /// @remarks - ///This scope requires approval to be used - /// - @"relationships.read", - /// Allows your app to update a user's connection and metadata for the app - @"role_connections.write", - /// - ///For local rpc server access, this allows you to control a user's local client - /// - /// @remarks - ///This scope requires approval to be used - /// - rpc, - /// - /// For local rpc server access, this allows you to update a user's activity - /// - /// @remarks - ///This scope requires approval to be used - /// - @"rpc.activities.write", - /// - /// For local rpc server api access, this allows you to receive notifications pushed out to the user - /// - /// @remarks - ///This scope requires approval to be used - /// - @"rpc.notifications.read", - /// - /// For local rpc server access, this allows you to read a user's voice settings and listen for voice events - /// - /// @remarks - ///This scope requires approval to be used - /// - @"rpc.voice.read", - /// - /// For local rpc server access, this allows you to update a user's voice settings - /// - /// @remarks - ///This scope requires approval to be used - /// - @"rpc.voice.write", - /// - /// Allows your app to connect to voice on user's behalf and see all the voice members - /// - /// @remarks - ///This scope requires approval to be used - /// - voice, - /// Generate a webhook that is returned in the oauth token response for authorization code grants - @"webhook.incoming", -}; - -/// https://discord.com/developers/docs/resources/guild#integration-object-integration-structure -pub const Integration = struct { - /// Integration Id - id: Snowflake, - /// Integration name - name: []const u8, - /// Integration type (twitch, youtube, discord, or guild_subscription). - type: union(enum) { - twitch, - youtube, - discord, - }, - /// Is this integration enabled - enabled: ?bool, - /// Is this integration syncing - syncing: ?bool, - /// Role Id that this integration uses for "subscribers" - role_id: ?Snowflake, - /// Whether emoticons should be synced for this integration (twitch only currently) - enable_emoticons: ?bool, - /// The behavior of expiring subscribers - expire_behavior: ?IntegrationExpireBehaviors, - /// The grace period (in days) before expiring subscribers - expire_grace_period: ?isize, - /// When this integration was last synced - synced_at: ?[]const u8, - /// How many subscribers this integration has - subscriber_count: ?isize, - /// Has this integration been revoked - revoked: ?bool, - /// User for this integration - user: ?User, - /// Integration account information - account: IntegrationAccount, - /// The bot/OAuth2 application for discord integrations - application: ?IntegrationApplication, - /// the scopes the application has been authorized for - scopes: []OAuth2Scope, -}; - -/// https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure -pub const IntegrationAccount = struct { - /// Id of the account - id: Snowflake, - /// Name of the account - name: []const u8, -}; - -/// https://discord.com/developers/docs/resources/guild#integration-application-object-integration-application-structure -pub const IntegrationApplication = struct { - /// The id of the app - id: Snowflake, - /// The name of the app - name: []const u8, - /// the icon hash of the app - icon: ?[]const u8, - /// The description of the app - description: []const u8, - /// The bot associated with this application - bot: ?User, -}; - -/// https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-create-event-additional-fields -pub const IntegrationCreateUpdate = struct { - /// Integration Id - id: Snowflake, - /// Integration name - name: []const u8, - /// Integration type (twitch, youtube, discord, or guild_subscription). - type: union(enum) { - twitch, - youtube, - discord, - }, - /// Is this integration enabled - enabled: ?bool, - /// Is this integration syncing - syncing: ?bool, - /// Role Id that this integration uses for "subscribers" - role_id: ?Snowflake, - /// Whether emoticons should be synced for this integration (twitch only currently) - enable_emoticons: ?bool, - /// The behavior of expiring subscribers - expire_behavior: ?IntegrationExpireBehaviors, - /// The grace period (in days) before expiring subscribers - expire_grace_period: ?isize, - /// When this integration was last synced - synced_at: ?[]const u8, - /// How many subscribers this integration has - subscriber_count: ?isize, - /// Has this integration been revoked - revoked: ?bool, - /// User for this integration - user: ?User, - /// Integration account information - account: IntegrationAccount, - /// The bot/OAuth2 application for discord integrations - application: ?IntegrationApplication, - /// the scopes the application has been authorized for - scopes: []OAuth2Scope, - /// Id of the guild - guild_id: Snowflake, -}; - -/// https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-delete-event-fields -pub const IntegrationDelete = struct { - /// Integration id - id: Snowflake, - /// Id of the guild - guild_id: Snowflake, - /// Id of the bot/OAuth2 application for this discord integration - application_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-integrations-update -pub const GuildIntegrationsUpdate = struct { - /// id of the guild whose integrations were updated - guild_id: Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#typing-start -pub const TypingStart = struct { - /// Unix time (in seconds) of when the user started typing - timestamp: isize, - /// id of the channel - channel_id: Snowflake, - /// id of the guild - guild_id: ?Snowflake, - /// id of the user - user_id: Snowflake, - /// The member who started typing if this happened in a guild - member: ?Member, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-member-object -pub const Member = struct { - /// Whether the user is deafened in voice channels - deaf: ?bool, - /// Whether the user is muted in voice channels - mute: ?bool, - /// Whether the user has not yet passed the guild's Membership Screening requirements - pending: ?bool, - /// The user this guild member represents - user: ?User, - /// This users guild nickname - nick: ?[]const u8, - /// The members custom avatar for this server. - avatar: ?[]const u8, - /// Array of role object ids - roles: [][]const u8, - /// When the user joined the guild - joined_at: []const u8, - /// When the user started boosting the guild - premium_since: ?[]const u8, - /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. - permissions: ?[]const u8, - /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out - communication_disabled_until: ?[]const u8, - /// Guild member flags - flags: isize, - /// data for the member's guild avatar decoration - avatar_decoration_data: ?AvatarDecorationData, -}; - -/// https://discord.com/developers/docs/resources/user#avatar-decoration-data-object -pub const AvatarDecorationData = struct { - /// the avatar decoration hash - asset: []const u8, - /// id of the avatar decoration's SKU - sku_id: Snowflake, -}; - -/// https://discord.com/developers/docs/resources/application#application-object -pub const Application = struct { - /// The name of the app - name: []const u8, - /// The description of the app - description: []const u8, - /// An array of rpc origin urls, if rpc is enabled - 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 - privacy_policy_url: ?[]const u8, - /// The hex encoded key for verification in interactions and the GameSDK's GetTicket - verify_key: []const u8, - ///If this application is a game sold on , this field will be the id of the "Game SKU" that is created, if exists - primary_sku_id: ?Snowflake, - ///If this application is a game sold on , this field will be the URL slug that links to the store page - slug: ?[]const u8, - /// The application's public flags - flags: ?ApplicationFlags, - /// The id of the app - id: Snowflake, - /// The icon hash of the app - icon: ?[]const u8, - /// When false only app owner can join the app's bot to guilds - bot_public: bool, - /// When true the app's bot will only join upon completion of the full oauth2 code grant flow - bot_require_code_grant: bool, - /// Partial user object containing info on the owner of the application - owner: ?Partial(User), - /// If the application belongs to a team, this will be a list of the members of that team - team: ?Team, - /// Guild associated with the app. For example, a developer support server. - guild_id: ?Snowflake, - /// A partial object of the associated guild - guild: ?Partial(Guild), - ///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, - /// settings for the application's default in-app authorization link, if enabled - install_params: ?InstallParams, - /// Default scopes and permissions for each supported installation context. - integration_types_config: ?Partial(AutoArrayHashMap(ApplicationIntegrationType, ApplicationIntegrationTypeConfiguration)), - /// the application's default custom authorization link, if enabled - custom_install_url: ?[]const u8, - /// the application's role connection verification entry point, which when configured will render the app as a verification method in the guild role verification configuration - role_connections_verification_url: ?[]const u8, - /// An approximate count of the app's guild membership. - approximate_guild_count: ?isize, - /// Approximate count of users that have installed the app. - approximate_user_install_count: ?isize, - /// 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, - /// Interactions endpoint URL for the app - interactions_endpoint_url: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/application#application-object-application-integration-type-configuration-object -pub const ApplicationIntegrationTypeConfiguration = struct { - /// - /// Install params for each installation context's default in-app authorization link - /// - /// https://discord.com/developers/docs/resources/application#install-params-object-install-params-structure - /// - oauth2_install_params: ?InstallParams, -}; - -pub const ApplicationIntegrationType = enum(u4) { - /// App is installable to servers - GuildInstall = 0, - /// App is installable to users - UserInstall = 1, -}; - -/// TODO: implement -pub const TokenExchange = null; - -pub const TokenExchangeAuthorizationCode = struct { - grant_type: []const u8, //"authorization_code", - /// The code for the token exchange - code: []const u8, - /// The redirect_uri associated with this authorization - redirect_uri: []const u8, -}; - -/// https://discord.com/developers/docs/topics/oauth2#client-credentials-grant -pub const TokenExchangeRefreshToken = struct { - grant_type: "refresh_token", - /// the user's refresh token - refresh_token: []const u8, -}; - -/// https://discord.com/developers/docs/topics/oauth2#client-credentials-grant -pub const TokenExchangeClientCredentials = struct { - grant_type: "client_credentials", - /// The scope(s) for the access token - scope: []OAuth2Scope, -}; - -pub const AccessTokenResponse = struct { - /// The access token of the user - access_token: []const u8, - /// The type of token - token_type: []const u8, - /// The isize of seconds after that the access token is expired - expires_in: isize, - /// - /// The refresh token to refresh the access token - /// - /// @remarks - /// When the token exchange is a client credentials type grant this value is not defined. - /// - refresh_token: []const u8, - /// The scopes for the access token - scope: []const u8, - /// The webhook the user created for the application. Requires the `webhook.incoming` scope - webhook: ?IncomingWebhook, - /// The guild the bot has been added. Requires the `bot` scope - guild: ?Guild, -}; - -pub const TokenRevocation = struct { - /// The access token to revoke - token: []const u8, - /// Optional, the type of token you are using for the revocation - token_type_hint: ?"access_token' | 'refresh_token", -}; - -/// https://discord.com/developers/docs/topics/oauth2#get-current-authorization-information-response-structure -pub const CurrentAuthorization = struct { - application: Application, - /// the scopes the user has authorized the application for - scopes: []OAuth2Scope, - /// when the access token expires - expires: []const u8, - /// the user who has authorized, if the user has authorized with the `identify` scope - user: ?User, -}; - -/// https://discord.com/developers/docs/resources/user#connection-object-connection-structure -pub const Connection = struct { - /// id of the connection account - id: Snowflake, - /// the username of the connection account - name: []const u8, - /// the service of this connection - type: ConnectionServiceType, - /// whether the connection is revoked - revoked: ?bool, - /// an array of partial server integrations - integrations: []?Partial(Integration), - /// whether the connection is verified - verified: bool, - /// whether friend sync is enabled for this connection - friend_sync: bool, - /// whether activities related to this connection will be shown in presence updates - show_activity: bool, - /// whether this connection has a corresponding third party OAuth2 token - two_way_link: bool, - /// visibility of this connection - visibility: ConnectionVisibility, -}; - -/// https://discord.com/developers/docs/resources/user#connection-object-services -pub const ConnectionServiceType = enum { - @"amazon-music", - battlenet, - @"Bungie.net", - domain, - ebay, - epicgames, - facebook, - github, - instagram, - leagueoflegends, - paypal, - playstation, - reddit, - riotgames, - roblox, - spotify, - skype, - steam, - tiktok, - twitch, - twitter, - xbox, - youtube, -}; - -//https://discord.com/developers/docs/resources/user#connection-object-visibility-types -pub const ConnectionVisibility = enum(u4) { - /// invisible to everyone except the user themselves - None = 0, - /// visible to everyone - Everyone = 1, -}; - -/// https://discord.com/developers/docs/resources/user#application-role-connection-object-application-role-connection-structure -pub const ApplicationRoleConnection = struct { - /// the vanity name of the platform a bot has connected (max 50 characters) - platform_name: ?[]const u8, - /// the username on the platform a bot has connected (max 100 characters) - platform_username: ?[]const u8, - /// object mapping application role connection metadata keys to their stringified value (max 100 characters) for the user on the platform a bot has connected - metadata: AutoArrayHashMap([]const u8, []const u8), -}; - -/// https://discord.com/developers/docs/topics/teams#data-models-team-object -pub const Team = struct { - /// Hash of the image of the team's icon - icon: ?[]const u8, - /// Unique ID of the team - id: Snowflake, - /// Members of the team - members: []TeamMember, - /// User ID of the current team owner - owner_user_id: Snowflake, - /// Name of the team - name: []const u8, -}; - -/// https://discord.com/developers/docs/topics/teams#data-models-team-members-object -pub const TeamMember = struct { - /// The user's membership state on the team - membership_state: TeamMembershipStates, - /// The id of the parent team of which they are a member - team_id: Snowflake, - /// The avatar, discriminator, id, username, and global_name of the user - /// TODO: needs fixing - user: struct { - /// Unique ID of the user - id: Snowflake, - /// The user's username, not unique across the platform - username: []const u8, - /// The user's display name, if it is set. For bots, this is the application name - global_name: []const u8, - /// The user's discord-tag - discriminator: []const u8, - /// The user's avatar hash - avatar: []const u8, - }, -}; - -/// https://discord.com/developers/docs/topics/gateway#webhooks-update-webhook-update-event-fields -pub const WebhookUpdate = struct { - /// id of the guild - guild_id: Snowflake, - /// id of the channel - channel_id: Snowflake, -}; - -/// 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, - /// 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, - /// Array of user_ids to mention (Max size of 100) - users: []?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object -pub const Embed = struct { - /// Title of embed - title: ?[]const u8, - /// Type of embed (always "rich" for webhook embeds) - type: ?EmbedTypes, - /// Description of embed - description: ?[]const u8, - /// Url of embed - url: ?[]const u8, - /// Color code of the embed - color: ?isize, - /// Timestamp of embed content - timestamp: ?[]const u8, - /// Footer information - footer: ?EmbedFooter, - /// Image information - image: ?EmbedImage, - /// Thumbnail information - thumbnail: ?EmbedThumbnail, - /// Video information - video: ?EmbedVideo, - /// Provider information - provider: ?EmbedProvider, - /// Author information - author: ?EmbedAuthor, - /// Fields information - fields: []?EmbedField, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure -pub const EmbedAuthor = struct { - /// Name of author - name: []const u8, - /// Url of author - url: ?[]const u8, - /// Url of author icon (only supports http(s) and attachments) - icon_url: ?[]const u8, - /// A proxied url of author icon - proxy_icon_url: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure -pub const EmbedField = struct { - /// Name of the field - name: []const u8, - /// Value of the field - value: []const u8, - /// Whether or not this field should display inline - @"inline": ?bool, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure -pub const EmbedFooter = struct { - /// Footer text - text: []const u8, - /// Url of footer icon (only supports http(s) and attachments) - icon_url: ?[]const u8, - /// A proxied url of footer icon - proxy_icon_url: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure -pub const EmbedImage = struct { - /// Source url of image (only supports http(s) and attachments) - url: []const u8, - /// A proxied url of the image - proxy_url: ?[]const u8, - /// Height of image - height: ?isize, - /// Width of image - width: ?isize, -}; - -pub const EmbedProvider = struct { - /// Name of provider - name: ?[]const u8, - /// Url of provider - url: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure -pub const EmbedThumbnail = struct { - /// Source url of thumbnail (only supports http(s) and attachments) - url: []const u8, - /// A proxied url of the thumbnail - proxy_url: ?[]const u8, - /// Height of thumbnail - height: ?isize, - /// Width of thumbnail - width: ?isize, -}; - -/// https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure -pub const EmbedVideo = struct { - /// Source url of video - url: ?[]const u8, - /// A proxied url of the video - proxy_url: ?[]const u8, - /// Height of video - height: ?isize, - /// Width of video - width: ?isize, -}; - -/// https://discord.com/developers/docs/resources/channel#attachment-object -pub const Attachment = struct { - /// Name of file attached - filename: []const u8, - /// The title of the file - title: ?[]const u8, - /// The attachment's [media type](https://en.wikipedia.org/wiki/Media_type) - content_type: ?[]const u8, - /// Size of file in bytes - size: isize, - /// Source url of file - url: []const u8, - /// A proxied url of file - proxy_url: []const u8, - /// Attachment id - id: Snowflake, - /// description for the file (max 1024 characters) - description: ?[]const u8, - /// Height of file (if image) - height: ?isize, - /// Width of file (if image) - width: ?isize, - /// whether this attachment is ephemeral. Ephemeral attachments will automatically be removed after a set period of time. Ephemeral attachments on messages are guaranteed to be available as long as the message itself exists. - ephemeral: ?bool, - /// The duration of the audio file for a voice message - duration_secs: ?isize, - /// A base64 encoded bytearray representing a sampled waveform for a voice message - waveform: ?[]const u8, - /// Attachment flags combined as a bitfield - flags: ?AttachmentFlags, -}; - -/// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure -/// TODO: implement -pub const Webhook = null; - -pub const IncomingWebhook = struct { - /// The type of the webhook - type: WebhookTypes, - /// The secure token of the webhook (returned for Incoming Webhooks) - token: ?[]const u8, - /// The url used for executing the webhook (returned by the webhooks OAuth2 flow) - url: ?[]const u8, - - /// The id of the webhook - id: Snowflake, - /// The guild id this webhook is for - guild_id: ?Snowflake, - /// The channel id this webhook is for - channel_id: Snowflake, - /// The user this webhook was created by (not returned when getting a webhook with its token) - user: ?User, - /// The default name of the webhook - name: ?[]const u8, - /// The default user avatar hash of the webhook - avatar: ?[]const u8, - /// The bot/OAuth2 application that created this webhook - application_id: ?Snowflake, - /// The guild of the channel that this webhook is following (returned for Channel Follower Webhooks) - source_guild: ?Partial(Guild), - /// The channel that this webhook is following (returned for Channel Follower Webhooks) - source_channel: ?Partial(Channel), -}; - -pub const ApplicationWebhook = struct { - /// The type of the webhook - type: WebhookTypes.Application, - /// The secure token of the webhook (returned for Incoming Webhooks) - token: ?[]const u8, - /// The url used for executing the webhook (returned by the webhooks OAuth2 flow) - url: ?[]const u8, - - /// The id of the webhook - id: Snowflake, - /// The guild id this webhook is for - guild_id: ?Snowflake, - /// The channel id this webhook is for - channel_id: ?Snowflake, - /// The user this webhook was created by (not returned when getting a webhook with its token) - user: ?User, - /// The default name of the webhook - name: ?[]const u8, - /// The default user avatar hash of the webhook - avatar: ?[]const u8, - /// The bot/OAuth2 application that created this webhook - application_id: ?Snowflake, - /// The guild of the channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides - source_guild: ?Partial(Guild), - /// The channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides - source_channel: ?Partial(Channel), -}; - -/// https://discord.com/developers/docs/resources/guild#guild-object -pub const Guild = struct { - /// Guild name (2-100 characters, excluding trailing and leading whitespace) - name: []const u8, - /// True if the user is the owner of the guild - owner: ?bool, - /// Afk timeout in seconds - afk_timeout: isize, - /// True if the server widget is enabled - widget_enabled: ?bool, - /// Verification level required for the guild - verification_level: VerificationLevels, - /// Default message notifications level - default_message_notifications: DefaultMessageNotificationLevels, - /// Explicit content filter level - explicit_content_filter: ExplicitContentFilterLevels, - /// Enabled guild features - features: []GuildFeatures, - /// Required MFA level for the guild - mfa_level: MfaLevels, - /// System channel flags - system_channel_flags: SystemChannelFlags, - /// True if this is considered a large guild - large: ?bool, - /// True if this guild is unavailable due to an outage - unavailable: ?bool, - /// Total isize of members in this guild - member_count: ?isize, - /// The maximum isize of presences for the guild (the default value, currently 25000, is in effect when null is returned) - max_presences: ?isize, - /// The maximum isize of members for the guild - max_members: ?isize, - /// The vanity url code for the guild - vanity_url_code: ?[]const u8, - /// The description of a guild - description: ?[]const u8, - /// Premium tier (Server Boost level) - premium_tier: PremiumTiers, - /// The isize of boosts this guild currently has - premium_subscription_count: ?isize, - /// The maximum amount of users in a video channel - max_video_channel_users: ?isize, - /// Maximum amount of users in a stage video channel - max_stage_video_channel_users: ?isize, - /// Approximate isize of members in this guild, returned from the GET /guilds/id endpoint when with_counts is true - approximate_member_count: ?isize, - /// Approximate isize of non-offline members in this guild, returned from the GET /guilds/id endpoint when with_counts is true - approximate_presence_count: ?isize, - /// Guild NSFW level - nsfw_level: GuildNsfwLevel, - /// Whether the guild has the boost progress bar enabled - premium_progress_bar_enabled: bool, - /// Guild id - id: Snowflake, - /// Icon hash - icon: ?[]const u8, - /// Icon hash, returned when in the template object - icon_hash: ?[]const u8, - /// Splash hash - splash: ?[]const u8, - /// Discovery splash hash; only present for guilds with the "DISCOVERABLE" feature - discovery_splash: ?[]const u8, - /// Id of the owner - owner_id: Snowflake, - /// Total permissions for the user in the guild (excludes overwrites and implicit permissions) - permissions: ?[]const u8, - /// Id of afk channel - afk_channel_id: ?Snowflake, - /// The channel id that the widget will generate an invite to, or null if set to no invite - widget_channel_id: ?Snowflake, - /// Roles in the guild - roles: []Role, - /// Custom guild emojis - emojis: []Emoji, - /// Application id of the guild creator if it is bot-created - application_id: ?Snowflake, - /// The id of the channel where guild notices such as welcome messages and boost events are posted - system_channel_id: ?Snowflake, - /// The id of the channel where community guilds can display rules and/or guidelines - rules_channel_id: ?Snowflake, - /// 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"}), - /// Users in the guild - members: []?Member, - /// Channels in the guild - channels: []?Channel, - /// All active threads in the guild that the current user has permission to view - 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), - /// Banner hash - banner: ?[]const u8, - ///The preferred locale of a Community guild; used in server discovery and notices from ; defaults to "en-US" - preferred_locale: []const u8, - ///The id of the channel where admins and moderators of Community guilds receive notices from - public_updates_channel_id: ?Snowflake, - /// 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, - /// Custom guild stickers - stickers: []?Sticker, - ///The id of the channel where admins and moderators of Community guilds receive safety alerts from - safety_alerts_channel_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/topics/permissions#role-object-role-structure -pub const Role = struct { - /// Role id - id: Snowflake, - /// If this role is showed separately in the user listing - hoist: bool, - /// Permission bit set - permissions: []const u8, - /// Whether this role is managed by an integration - managed: bool, - /// Whether this role is mentionable - mentionable: bool, - /// The tags this role has - tags: ?RoleTags, - /// the role emoji hash - icon: ?[]const u8, - /// Role name - name: []const u8, - /// Integer representation of hexadecimal color code - color: isize, - /// Position of this role (roles with the same position are sorted by id) - position: isize, - /// role unicode emoji - unicode_emoji: ?[]const u8, - /// Role flags combined as a bitfield - flags: RoleFlags, -}; - -/// https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure -pub const RoleTags = struct { - /// The id of the bot this role belongs to - bot_id: ?Snowflake, - /// The id of the integration this role belongs to - integration_id: ?Snowflake, - /// Whether this is the guild's premium subscriber role - /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". - premium_subscriber: ?bool, - /// Id of this role's subscription sku and listing. - subscription_listing_id: ?Snowflake, - /// Whether this role is available for purchase. - /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". - available_for_purchase: ?bool, - /// Whether this is a guild's linked role - /// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false". - guild_connections: ?bool, -}; - -/// https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure -pub const Emoji = struct { - /// Emoji name (can only be null in reaction emoji objects) - name: ?[]const u8, - /// Emoji id - id: ?Snowflake, - /// Roles allowed to use this emoji - roles: []?[]const u8, - /// User that created this emoji - user: ?User, - /// Whether this emoji must be wrapped in colons - require_colons: ?bool, - /// Whether this emoji is managed - managed: ?bool, - /// Whether this emoji is animated - animated: ?bool, - /// Whether this emoji can be used, may be false due to loss of Server Boosts - available: ?bool, -}; - -/// https://discord.com/developers/docs/resources/voice#voice-state-object-voice-state-structure -pub const VoiceState = struct { - /// The session id for this voice state - session_id: []const u8, - /// The guild id this voice state is for - guild_id: ?Snowflake, - /// The channel id this user is connected to - channel_id: ?Snowflake, - /// The user id this voice state is for - user_id: Snowflake, - /// The guild member this voice state is for - member: ?MemberWithUser, - /// Whether this user is deafened by the server - deaf: bool, - /// Whether this user is muted by the server - mute: bool, - /// Whether this user is locally deafened - self_deaf: bool, - /// Whether this user is locally muted - self_mute: bool, - /// Whether this user is streaming using "Go Live" - self_stream: ?bool, - /// Whether this user's camera is enabled - self_video: bool, - /// Whether this user is muted by the current user - suppress: bool, - /// The time at which the user requested to speak - request_to_speak_timestamp: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#channel-object -pub const Channel = struct { - /// The id of the channel - id: Snowflake, - /// The type of channel - type: ChannelTypes, - /// The id of the guild - guild_id: ?Snowflake, - /// 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, - /// 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) - topic: ?[]const u8, - /// Whether the channel is nsfw - nsfw: ?bool, - /// The id of the last message sent in this channel (may not point to an existing or valid message) - last_message_id: ?Snowflake, - /// The bitrate (in bits) of the voice or stage channel - bitrate: ?isize, - /// The user limit of the voice or stage channel - user_limit: ?isize, - /// 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, - /// icon hash of the group DM - icon: ?[]const u8, - /// Id of the creator of the thread - owner_id: ?Snowflake, - /// Application id of the group DM creator if it is bot-created - application_id: ?Snowflake, - /// For group DM channels: whether the channel is managed by an application via the `gdm.join` OAuth2 scope., - managed: ?bool, - /// For guild channels: Id of the parent category for a channel (each parent category can contain up to 50 channels), for threads: id of the text channel this thread was created, - parent_id: ?Snowflake, - /// When the last pinned message was pinned. This may be null in events such as GUILD_CREATE when a message is not pinned. - last_pin_timestamp: ?[]const u8, - /// Voice region id for the voice or stage channel, automatic when set to null - rtc_region: ?[]const u8, - /// The camera video quality mode of the voice channel, 1 when not present - video_quality_mode: ?VideoQualityModes, - /// An approximate count of messages in a thread, stops counting at 50 - message_count: ?isize, - /// An approximate count of users in a thread, stops counting at 50 - member_count: ?isize, - /// Thread-specific fields not needed by other channels - thread_metadata: ?ThreadMetadata, - /// Thread member object for the current user, if they have joined the thread, only included on certain API endpoints - member: ?ThreadMember, - /// Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080, - default_auto_archive_duration: ?isize, - /// computed permissions for the invoking user in the channel, including overwrites, only included when part of the resolved data received on a slash command interaction. This does not include implicit permissions, which may need to be checked separately. - permissions: ?[]const u8, - /// The flags of the channel - flags: ?ChannelFlags, - /// 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, - /// The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel - 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. - default_thread_rate_limit_per_user: ?isize, - /// the default sort order type used to order posts in GUILD_FORUM channels. Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin - default_sort_order: ?SortOrderTypes, - /// the default forum layout view used to display posts in `GUILD_FORUM` channels. Defaults to `0`, which indicates a layout view has not been set by a channel admin - default_forum_layout: ?ForumLayout, - /// When a thread is created this will be true on that channel payload for the thread. - newly_created: ?bool, -}; - -/// https://discord.com/developers/docs/topics/gateway#presence-update -pub const PresenceUpdate = struct { - /// Either "idle", "dnd", "online", or "offline" - status: union(enum) { - idle, - dnd, - online, - offline, - }, - /// The user presence is being updated for - user: User, - /// id of the guild - guild_id: Snowflake, - /// User's current activities - activities: []Activity, - /// User's platform-dependent status - client_status: ClientStatus, -}; - -/// https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-structure -pub const WelcomeScreen = struct { - /// The server description shown in the welcome screen - description: ?[]const u8, - /// The channels shown in the welcome screen, up to 5 - welcome_channels: []WelcomeScreenChannel, -}; - -/// https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure -pub const WelcomeScreenChannel = struct { - /// The description shown for the channel - description: []const u8, - /// The channel's id - channel_id: Snowflake, - /// The emoji id, if the emoji is custom - emoji_id: ?Snowflake, - /// The emoji name if custom, the unicode character if standard, or `null` if no emoji is set - emoji_name: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/stage-instance#auto-closing-stage-instance-structure -pub const StageInstance = struct { - /// The topic of the Stage instance (1-120 characters) - topic: []const u8, - /// The id of this Stage instance - id: Snowflake, - /// The guild id of the associated Stage channel - guild_id: Snowflake, - /// The id of the associated Stage channel - channel_id: Snowflake, - /// The id of the scheduled event for this Stage instance - guild_scheduled_event_id: ?Snowflake, -}; - -pub const ThreadMetadata = struct { - /// Whether the thread is archived - archived: bool, - /// Duration in minutes to automatically archive the thread after recent activity - auto_archive_duration: isize, - /// When a thread is locked, only users with `MANAGE_THREADS` can unarchive it - locked: bool, - /// whether non-moderators can add other non-moderators to a thread; only available on private threads - invitable: ?bool, - /// Timestamp when the thread's archive status was last changed, used for calculating recent activity - archive_timestamp: []const u8, - /// Timestamp when the thread was created; only populated for threads created after 2022-01-09 - create_timestamp: ?[]const u8, -}; - -pub const ThreadMember = struct { - /// Any user-thread settings, currently only used for notifications - flags: isize, - /// The id of the thread - id: Snowflake, - /// The id of the user - user_id: Snowflake, - /// The time the current user last joined the thread - join_timestamp: []const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#activity-object -pub const Activity = struct { - /// The activity's name - name: []const u8, - /// Activity type - type: ActivityTypes, - /// Stream url, is validated when type is 1 - url: ?[]const u8, - /// Unix timestamp of when the activity was added to the user's session - created_at: isize, - /// What the player is currently doing - details: ?[]const u8, - /// The user's current party status - state: ?[]const u8, - /// Whether or not the activity is an instanced game session - instance: ?bool, - /// Activity flags `OR`d together, describes what the payload includes - flags: ?isize, - /// Unix timestamps for start and/or end of the game - timestamps: ?ActivityTimestamps, - /// Application id for the game - application_id: ?Snowflake, - /// The emoji used for a custom status - emoji: ?ActivityEmoji, - /// Information for the current party of the player - party: ?ActivityParty, - /// Images for the presence and their hover texts - assets: ?ActivityAssets, - /// Secrets for Rich Presence joining and spectating - secrets: ?ActivitySecrets, - /// The custom buttons shown in the Rich Presence (max 2) - buttons: []?ActivityButton, -}; - -/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-instance-object -pub const ActivityInstance = struct { - /// Application ID - application_id: Snowflake, - /// Activity Instance ID - instance_id: Snowflake, - /// Unique identifier for the launch - launch_id: Snowflake, - /// The Location the instance is runnning in - location: ActivityLocation, - /// The IDs of the Users currently connected to the instance - users: [][]const u8, -}; - -/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-object -pub const ActivityLocation = struct { - /// The unique identifier for the location - id: Snowflake, - /// Enum describing kind of location - kind: ActivityLocationKind, - /// The id of the Channel - channel_id: Snowflake, - /// The id of the Guild - guild_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-kind-enum -pub const ActivityLocationKind = enum { - /// The Location is a Guild Channel - gc, - /// The Location is a Private Channel, such as a DM or GDM - pc, -}; - -/// https://discord.com/developers/docs/topics/gateway#client-status-object -pub const ClientStatus = struct { - /// The user's status set for an active desktop (Windows, Linux, Mac) application session - desktop: ?[]const u8, - /// The user's status set for an active mobile (iOS, Android) application session - mobile: ?[]const u8, - /// The user's status set for an active web (browser, bot account) application session - web: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-timestamps -pub const ActivityTimestamps = struct { - /// Unix time (in milliseconds) of when the activity started - start: ?isize, - /// Unix time (in milliseconds) of when the activity ends - end: ?isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-emoji -pub const ActivityEmoji = struct { - /// The name of the emoji - name: []const u8, - /// Whether this emoji is animated - animated: ?bool, - /// The id of the emoji - id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-party -pub const ActivityParty = struct { - /// Used to show the party's current and maximum size - size: ?[2]i64, - /// The id of the party - id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-assets -pub const ActivityAssets = struct { - /// Text displayed when hovering over the large image of the activity - large_text: ?[]const u8, - /// Text displayed when hovering over the small image of the activity - small_text: ?[]const u8, - /// The id for a large asset of the activity, usually a snowflake - large_image: ?[]const u8, - /// The id for a small asset of the activity, usually a snowflake - small_image: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-secrets -pub const ActivitySecrets = struct { - /// The secret for joining a party - join: ?[]const u8, - /// The secret for spectating a game - spectate: ?[]const u8, - /// The secret for a specific instanced match - match: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-buttons -pub const ActivityButton = struct { - /// The text shown on the button (1-32 characters) - label: []const u8, - /// The url opened when clicking the button (1-512 characters) - url: []const u8, -}; - -pub const Overwrite = struct { - /// Either 0 (role) or 1 (member) - type: OverwriteTypes, - /// Role or user id - id: Snowflake, - /// Permission bit set - allow: ?[]const u8, - /// Permission bit set - deny: ?[]const u8, -}; - -/// inherits -pub const MemberWithUser = struct { - /// Whether the user is deafened in voice channels - deaf: ?bool, - /// Whether the user is muted in voice channels - mute: ?bool, - /// Whether the user has not yet passed the guild's Membership Screening requirements - pending: ?bool, - /// This users guild nickname - nick: ?[]const u8, - /// The members custom avatar for this server. - avatar: ?[]const u8, - /// Array of role object ids - roles: [][]const u8, - /// When the user joined the guild - joined_at: []const u8, - /// When the user started boosting the guild - premium_since: ?[]const u8, - /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. - permissions: ?[]const u8, - /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out - communication_disabled_until: ?[]const u8, - /// Guild member flags - flags: isize, - /// data for the member's guild avatar decoration - avatar_decoration_data: ?AvatarDecorationData, - /// The user object for this member - user: User, -}; - -/// TODO: fix this -pub const MessageComponent = isize; - -/// https://discord.com/developers/docs/resources/channel#message-object -pub const Message = struct { - /// id of the message - id: Snowflake, - /// id of the channel the message was sent in - channel_id: Snowflake, - /// - /// id of the guild the message was sent in - /// Note: For MESSAGE_CREATE and MESSAGE_UPDATE events, the message object may not contain a guild_id or member field since the events are sent directly to the receiving user and the bot who sent the message, rather than being sent through the guild like non-ephemeral messages., - /// - guild_id: ?Snowflake, - /// - /// The author of this message (not guaranteed to be a valid user) - /// Note: The author object follows the structure of the user object, but is only a valid user in the case where the message is generated by a user or bot user. If the message is generated by a webhook, the author object corresponds to the webhook's id, username, and avatar. You can tell if a message is generated by a webhook by checking for the webhook_id on the message object., - /// - author: User, - /// - /// Member properties for this message's author - /// Note: The member object exists in `MESSAGE_CREATE` and `MESSAGE_UPDATE` events from text-based guild channels. This allows bots to obtain real-time member data without requiring bots to store member state in memory., - /// - member: ?Member, - /// Contents of the message - content: ?[]const u8, - /// When this message was sent - timestamp: []const u8, - /// When this message was edited (or null if never) - edited_timestamp: ?[]const u8, - /// Whether this was a TTS message - tts: bool, - /// Whether this message mentions everyone - mention_everyone: bool, - /// - /// Users specifically mentioned in the message - /// Note: The user objects in the mentions array will only have the partial member field present in `MESSAGE_CREATE` and `MESSAGE_UPDATE` events from text-based guild channels., - /// - mentions: []User, - /// Roles specifically mentioned in this message - mention_roles: []?[]const u8, - /// - /// Channels specifically mentioned in this message - /// Note: Not all channel mentions in a message will appear in `mention_channels`. Only textual channels that are visible to everyone in a discoverable guild will ever be included. Only crossposted messages (via Channel Following) currently include `mention_channels` at all. If no mentions in the message meet these requirements, this field will not be sent., - /// - mention_channels: []?ChannelMention, - /// Any attached files - attachments: []Attachment, - /// Any embedded content - embeds: []Embed, - /// Reactions to the message - reactions: []?Reaction, - /// Used for validating a message was sent - nonce: ?union(enum) { - int: isize, - string: []const u8, - }, - /// Whether this message is pinned - pinned: bool, - /// If the message is generated by a webhook, this is the webhook's id - webhook_id: ?Snowflake, - /// Type of message - type: MessageTypes, - /// Sent with Rich Presence-related chat embeds - activity: ?MessageActivity, - /// Sent with Rich Presence-related chat embeds - application: ?Partial(Application), - /// if the message is an Interaction or application-owned webhook, this is the id of the application - application_id: ?Snowflake, - /// Data showing the source of a crosspost, channel follow add, pin, or reply message - message_reference: ?Omit(MessageReference, .{"failIfNotExists"}), - /// Message flags combined as a bitfield - flags: ?MessageFlags, - /// - /// The stickers sent with the message (bots currently can only receive messages with stickers, not send) - /// @deprecated - /// - stickers: []?Sticker, - /// - /// The message associated with the `message_reference` - /// Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted., - /// TAKES A POINTER - referenced_message: ?*Message, - /// The message associated with the `message_reference`. This is a minimal subset of fields in a message (e.g. `author` is excluded.) - message_snapshots: []?MessageSnapshot, - /// sent if the message is sent as a result of an interaction - interaction_metadata: ?MessageInteractionMetadata, - /// - /// Sent if the message is a response to an Interaction - /// - /// @deprecated Deprecated in favor of {@link interaction_metadata}; - /// - interaction: ?MessageInteraction, - /// The thread that was started from this message, includes thread member object - thread: ?Omit(Channel, .{"member"}), //& { member: ThreadMember };, - /// The components related to this message - components: ?[]MessageComponent, - /// Sent if the message contains stickers - sticker_items: []?StickerItem, - /// A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with `total_message_sent` on parent thread - position: ?isize, - /// The poll object - poll: ?Poll, - /// The call associated with the message - call: ?MessageCall, -}; - -/// https://discord.com/developers/docs/resources/channel#message-call-object -pub const MessageCall = struct { - /// Array of user object ids that participated in the call - participants: [][]const u8, - /// Time when call ended - ended_timestamp: []const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#channel-mention-object -pub const ChannelMention = struct { - /// id of the channel - id: Snowflake, - /// id of the guild containing the channel - guild_id: Snowflake, - /// The type of channel - type: isize, - /// The name of the channel - name: []const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#reaction-object -pub const Reaction = struct { - /// Total isize of times this emoji has been used to react (including super reacts) - count: isize, - /// - count_details: ReactionCountDetails, - /// Whether the current user reacted using this emoji - me: bool, - /// - me_burst: bool, - /// Emoji information - emoji: Partial(Emoji), - /// HEX colors used for super reaction - burst_colors: [][]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#get-reactions-reaction-types -pub const ReactionType = enum { - Normal, - Burst, -}; - -/// https://discord.com/developers/docs/resources/channel#reaction-count-details-object -pub const ReactionCountDetails = struct { - /// Count of super reactions - burst: isize, - /// - normal: isize, -}; - -/// https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure -pub const MessageActivity = struct { - /// Type of message activity - type: MessageActivityTypes, - /// `party_id` from a Rich Presence event - party_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure -pub const MessageReference = struct { - /// Type of reference - type: ?MessageReferenceType, - /// id of the originating message - message_id: ?Snowflake, - /// - /// id of the originating message's channel - /// Note: `channel_id` is optional when creating a reply, but will always be present when receiving an event/response that includes this data model., - /// - channel_id: ?Snowflake, - /// id of the originating message's guild - guild_id: ?Snowflake, - /// When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true - fail_if_not_exists: bool, -}; - -/// https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-types -pub const MessageReferenceType = enum { - /// - /// A standard reference used by replies. - /// - /// @remarks - /// When the type is set to this value, the field referenced_message on the message will be present - /// - Default, - /// - /// Reference used to point to a message at a point in time. - /// - /// @remarks - /// When the type is set to this value, the field message_snapshot on the message will be present - /// - /// This value can only be used for basic messages; - /// i.e. messages which do not have strong bindings to a non global entity. - /// Thus we support only messages with `DEFAULT` or `REPLY` types, but disallowed if there are any polls, calls, or components. - /// - Forward, -}; - -/// https://discord.com/developers/docs/resources/channel#message-snapshot-object-message-snapshot-structure -pub const MessageSnapshot = struct { - /// https://discord.com/developers/docs/resources/channel#message-object - /// Minimal subset of fields in the forwarded message - message: struct { - content: ?[]const u8, - timestamp: []const u8, - edited_timestamp: ?[]const u8, - mentions: []struct { - username: []const u8, - global_name: ?[]const u8, - locale: ?[]const u8, - flags: ?isize, - premium_type: ?PremiumTypes, - public_flags: ?isize, - accent_color: ?isize, - id: Snowflake, - discriminator: []const u8, - avatar: ?[]const u8, - bot: ?bool, - system: ?bool, - mfa_enabled: ?bool, - verified: ?bool, - email: ?[]const u8, - banner: ?[]const u8, - avatar_decoration_data: ?AvatarDecorationData, - member: ?Partial(Member), - }, - mention_roles: []?[]const u8, - type: MessageTypes, - flags: ?MessageFlags, - stickers: []?Sticker, - components: ?[]MessageComponent, - sticker_items: []?StickerItem, - attachments: []Attachment, - embeds: []Embed, - }, -}; - -/// https://discord.com/developers/docs/resources/poll#poll-object -pub const Poll = struct { - /// The question of the poll. Only `text` is supported. - question: PollMedia, - /// Each of the answers available in the poll. There is a maximum of 10 answers per poll. - answers: []PollAnswer, - /// - /// The time when the poll ends. - /// - /// @remarks - /// `expiry` is marked as nullable to support non-expiring polls in the future, but all polls have an expiry currently. - /// - expiry: ?[]const u8, - /// Whether a user can select multiple answers - allow_multiselect: bool, - /// The layout type of the poll - layout_type: PollLayoutType, - /// - /// The results of the poll - /// - /// @remarks - /// This value will not be sent by discord under specific conditions where they don't fetch them on their backend. When this value is missing it should be interpreted as "Unknown results" and not as "No results" - ///The results may not be totally accurate while the poll has not ended. When it ends discord will re-calculate all the results and set {@link PollResult.is_finalized}; to true - /// - results: ?PollResult, -}; - -/// https://discord.com/developers/docs/resources/poll#layout-type -pub const PollLayoutType = enum(u4) { - /// The default layout - Default = 1, -}; - -/// https://discord.com/developers/docs/resources/poll#poll-media-object -pub const PollMedia = struct { - /// - /// The text of the field - /// - /// @remarks - /// `text` should always be non-null for both questions and answers, but this is subject to changes. - /// The maximum length of `text` is 300 for the question, and 55 for any answer. - /// - text: ?[]const u8, - /// - /// The emoji of the field - /// - /// @remarks - /// When creating a poll answer with an emoji, one only needs to send either the `id` (custom emoji) or `name` (default emoji) as the only field. - /// - emoji: ?Partial(Emoji), -}; - -/// https://discord.com/developers/docs/resources/poll#poll-answer-object -pub const PollAnswer = struct { - /// - /// The id of the answer - /// - /// @remarks - ///This id labels each answer. It starts at 1 and goes up sequentially. recommend against depending on this value as is a implementation detail. - /// - answer_id: isize, - /// The data of the answer - poll_media: PollMedia, -}; - -pub const PollAnswerCount = struct { - ///The {@link PollAnswer.answer_id | answer_id}; - id: isize, - /// The isize of votes for this answer - count: isize, - /// Whether the current user voted for this answer - me_voted: bool, -}; - -/// https://discord.com/developers/docs/resources/poll#poll-results-object -pub const PollResult = struct { - /// Whether the votes have been precisely counted - is_finalized: bool, - /// The counts for each answer - answer_counts: []PollAnswerCount, -}; - -/// https://discord.com/developers/docs/resources/poll#get-answer-voters-response-body -pub const GetAnswerVotesResponse = struct { - /// Users who voted for this answer - users: []User, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add -pub const PollVoteAdd = struct { - /// ID of the user. Usually a snowflake - user_id: Snowflake, - /// ID of the channel. Usually a snowflake - channel_id: Snowflake, - /// ID of the message. Usually a snowflake - message_id: Snowflake, - /// ID of the guild. Usually a snowflake - guild_id: ?Snowflake, - /// ID of the answer. - answer_id: isize, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove -pub const PollVoteRemove = struct { - /// ID of the user. Usually a snowflake - user_id: Snowflake, - /// ID of the channel. Usually a snowflake - channel_id: Snowflake, - /// ID of the message. Usually a snowflake - message_id: Snowflake, - /// ID of the guild. Usually a snowflake - guild_id: ?Snowflake, - /// ID of the answer. - answer_id: isize, -}; - -/// https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-structure -pub const Sticker = struct { - /// [Id of the sticker](https://discord.com/developers/docs/reference#image-formatting) - id: Snowflake, - /// Id of the pack the sticker is from - pack_id: ?Snowflake, - /// Name of the sticker - name: []const u8, - /// Description of the sticker - description: []const u8, - /// a unicode emoji representing the sticker's expression - tags: []const u8, - /// [type of sticker](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types) - type: StickerTypes, - /// [Type of sticker format](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types) - format_type: StickerFormatTypes, - /// Whether or not the sticker is available - available: ?bool, - /// Id of the guild that owns this sticker - guild_id: ?Snowflake, - /// The user that uploaded the sticker - user: ?User, - /// A sticker's sort order within a pack - sort_value: ?isize, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object-message-interaction-structure -pub const MessageInteraction = struct { - /// Id of the interaction - id: Snowflake, - /// The type of interaction - type: InteractionTypes, - /// The name of the ApplicationCommand including the name of the subcommand/subcommand group - name: []const u8, - /// The user who invoked the interaction - user: User, - /// The member who invoked the interaction in the guild - member: ?Partial(Member), -}; - -/// https://discord.com/developers/docs/resources/channel#message-interaction-metadata-object-message-interaction-metadata-structure -pub const MessageInteractionMetadata = struct { - /// Id of the interaction - id: Snowflake, - /// The type of interaction - type: InteractionTypes, - /// User who triggered the interaction - user: User, - /// IDs for installation context(s) related to an interaction - authorizing_integration_owners: Partial(AutoArrayHashMap(ApplicationIntegrationType, []const u8)), - /// ID of the original response message, present only on follow-up messages - original_response_message_id: ?Snowflake, - /// ID of the message that contained interactive component, present only on messages created from component interactions - interacted_message_id: ?Snowflake, - /// Metadata for the interaction that was used to open the modal, present only on modal submit interactions - /// TAKES A POINTER - triggering_interaction_metadata: ?*MessageInteractionMetadata, -}; - -/// https://discord.com/developers/docs/resources/sticker#sticker-item-object-sticker-item-structure -pub const StickerItem = struct { - /// Id of the sticker - id: Snowflake, - /// Name of the sticker - name: []const u8, - /// [Type of sticker format](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types) - format_type: StickerFormatTypes, -}; - -/// https://discord.com/developers/docs/resources/sticker#sticker-pack-object-sticker-pack-structure -pub const StickerPack = struct { - /// id of the sticker pack - id: Snowflake, - /// the stickers in the pack - stickers: []Sticker, - /// name of the sticker pack - name: []const u8, - /// id of the pack's SKU - sku_id: Snowflake, - /// id of a sticker in the pack which is shown as the pack's icon - cover_sticker_id: ?Snowflake, - /// description of the sticker pack - description: []const u8, - /// id of the sticker pack's [banner image](https://discord.com/developers/docs/reference#image-formatting) - banner_asset_id: ?Snowflake, -}; - -pub const Interaction = struct { - /// Id of the interaction - id: Snowflake, - /// Id of the application this interaction is for - application_id: Snowflake, - /// The type of interaction - type: InteractionTypes, - /// Guild that the interaction was sent from - guild: ?Partial(Guild), - /// The guild it was sent from - guild_id: ?Snowflake, - /// The channel it was sent from - channel: Partial(Channel), - /// - /// The ID of channel it was sent from - /// - /// @remarks - /// It is recommended that you begin using this channel field to identify the source channel of the interaction as they may deprecate the existing channel_id field in the future. - /// - channel_id: ?Snowflake, - /// Guild member data for the invoking user, including permissions - member: ?InteractionMember, - /// User object for the invoking user, if invoked in a DM - user: ?User, - /// A continuation token for responding to the interaction - token: []const u8, - /// Read-only property, always `1` - version: 1, - /// For the message the button was attached to - message: ?Message, - /// the command data payload - data: ?InteractionData, - /// The selected language of the invoking user - locale: ?[]const u8, - /// The guild's preferred locale, if invoked in a guild - guild_locale: ?[]const u8, - /// The computed permissions for a bot or app in the context of a specific interaction (including channel overwrites) - app_permissions: []const u8, - /// For monetized apps, any entitlements for the invoking user, representing access to premium SKUs - entitlements: []Entitlement, - /// Mapping of installation contexts that the interaction was authorized for to related user or guild IDs. - authorizing_integration_owners: Partial(AutoArrayHashMap(ApplicationIntegrationType, []const u8)), - /// Context where the interaction was triggered from - context: ?InteractionContextType, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-response-object -pub const InteractionCallbackResponse = struct { - /// The interaction object associated with the interaction response - interaction: InteractionCallback, - /// The resource that was created by the interaction response. - resource: ?InteractionResource, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-object -pub const InteractionCallback = struct { - /// ID of the interaction - id: Snowflake, - /// Interaction type - type: InteractionTypes, - /// Instance ID of the Activity if one was launched or joined - activity_instance_id: ?Snowflake, - /// ID of the message that was created by the interaction - response_message_id: ?Snowflake, - /// Whether or not the message is in a loading state - response_message_loading: ?bool, - /// Whether or not the response message was ephemeral - response_message_ephemeral: ?bool, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-resource-object -pub const InteractionResource = struct { - type: InteractionResponseTypes, - /// - /// Represents the Activity launched by this interaction. - /// - /// @remarks - /// Only present if type is `LAUNCH_ACTIVITY`. - /// - activity_instance: ?ActivityInstanceResource, - /// - /// Message created by the interaction. - /// - /// @remarks - /// Only present if type is either `CHANNEL_MESSAGE_WITH_SOURCE` or `UPDATE_MESSAGE`. - /// - message: ?Message, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-activity-instance-resource -pub const ActivityInstanceResource = struct { - /// Instance ID of the Activity if one was launched or joined. - id: Snowflake, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-member-object -pub const InteractionMember = struct { - /// Whether the user is deafened in voice channels - deaf: ?bool, - /// Whether the user is muted in voice channels - mute: ?bool, - /// Whether the user has not yet passed the guild's Membership Screening requirements - pending: ?bool, - /// This users guild nickname - nick: ?[]const u8, - /// The members custom avatar for this server. - avatar: ?[]const u8, - /// Array of role object ids - roles: [][]const u8, - /// When the user joined the guild - joined_at: []const u8, - /// When the user started boosting the guild - premium_since: ?[]const u8, - /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out - communication_disabled_until: ?[]const u8, - /// Guild member flags - flags: isize, - /// data for the member's guild avatar decoration - avatar_decoration_data: ?AvatarDecorationData, - /// The user object for this member - user: User, - /// Total permissions of the member in the channel, including overwrites, returned when in the interaction object - permissions: []const u8, -}; - -pub const InteractionData = struct { - /// The type of component - component_type: ?MessageComponentTypes, - /// The custom id provided for this component. - custom_id: ?Snowflake, - /// The components if its a Modal Submit interaction. - components: ?[]MessageComponent, - /// The values chosen by the user. - values: []?[]const u8, - /// The Id of the invoked command - id: Snowflake, - /// The name of the invoked command - name: []const u8, - /// the type of the invoked command - type: ApplicationCommandTypes, - /// Converted users + roles + channels + attachments - resolved: ?struct { - /// The Ids and Message objects - messages: ?AutoArrayHashMap([]const u8, Message), - /// The Ids and User objects - users: ?AutoArrayHashMap([]const u8, User), - /// The Ids and partial Member objects - members: ?AutoArrayHashMap([]const u8, Omit(InteractionMember, .{ "user", "deaf", "mute" })), - /// The Ids and Role objects - roles: ?AutoArrayHashMap([]const u8, Role), - /// The Ids and partial Channel objects - channels: ?AutoArrayHashMap([]const u8, struct { - id: Snowflake, - type: ChannelTypes, - name: ?[]const u8, - permissions: ?[]const u8, - }), - /// The ids and attachment objects - attachments: AutoArrayHashMap([]const u8, Attachment), - }, - /// The params + values from the user - 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 - guild_id: ?Snowflake, -}; - -pub const InteractionDataOption = struct { - /// Name of the parameter - name: []const u8, - /// Value of application command option type - type: ApplicationCommandOptionTypes, - /// Value of the option resulting from user input - value: ?union(enum) { - string: []const u8, - bool: bool, - integer: isize, - }, - /// Present if this option is a group or subcommand - options: []?InteractionDataOption, - /// `true` if this option is the currently focused option for autocomplete - focused: ?bool, -}; - -pub const ListActiveThreads = struct { - /// The active threads - threads: []Channel, - /// A thread member object for each returned thread the current user has joined - members: []ThreadMember, -}; - -pub const ListArchivedThreads = struct { - /// The active threads - threads: []Channel, - /// A thread member object for each returned thread the current user has joined - members: []ThreadMember, - /// Whether there are potentially additional threads that could be returned on a subsequent call - has_more: bool, -}; - -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, - /// 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 - members: []ThreadMember, -}; - -/// https://discord.com/developers/docs/resources/audit-log#audit-log-object -pub const AuditLog = struct { - /// List of webhooks found in the audit log - webhooks: []Webhook, - /// List of users found in the audit log - users: []User, - /// List of audit log entries, sorted from most to least recent - audit_log_entries: []AuditLogEntry, - /// List of partial integration objects - integrations: []Partial(Integration), - /// - /// List of threads found in the audit log. - /// Threads referenced in `THREAD_CREATE` and `THREAD_UPDATE` events are included in the threads map since archived threads might not be kept in memory by clients. - /// - threads: []Channel, - /// List of guild scheduled events found in the audit log - guild_scheduled_events: []?ScheduledEvent, - /// List of auto moderation rules referenced in the audit log - auto_moderation_rules: []?AutoModerationRule, - /// List of application commands referenced in the audit log - application_commands: []ApplicationCommand, -}; - -/// https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object -pub const AutoModerationRule = struct { - /// The id of this rule - id: Snowflake, - /// The guild id of the rule - guild_id: Snowflake, - /// The name of the rule - name: []const u8, - /// The id of the user who created this rule. - creator_id: Snowflake, - /// Indicates in what event context a rule should be checked. - event_type: AutoModerationEventTypes, - /// The type of trigger for this rule - trigger_type: AutoModerationTriggerTypes, - /// The metadata used to determine whether a rule should be triggered. - trigger_metadata: AutoModerationRuleTriggerMetadata, - /// Actions which will execute whenever a rule is triggered. - actions: []AutoModerationAction, - /// Whether the rule is enabled. - enabled: bool, - /// The role ids that are whitelisted. Max 20. - exempt_roles: [][]const u8, - /// The channel ids that are whitelisted. Max 50. - exempt_channels: [][]const u8, -}; - -pub const AutoModerationEventTypes = enum(u4) { - /// When a user sends a message - MessageSend = 1, - /// Wen a member edits their profile - MemberUpdate, -}; - -pub const AutoModerationTriggerTypes = enum(u4) { - /// Check if content contains words from a user defined list of keywords. Max 6 per guild - Keyword = 1, - /// Check if content represents generic spam. Max 1 per guild - Spam = 3, - /// Check if content contains words from internal pre-defined word sets. Max 1 per guild - KeywordPreset, - /// Check if content contains more unique mentions than allowed. Max 1 per guild - MentionSpam, - /// Check if member profile contains words from a user defined list of keywords. Max 1 per guild - MemberProfile, -}; - -pub const AutoModerationRuleTriggerMetadata = struct { - /// - /// The keywords needed to match. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MemberProfile};. - /// - /// Can have up to 1000 elements in the array and each []const u8 can have up to 60 characters - /// - keyword_filter: []?[]const u8, - /// - /// Regular expression patterns which will be matched against content. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MemberProfile};. - /// - /// Can have up to 10 elements in the array and each []const u8 can have up to 260 characters - /// - regex_patterns: [][]const u8, - /// - /// The pre-defined lists of words to match from. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.KeywordPreset};. - /// - presets: []?AutoModerationRuleTriggerMetadataPresets, - /// - /// The substrings which will exempt from triggering the preset trigger type. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.Keyword};, {@link AutoModerationTriggerTypes.KeywordPreset}; and {@link AutoModerationTriggerTypes.MemberProfile};. - /// - /// 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, - /// - /// Total isize of mentions (role & user) allowed per message. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.MentionSpam};. - /// - /// Maximum of 50 - /// - mention_total_limit: ?isize, - /// - /// Whether to automatically detect mention raids. - /// - /// @remarks - /// Only present with {@link AutoModerationTriggerTypes.MentionSpam};. - /// - mention_raid_protection_enabled: ?bool, -}; - -pub const AutoModerationRuleTriggerMetadataPresets = enum(u4) { - /// Words that may be considered forms of swearing or cursing - Profanity = 1, - /// Words that refer to sexually explicit behavior or activity - SexualContent, - /// Personal insults or words that may be considered hate speech - Slurs, -}; - -pub const AutoModerationAction = struct { - /// The type of action to take when a rule is triggered - type: AutoModerationActionType, - /// additional metadata needed during execution for this specific action type - metadata: AutoModerationActionMetadata, -}; - -pub const AutoModerationActionType = enum(u4) { - /// Blocks the content of a message according to the rule - BlockMessage = 1, - /// Logs user content to a specified channel - SendAlertMessage, - /// - /// Times out user for specified duration - /// - /// @remarks - /// A timeout action can only be set up for {@link AutoModerationTriggerTypes.Keyword}; and {@link AutoModerationTriggerTypes.MentionSpam}; rules. - /// - /// The `MODERATE_MEMBERS` permission is required to use the timeout action type. - /// - Timeout, - /// prevents a member from using text, voice, or other interactions - BlockMemberInteraction, -}; - -pub const AutoModerationActionMetadata = struct { - /// The id of channel to which user content should be logged. Only in ActionType.SendAlertMessage - channel_id: ?Snowflake, - /// Additional explanation that will be shown to members whenever their message is blocked. Maximum of 150 characters. Only supported for AutoModerationActionType.BlockMessage - custom_message: ?[]const u8, - /// Timeout duration in seconds maximum of 2419200 seconds (4 weeks). Only supported for TriggerType.Keyword && Only in ActionType.Timeout - duration_seconds: ?isize, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution-auto-moderation-action-execution-event-fields -pub const AutoModerationActionExecution = struct { - /// The id of the guild - guild_id: Snowflake, - /// The id of the rule that was executed - rule_id: Snowflake, - /// The id of the user which generated the content which triggered the rule - user_id: Snowflake, - /// The content from the user - content: []const u8, - /// Action which was executed - action: AutoModerationAction, - /// The trigger type of the rule that was executed. - rule_trigger_type: AutoModerationTriggerTypes, - /// The id of the channel in which user content was posted - channel_id: ?Snowflake, - /// The id of the message. Will not exist if message was blocked by automod or content was not part of any message - message_id: ?Snowflake, - /// The id of any system auto moderation messages posted as a result of this action - alert_system_message_id: ?Snowflake, - /// The word or phrase that triggerred the rule. - matched_keyword: ?[]const u8, - /// The substring in content that triggered the rule - matched_content: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure -pub const AuditLogEntry = struct { - /// ID of the affected entity (webhook, user, role, etc.) - target_id: ?Snowflake, - /// Changes made to the `target_id` - /// TODO: change this - changes: []?AuditLogChange(noreturn), - /// User or app that made the changes - user_id: ?Snowflake, - /// ID of the entry - id: Snowflake, - /// Type of action that occurred - action_type: AuditLogEvents, - /// Additional info for certain event types - options: ?OptionalAuditEntryInfo, - /// Reason for the change (1-512 characters) - reason: ?[]const u8, -}; - -pub fn AuditLogChange(comptime T: type) type { - return T; -} - -/// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info -pub const OptionalAuditEntryInfo = struct { - /// - /// ID of the app whose permissions were targeted. - /// - /// Event types: `APPLICATION_COMMAND_PERMISSION_UPDATE`, - /// - application_id: ?Snowflake, - /// - /// Name of the Auto Moderation rule that was triggered. - /// - /// Event types: `AUTO_MODERATION_BLOCK_MESSAGE`, `AUTO_MODERATION_FLAG_TO_CHANNEL`, `AUTO_MODERATION_USER_COMMUNICATION_DISABLED`, - /// - auto_moderation_rule_name: ?[]const u8, - /// - /// Trigger type of the Auto Moderation rule that was triggered. - /// - /// Event types: `AUTO_MODERATION_BLOCK_MESSAGE`, `AUTO_MODERATION_FLAG_TO_CHANNEL`, `AUTO_MODERATION_USER_COMMUNICATION_DISABLED`, - /// - auto_moderation_rule_trigger_type: ?[]const u8, - /// - /// Channel in which the entities were targeted. - /// - /// Event types: `MEMBER_MOVE`, `MESSAGE_PIN`, `MESSAGE_UNPIN`, `MESSAGE_DELETE`, `STAGE_INSTANCE_CREATE`, `STAGE_INSTANCE_UPDATE`, `STAGE_INSTANCE_DELETE`, - /// - channel_id: ?Snowflake, - /// - /// isize of entities that were targeted. - /// - /// Event types: `MESSAGE_DELETE`, `MESSAGE_BULK_DELETE`, `MEMBER_DISCONNECT`, `MEMBER_MOVE`, - /// - count: ?[]const u8, - /// - /// isize of days after which inactive members were kicked. - /// - /// Event types: `MEMBER_PRUNE`, - /// - delete_member_days: ?[]const u8, - /// - /// ID of the overwritten entity. - /// - /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, - /// - id: ?Snowflake, - /// - /// isize of members removed by the prune. - /// - /// Event types: `MEMBER_PRUNE`, - /// - members_removed: ?[]const u8, - /// - /// ID of the message that was targeted. - /// - /// Event types: `MESSAGE_PIN`, `MESSAGE_UNPIN`, `STAGE_INSTANCE_CREATE`, `STAGE_INSTANCE_UPDATE`, `STAGE_INSTANCE_DELETE`, - /// - message_id: ?Snowflake, - /// - /// Name of the role if type is "0" (not present if type is "1"). - /// - /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, - /// - role_name: ?[]const u8, - /// - /// Type of overwritten entity - "0", for "role", or "1" for "member". - /// - /// Event types: `CHANNEL_OVERWRITE_CREATE`, `CHANNEL_OVERWRITE_UPDATE`, `CHANNEL_OVERWRITE_DELETE`, - /// - type: ?[]const u8, - /// - /// The type of integration which performed the action - /// - /// Event types: `MEMBER_KICK`, `MEMBER_ROLE_UPDATE`, - /// - integration_type: ?[]const u8, -}; - -pub const ScheduledEvent = struct { - /// the id of the scheduled event - id: Snowflake, - /// the guild id which the scheduled event belongs to - guild_id: Snowflake, - /// the channel id in which the scheduled event will be hosted if specified - channel_id: ?Snowflake, - /// the id of the user that created the scheduled event - creator_id: ?Snowflake, - /// the name of the scheduled event - name: []const u8, - /// the description of the scheduled event - description: ?[]const u8, - /// the time the scheduled event will start - scheduled_start_time: []const u8, - /// the time the scheduled event will end if it does end. - scheduled_end_time: ?[]const u8, - /// the privacy level of the scheduled event - privacy_level: ScheduledEventPrivacyLevel, - /// the status of the scheduled event - status: ScheduledEventStatus, - /// the type of hosting entity associated with a scheduled event - entity_type: ScheduledEventEntityType, - /// any additional id of the hosting entity associated with event - entity_id: ?Snowflake, - /// the entity metadata for the scheduled event - entity_metadata: ?ScheduledEventEntityMetadata, - /// the user that created the scheduled event - creator: ?User, - /// the isize of users subscribed to the scheduled event - user_count: ?isize, - /// the cover image hash of the scheduled event - image: ?[]const u8, - /// the definition for how often this event should recur - recurrence_rule: ?ScheduledEventRecurrenceRule, -}; - -pub const ScheduledEventEntityMetadata = struct { - /// location of the event - location: ?[]const u8, -}; - -pub const ScheduledEventRecurrenceRule = struct { - /// Starting time of the recurrence interval - start: []const u8, - /// Ending time of the recurrence interval - end: ?[]const u8, - /// How often the event occurs - frequency: ScheduledEventRecurrenceRuleFrequency, - /// 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, - /// List of specific days within a specific week (1-5) to recur on - by_n_weekday: []?ScheduledEventRecurrenceRuleNWeekday, - /// Set of specific months to recur on - by_month: []?ScheduledEventRecurrenceRuleMonth, - /// Set of specific dates within a month to recur on - by_month_day: []?isize, - /// Set of days within a year to recur on (1-364) - by_year_day: []?isize, - /// The total amount of times that the event is allowed to recur before stopping - count: ?isize, -}; - -pub const ScheduledEventRecurrenceRuleFrequency = enum { - Yearly, - Monthly, - Weekly, - Daily, -}; - -pub const ScheduledEventRecurrenceRuleWeekday = enum { - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - Sunday, -}; - -pub const ScheduledEventRecurrenceRuleNWeekday = struct { - /// The week to reoccur on. 1 - 5 - n: isize, - /// The day within the week to reoccur on - day: ScheduledEventRecurrenceRuleWeekday, -}; - -pub const ScheduledEventRecurrenceRuleMonth = enum(u4) { - January = 1, - February, - March, - April, - May, - June, - July, - August, - September, - October, - November, - December, -}; - -/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot -pub const GetGatewayBot = struct { - /// The WSS URL that can be used for connecting to the gateway - url: []const u8, - /// The recommended isize of shards to use when connecting - shards: isize, - /// Information on the current session start limit - session_start_limit: SessionStartLimit, -}; - -/// https://discord.com/developers/docs/topics/gateway#session-start-limit-object -pub const SessionStartLimit = struct { - /// The total isize of session starts the current user is allowed - total: isize, - /// The remaining isize of session starts the current user is allowed - remaining: isize, - /// The isize of milliseconds after which the limit resets - reset_after: isize, - /// The isize of identify requests allowed per 5 seconds - max_concurrency: isize, -}; - -/// https://discord.com/developers/docs/resources/invite#invite-metadata-object -pub const InviteMetadata = struct { - /// The type of invite - type: InviteType, - /// The invite code (unique Id) - code: []const u8, - /// The guild this invite is for - guild: ?Partial(Guild), - /// The channel this invite is for - channel: ?Partial(Channel), - /// The user who created the invite - inviter: ?User, - /// The type of target for this voice channel invite - target_type: ?TargetTypes, - /// The target user for this invite - target_user: ?User, - /// The embedded application to open for this voice channel embedded application invite - target_application: ?Partial(Application), - /// Approximate count of online members (only present when target_user is set) - approximate_presence_count: ?isize, - /// Approximate count of total members - approximate_member_count: ?isize, - /// The expiration date of this invite, returned from the `GET /invites/` endpoint when `with_expiration` is `true` - expires_at: ?[]const u8, - /// Stage instance data if there is a public Stage instance in the Stage channel this invite is for - stage_instance: ?InviteStageInstance, - /// guild scheduled event data - guild_scheduled_event: ?ScheduledEvent, - /// isize of times this invite has been used - uses: isize, - /// Max isize of times this invite can be used - max_uses: isize, - /// Duration (in seconds) after which the invite expires - max_age: isize, - /// Whether this invite only grants temporary membership - temporary: bool, - /// When this invite was created - created_at: []const u8, -}; - -/// https://discord.com/developers/docs/resources/invite#invite-object -pub const Invite = struct { - /// The type of invite - type: InviteType, - /// The invite code (unique Id) - code: []const u8, - /// The guild this invite is for - guild: ?Partial(Guild), - /// The channel this invite is for - channel: ?Partial(Channel), - /// The user who created the invite - inviter: ?User, - /// The type of target for this voice channel invite - target_type: ?TargetTypes, - /// The target user for this invite - target_user: ?User, - /// The embedded application to open for this voice channel embedded application invite - target_application: ?Partial(Application), - /// Approximate count of online members (only present when target_user is set) - approximate_presence_count: ?isize, - /// Approximate count of total members - approximate_member_count: ?isize, - /// The expiration date of this invite, returned from the `GET /invites/` endpoint when `with_expiration` is `true` - expires_at: ?[]const u8, - /// Stage instance data if there is a public Stage instance in the Stage channel this invite is for - stage_instance: ?InviteStageInstance, - /// guild scheduled event data - guild_scheduled_event: ?ScheduledEvent, -}; - -pub const InviteType = enum { - Guild, - GroupDm, - Friend, -}; - -pub const InviteStageInstance = struct { - /// The members speaking in the Stage - members: []Partial(Member), - /// The isize of users in the Stage - participant_count: isize, - /// The isize of users speaking in the Stage - speaker_count: isize, - /// The topic of the Stage instance (1-120 characters) - topic: []const u8, -}; - -/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure -pub const ApplicationCommand = struct { - /// Type of command, defaults to `ApplicationCommandTypes.ChatInput` - type: ?ApplicationCommandTypes, - /// - /// Name of command, 1-32 characters. - /// `ApplicationCommandTypes.ChatInput` command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. - /// If there is a lowercase variant of any letters used, you must use those. - /// Characters with no lowercase variants and/or uncased letters are still allowed. - /// ApplicationCommandTypes.User` and `ApplicationCommandTypes.Message` commands may be mixed case and can include spaces. - /// - name: []const u8, - /// Localization object for `name` field. Values follow the same restrictions as `name` - name_localizations: ?[]const u8, //?Localization, - /// Description for `ApplicationCommandTypes.ChatInput` commands, 1-100 characters. - description: ?[]const u8, - /// 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, - /// Set of permissions represented as a bit set - default_member_permissions: ?[]const u8, - /// - /// Installation contexts where the command is available - /// - /// @remarks - /// This value is available only for globally-scoped commands - /// Defaults to the application configured contexts - /// - integration_types: []?ApplicationIntegrationType, - /// - /// Interaction context(s) where the command can be used - /// - /// @remarks - /// This value is available only for globally-scoped commands - /// By default, all interaction context types included for new commands. - /// - contexts: []?InteractionContextType, - /// - /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. - /// - /// @deprecated use {@link contexts}; instead - /// - dm_permission: ?bool, - /// Indicates whether the command is age-restricted, defaults to false - nsfw: ?bool, - /// Auto incrementing version identifier updated during substantial record changes - version: ?[]const u8, - /// - ///Determines whether the interaction is handled by the app's interactions handler or by - /// - /// @remarks - /// This can only be set for application commands of type `PRIMARY_ENTRY_POINT` for applications with the `EMBEDDED` flag (i.e. applications that have an Activity). - /// - handler: ?InteractionEntryPointCommandHandlerType, - /// Unique ID of command - id: Snowflake, - /// ID of the parent application - application_id: Snowflake, - /// Guild id of the command, if not global - guild_id: ?Snowflake, -}; - -pub const CreateApplicationCommand = struct { - /// Type of command, defaults to `ApplicationCommandTypes.ChatInput` - type: ?ApplicationCommandTypes, - /// - /// Name of command, 1-32 characters. - /// `ApplicationCommandTypes.ChatInput` command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. - /// If there is a lowercase variant of any letters used, you must use those. - /// Characters with no lowercase variants and/or uncased letters are still allowed. - /// ApplicationCommandTypes.User` and `ApplicationCommandTypes.Message` commands may be mixed case and can include spaces. - /// - name: []const u8, - /// Localization object for `name` field. Values follow the same restrictions as `name` - name_localizations: []const u8, //?Localization, - /// Description for `ApplicationCommandTypes.ChatInput` commands, 1-100 characters. - description: ?[]const u8, - /// 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, - /// Set of permissions represented as a bit set - default_member_permissions: ?[]const u8, - /// - /// Installation contexts where the command is available - /// - /// @remarks - /// This value is available only for globally-scoped commands - /// Defaults to the application configured contexts - /// - integration_types: []?ApplicationIntegrationType, - /// - /// Interaction context(s) where the command can be used - /// - /// @remarks - /// This value is available only for globally-scoped commands - /// By default, all interaction context types included for new commands. - /// - contexts: []?InteractionContextType, - /// - /// Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. - /// - /// @deprecated use {@link contexts}; instead - /// - dm_permission: ?bool, - /// Indicates whether the command is age-restricted, defaults to false - nsfw: ?bool, - /// Auto incrementing version identifier updated during substantial record changes - version: ?[]const u8, - /// - ///Determines whether the interaction is handled by the app's interactions handler or by - /// - /// @remarks - /// This can only be set for application commands of type `PRIMARY_ENTRY_POINT` for applications with the `EMBEDDED` flag (i.e. applications that have an Activity). - /// - handler: ?InteractionEntryPointCommandHandlerType, -}; - -pub const InteractionEntryPointCommandHandlerType = enum(u4) { - /// The app handles the interaction using an interaction token - AppHandler = 1, - /// handles the interaction by launching an Activity and sending a follow-up message without coordinating with the app - LaunchActivity = 2, -}; - -/// https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure -pub const ApplicationCommandOption = struct { - /// Type of option - type: ApplicationCommandOptionTypes, - /// - /// Name of command, 1-32 characters. - /// - /// @remarks - ///This value should be unique within an array of {@link ApplicationCommandOption}; - /// - /// {@link ApplicationCommandTypes.ChatInput | ChatInput}; command names must match the following regex `^[-_\p{L};\p{N};\p{sc=Deva};\p{sc=Thai};]{1,32};$` with the unicode flag set. - /// If there is a lowercase variant of any letters used, you must use those. - /// Characters with no lowercase variants and/or uncased letters are still allowed. - /// - /// {@link ApplicationCommandTypes.User | User}; and {@link ApplicationCommandTypes.Message | Message}; commands may be mixed case and can include spaces. - /// - name: []const u8, - /// Localization object for the `name` field. Values follow the same restrictions as `name` - name_localizations: []const u4, //?Localization, - /// 1-100 character description - description: []const u8, - /// Localization object for the `description` field. Values follow the same restrictions as `description` - description_localizations: ?[]const u8, //?Localization, - /// - /// If the parameter is required or optional. default `false` - /// - /// @remarks - /// Valid in all option types except {@link ApplicationCommandOptionTypes.SubCommand | SubCommand}; and {@link ApplicationCommandOptionTypes.SubCommandGroup | SubCommandGroup}; - /// - required: ?bool, - /// - /// Choices for the option from which the user can choose, max 25 - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8};, {@link ApplicationCommandOptionTypes.Integer | Integer};, or {@link ApplicationCommandOptionTypes.isize | isize}; - /// - /// If you provide an array of choices, they will be the ONLY accepted values for this option - /// - 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, - /// - /// If autocomplete interactions are enabled for this option. - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8};, {@link ApplicationCommandOptionTypes.Integer | Integer};, or {@link ApplicationCommandOptionTypes.isize | isize}; - /// - ///When {@link ApplicationCommandOption.choices | choices}; are provided, this may not be set to true - /// - autocomplete: ?bool, - /// - /// The channels shown will be restricted to these types - /// - /// @remarks - /// Only valid in option of type {@link ApplicationCommandOptionTypes.Channel | Channel}; - /// - channel_types: []?ChannelTypes, - /// - /// The minimum permitted value - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.Integer | Integer}; or {@link ApplicationCommandOptionTypes.isize | isize}; - /// - min_value: ?isize, - /// - /// The maximum permitted value - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.Integer | Integer}; or {@link ApplicationCommandOptionTypes.isize | isize}; - /// - max_value: ?isize, - /// - /// The minimum permitted length, should be in the range of from 0 to 600 - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8}; - /// - min_length: ?isize, - /// - /// The maximum permitted length, should be in the range of from 0 to 600 - /// - /// @remarks - /// Only valid in options of type {@link ApplicationCommandOptionTypes.[]const u8 | []const u8}; - /// - max_length: ?isize, -}; - -/// https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object -pub const ApplicationCommandOptionChoice = struct { - /// 1-100 character choice name - name: []const u8, - /// Localization object for the `name` field. Values follow the same restrictions as `name` - name_localizations: []const u8, //?Localization, - /// Value for the choice, up to 100 characters if []const u8 - value: union(enum) { - string: []const u8, - integer: isize, - }, -}; - -/// https://discord.com/developers/docs/interactions/slash-commands#guildapplicationcommandpermissions -pub const GuildApplicationCommandPermissions = struct { - /// ID of the command or the application ID. When the `id` field is the application ID instead of a command ID, the permissions apply to all commands that do not contain explicit overwrites. - id: Snowflake, - /// ID of the application the command belongs to - application_id: Snowflake, - /// ID of the guild - guild_id: Snowflake, - /// Permissions for the command in the guild, max of 100 - permissions: []ApplicationCommandPermissions, -}; - -/// https://discord.com/developers/docs/interactions/slash-commands#applicationcommandpermissions -pub const ApplicationCommandPermissions = struct { - /// ID of the role, user, or channel. It can also be a permission constant - id: Snowflake, - /// ApplicationCommandPermissionTypes.Role, ApplicationCommandPermissionTypes.User, or ApplicationCommandPermissionTypes.Channel - type: ApplicationCommandPermissionTypes, - /// `true` to allow, `false`, to disallow - permission: bool, -}; - -/// https://discord.com/developers/docs/resources/guild#get-guild-widget-example-get-guild-widget -pub const GuildWidget = struct { - id: Snowflake, - name: []const u8, - instant_invite: []const u8, - channels: []struct { - id: Snowflake, - name: []const u8, - position: isize, - }, - members: []struct { - id: Snowflake, - username: []const u8, - discriminator: []const u8, - avatar: ?[]const u8, - status: []const u8, - avatar_url: []const u8, - }, - presence_count: isize, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-preview-object -pub const GuildPreview = struct { - /// Guild id - id: Snowflake, - /// Guild name (2-100 characters) - name: []const u8, - /// Icon hash - icon: ?[]const u8, - /// Splash hash - splash: ?[]const u8, - /// Discovery splash hash - discovery_splash: ?[]const u8, - /// Custom guild emojis - emojis: []Emoji, - /// Enabled guild features - features: []GuildFeatures, - /// Approximate isize of members in this guild - approximate_member_count: isize, - /// Approximate isize of online members in this guild - approximate_presence_count: isize, - /// The description for the guild, if the guild is discoverable - description: ?[]const u8, - /// Custom guild stickers - stickers: []Sticker, -}; - -/// https://discord.com/developers/docs/resources/channel#followed-channel-object -pub const FollowedChannel = struct { - /// Source message id - channel_id: Snowflake, - /// Created target webhook id - webhook_id: Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#payloads-gateway-payload-structure -pub fn GatewayPayload(comptime T: type) type { - const OBJ = struct { - /// opcode for the payload - op: isize, - /// Event data - d: ?T, - /// Sequence isize, used for resuming sessions and heartbeats - s: ?isize, - /// The event name for this payload - t: ?GatewayDispatchEventNames, - }; - - return OBJ; -} - -/// https://discord.com/developers/docs/topics/gateway#guild-members-chunk -pub const GuildMembersChunk = struct { - /// The id of the guild - guild_id: Snowflake, - /// Set of guild members - members: []MemberWithUser, - /// The chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count) - chunk_index: isize, - /// 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, - /// If passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned members will be here - presences: []?PresenceUpdate, - /// The nonce used in the Guild Members Request - nonce: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway#channel-pins-update -pub const ChannelPinsUpdate = struct { - /// The id of the guild - guild_id: ?Snowflake, - /// The id of the channel - channel_id: Snowflake, - /// The time at which the most recent pinned message was pinned - last_pin_timestamp: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-role-delete -pub const GuildRoleDelete = struct { - /// id of the guild - guild_id: Snowflake, - /// id of the role - role_id: Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-ban-add -pub const GuildBanAddRemove = struct { - /// id of the guild - guild_id: Snowflake, - /// The banned user - user: User, -}; - -/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove -pub const MessageReactionRemove = struct { - /// The id of the user - user_id: Snowflake, - /// The id of the channel - channel_id: Snowflake, - /// The id of the message - message_id: Snowflake, - /// The id of the guild - guild_id: ?Snowflake, - /// The emoji used to react - emoji: Partial(Emoji), - /// The id of the author of this message - message_author_id: ?Snowflake, - /// true if this is a super-reaction - burst: bool, - /// The type of reaction - type: ReactionType, -}; - -/// https://discord.com/developers/docs/events/gateway-events#message-reaction-add -pub const MessageReactionAdd = struct { - /// The id of the user - user_id: Snowflake, - /// The id of the channel - channel_id: Snowflake, - /// The id of the message - message_id: Snowflake, - /// The id of the guild - guild_id: ?Snowflake, - /// The member who reacted if this happened in a guild - member: ?MemberWithUser, - /// The emoji used to react - emoji: Partial(Emoji), - /// The id of the author of this message - message_author_id: ?Snowflake, - /// true if this is a super-reaction - burst: bool, - /// Colors used for super-reaction animation in "#rrggbb" format - burst_colors: []?[]const u8, - /// The type of reaction - type: ReactionType, -}; - -/// https://discord.com/developers/docs/topics/gateway#voice-server-update -pub const VoiceServerUpdate = struct { - /// Voice connection token - token: []const u8, - /// The guild this voice server update is for - guild_id: Snowflake, - /// The voice server host - endpoint: ?[]const u8, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send-voice-channel-effect-send-event-fields -pub const VoiceChannelEffectSend = struct { - /// ID of the channel the effect was sent in - channel_id: Snowflake, - /// ID of the guild the effect was sent in - guild_id: Snowflake, - /// ID of the user who sent the effect - user_id: Snowflake, - /// The emoji sent, for emoji reaction and soundboard effects - emoji: ?Emoji, - /// The type of emoji animation, for emoji reaction and soundboard effects - animation_type: ?VoiceChannelEffectAnimationType, - /// The ID of the emoji animation, for emoji reaction and soundboard effects - animation_id: ?isize, - /// The ID of the soundboard sound, for soundboard effects - sound_id: union(enum) { - string: ?[]const u8, - integer: isize, - }, - /// The volume of the soundboard sound, from 0 to 1, for soundboard effects - sound_volume: ?isize, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send-animation-types -pub const VoiceChannelEffectAnimationType = enum(u4) { - /// A fun animation, sent by a Nitro subscriber - Premium = 0, - /// The standard animation - Basic = 1, -}; - -/// https://discord.com/developers/docs/topics/gateway#invite-create -pub const InviteCreate = struct { - /// The channel the invite is for - channel_id: Snowflake, - /// The unique invite code - code: []const u8, - /// The time at which the invite was created - created_at: []const u8, - /// The guild of the invite - guild_id: ?Snowflake, - /// The user that created the invite - inviter: ?User, - /// How long the invite is valid for (in seconds) - max_age: isize, - /// The maximum isize of times the invite can be used - max_uses: isize, - /// The type of target for this voice channel invite - target_type: TargetTypes, - /// The target user for this invite - target_user: ?User, - /// The embedded application to open for this voice channel embedded application invite - target_application: ?Partial(Application), - /// Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) - temporary: bool, - /// How many times the invite has been used (always will be 0) - uses: isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#hello -pub const Hello = struct { - /// The interval (in milliseconds) the client should heartbeat with - heartbeat_interval: isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#ready -pub const Ready = struct { - /// Gateway version - v: isize, - /// Information about the user including email - user: User, - /// The guilds the user is in - guilds: []UnavailableGuild, - /// Used for resuming connections - session_id: []const u8, - /// Gateway url for resuming connections - resume_gateway_url: []const u8, - /// The shard information associated with this session, if sent when identifying - shard: ?[2]isize, - /// Contains id and flags, only sent to bots - application: ?struct { - name: ?[]const u8, - description: ?[]const u8, - rpc_origins: ?[]?[]const u8, - terms_of_service_url: ?[]const u8, - privacy_policy_url: ?[]const u8, - verify_key: ?[]const u8, - primary_sku_id: ?Snowflake, - slug: ?[]const u8, - icon: ?[]const u8, - bot_public: ?bool, - bot_require_code_grant: ?bool, - owner: ?Partial(User), - team: ?Team, - guild_id: ?Snowflake, - guild: ?Partial(Guild), - cover_image: ?[]const u8, - tags: ?[]?[]const u8, - install_params: ?InstallParams, - integration_types_config: ?Partial(AutoArrayHashMap(ApplicationIntegrationType, ApplicationIntegrationTypeConfiguration)), - custom_install_url: ?[]const u8, - role_connections_verification_url: ?[]const u8, - approximate_guild_count: ?isize, - approximate_user_install_count: ?isize, - bot: ?Partial(User), - redirect_uris: ?[][]const u8, - interactions_endpoint_url: ?[]const u8, - - flags: ApplicationFlags, - id: Snowflake, - }, -}; - -/// https://discord.com/developers/docs/resources/guild#unavailable-guild-object -pub const UnavailableGuild = struct { - unavailable: ?bool, - id: Snowflake, -}; - -/// https://discord.com/developers/docs/events/gateway-events#message-delete-bulk -pub const MessageDeleteBulk = struct { - /// The ids of the messages - ids: []Snowflake, - /// The id of the channel - channel_id: Snowflake, - /// The id of the guild - guild_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/resources/template#template-object-template-structure -pub const Template = struct { - /// The template code (unique Id) - code: []const u8, - /// Template name - name: []const u8, - /// The description for the template - description: ?[]const u8, - /// isize of times this template has been used - usage_count: isize, - /// The Id of the user who created the template - creator_id: Snowflake, - /// The user who created the template - creator: User, - /// When this template was created - created_at: []const u8, - /// When this template was last synced to the source guild - updated_at: []const u8, - /// The Id of the guild this template is based on - source_guild_id: Snowflake, - /// The guild snapshot this template contains - serialized_source_guild: TemplateSerializedSourceGuild, - is_dirty: ?bool, -}; - -pub const TemplateSerializedSourceGuild = null; - -/// https://discord.com/developers/docs/topics/gateway#guild-member-add -pub const GuildMemberAdd = struct { - /// Whether the user is deafened in voice channels - deaf: ?bool, - /// Whether the user is muted in voice channels - mute: ?bool, - /// Whether the user has not yet passed the guild's Membership Screening requirements - pending: ?bool, - /// This users guild nickname - nick: ?[]const u8, - /// The members custom avatar for this server. - avatar: ?[]const u8, - /// Array of role object ids - roles: [][]const u8, - /// When the user joined the guild - joined_at: []const u8, - /// When the user started boosting the guild - premium_since: ?[]const u8, - /// The permissions this member has in the guild. Only present on interaction events and OAuth2 current member fetch. - permissions: ?[]const u8, - /// when the user's timeout will expire and the user will be able to communicate in the guild again (set null to remove timeout), null or a time in the past if the user is not timed out - communication_disabled_until: ?[]const u8, - /// Guild member flags - flags: isize, - /// data for the member's guild avatar decoration - avatar_decoration_data: ?AvatarDecorationData, - /// The user object for this member - user: User, - /// id of the guild - guild_id: Snowflake, -}; - -/// https://discord.com/developers/docs/events/gateway-events#message-delete -pub const MessageDelete = struct { - /// The id of the message - id: Snowflake, - /// The id of the channel - channel_id: Snowflake, - /// The id of the guild - guild_id: ?Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#thread-members-update-thread-members-update-event-fields -pub const ThreadMembersUpdate = struct { - /// The id of the thread - id: Snowflake, - /// The id of the guild - guild_id: Snowflake, - /// The users who were added to the thread - added_members: []?ThreadMember, - /// The id of the users who were removed from the thread - removed_member_ids: []?[]const u8, - /// the approximate isize of members in the thread, capped at 50 - member_count: isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#thread-member-update -pub const ThreadMemberUpdate = struct { - /// The id of the thread - id: Snowflake, - /// The id of the guild - guild_id: Snowflake, - /// The timestamp when the bot joined this thread. - joined_at: []const u8, - /// The flags this user has for this thread. Not useful for bots. - flags: isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-role-create -pub const GuildRoleCreate = struct { - /// The id of the guild - guild_id: Snowflake, - /// The role created - role: Role, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-emojis-update -pub const GuildEmojisUpdate = struct { - /// id of the guild - guild_id: Snowflake, - /// Array of emojis - emojis: []Emoji, -}; - -/// https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update -pub const GuildStickersUpdate = struct { - /// id of the guild - guild_id: Snowflake, - /// Array of sticker - stickers: []Sticker, -}; - -/// https://discord.com/developers/docs/topics/gateway#guild-member-update -pub const GuildMemberUpdate = struct { - /// The id of the guild - guild_id: Snowflake, - /// User role ids - roles: [][]const u8, - /// The user - user: User, - /// Nickname of the user in the guild - nick: ?[]const u8, - /// the member's [guild avatar hash](https://discord.com/developers/docs/reference#image-formatting) - avatar: []const u8, - /// When the user joined the guild - joined_at: []const u8, - /// When the user starting boosting the guild - premium_since: ?[]const u8, - /// whether the user is deafened in voice channels - deaf: ?bool, - /// whether the user is muted in voice channels - mute: ?bool, - /// Whether the user has not yet passed the guild's Membership Screening requirements - pending: ?bool, - /// when the user's [timeout](https://support.discord.com/hc/en-us/articles/4413305239191-Time-Out-FAQ) will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out. Will throw a 403 error if the user has the ADMINISTRATOR permission or is the owner of the guild - communication_disabled_until: ?[]const u8, - /// Data for the member's guild avatar decoration - avatar_decoration_data: ?AvatarDecorationData, - /// Guild member flags - flags: ?isize, -}; - -/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all -pub const MessageReactionRemoveAll = null; - -/// https://discord.com/developers/docs/topics/gateway#guild-role-update -pub const GuildRoleUpdate = struct { - /// The id of the guild - guild_id: Snowflake, - /// The role updated - role: Role, -}; - -pub const ScheduledEventUserAdd = struct { - /// id of the guild scheduled event - guild_scheduled_event_id: Snowflake, - /// id of the user - user_id: Snowflake, - /// id of the guild - guild_id: Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#message-reaction-remove-emoji -pub const MessageReactionRemoveEmoji = null; - -/// https://discord.com/developers/docs/topics/gateway#guild-member-remove -pub const GuildMemberRemove = struct { - /// The id of the guild - guild_id: Snowflake, - /// The user who was removed - user: User, -}; - -/// https://discord.com/developers/docs/resources/guild#ban-object -pub const Ban = struct { - /// The reason for the ban - reason: ?[]const u8, - /// The banned user - user: User, -}; - -pub const ScheduledEventUserRemove = struct { - /// id of the guild scheduled event - guild_scheduled_event_id: Snowflake, - /// id of the user - user_id: Snowflake, - /// id of the guild - guild_id: Snowflake, -}; - -/// https://discord.com/developers/docs/topics/gateway#invite-delete -pub const InviteDelete = struct { - /// The channel of the invite - channel_id: Snowflake, - /// The guild of the invite - guild_id: ?Snowflake, - /// The unique invite code - code: []const u8, -}; - -/// https://discord.com/developers/docs/resources/voice#voice-region-object-voice-region-structure -pub const VoiceRegion = struct { - /// Unique Id for the region - id: Snowflake, - /// Name of the region - name: []const u8, - /// true for a single server that is closest to the current user's client - optimal: bool, - /// Whether this is a deprecated voice region (avoid switching to these) - deprecated: bool, - /// Whether this is a custom voice region (used for events/etc) - custom: bool, -}; - -pub const GuildWidgetSettings = struct { - /// whether the widget is enabled - enabled: bool, - /// the widget channel id - channel_id: ?Snowflake, -}; - -pub const InstallParams = struct { - /// Scopes to add the application to the server with - scopes: []OAuth2Scope, - /// Permissions to request for the bot role - permissions: []const u8, -}; - -pub const ForumTag = struct { - /// The id of the tag - id: Snowflake, - /// The name of the tag (0-20 characters) - name: []const u8, - /// Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission - moderated: bool, - /// The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. - emoji_id: Snowflake, - /// The unicode character of the emoji - emoji_name: ?[]const u8, -}; - -pub const DefaultReactionEmoji = struct { - /// The id of a guild's custom emoji - emoji_id: Snowflake, - /// The unicode character of the emoji - emoji_name: ?[]const u8, -}; - -pub const ModifyChannel = struct { - /// 1-100 character channel name - name: ?[]const u8, - /// The type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature - type: ?ChannelTypes, - /// The position of the channel in the left-hand listing - position: ?isize, - /// 0-1024 character channel topic - topic: ?[]const u8, - /// Whether the channel is nsfw - nsfw: ?bool, - /// 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 bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) - bitrate: ?isize, - /// 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, - /// Id of the new parent category for a channel - parent_id: ?Snowflake, - /// Voice region id for the voice channel, automatic when set to null - rtc_region: ?[]const u8, - /// The camera video quality mode of the voice channel - video_quality_mode: ?VideoQualityModes, - /// Whether the thread is archived - archived: ?bool, - /// Duration in minutes to automatically archive the thread after recent activity - auto_archive_duration: ?isize, - /// When a thread is locked, only users with `MANAGE_THREADS` can unarchive it - locked: ?bool, - /// whether non-moderators can add other non-moderators to a thread; only available on private threads - invitable: ?bool, - /// The set of tags that can be used in a GUILD_FORUM channel - available_tags: []struct { - /// The id of the tag - id: Snowflake, - /// The name of the tag (0-20 characters) - name: []const u8, - /// Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission - moderated: bool, - /// The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. - emoji_id: Snowflake, - /// The unicode character of the emoji - 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, - /// 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 - emoji_id: Snowflake, - /// The unicode character of the emoji - emoji_name: ?[]const u8, - }, - /// 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. - default_thread_rate_limit_per_user: ?isize, - /// the default sort order type used to order posts in forum channels - default_sort_order: ?SortOrderTypes, - /// the default forum layout view used to display posts in `GUILD_FORUM` channels. Defaults to `0`, which indicates a layout view has not been set by a channel admin - default_forum_layout: ?ForumLayout, -}; - -/// https://discord.com/developers/docs/resources/emoji#create-guild-emoji -pub const CreateGuildEmoji = struct { - /// Name of the emoji - name: []const u8, - ///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, -}; - -/// https://discord.com/developers/docs/resources/emoji#modify-guild-emoji -pub const ModifyGuildEmoji = struct { - /// Name of the emoji - name: ?[]const u8, - /// Roles allowed to use this emoji - roles: []?[]const u8, -}; - -pub const CreateGuildChannel = struct { - /// Channel name (1-100 characters) - name: []const u8, - /// The type of channel - type: ?ChannelTypes, - /// Channel topic (0-1024 characters) - topic: ?[]const u8, - /// The bitrate (in bits) of the voice channel (voice only) - bitrate: ?isize, - /// The user limit of the voice channel (voice only) - user_limit: ?isize, - /// 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, - /// Sorting position of the channel - position: ?isize, - /// The channel's permission overwrites - permission_overwrites: []?Overwrite, - /// Id of the parent category for a channel - parent_id: ?Snowflake, - /// Whether the channel is nsfw - nsfw: ?bool, - /// Default duration (in minutes) that clients (not the API) use for newly created threads in this channel, to determine when to automatically archive the thread after the last activity - default_auto_archive_duration: ?isize, - /// Emoji to show in the add reaction button on a thread in a forum channel - default_reaction_emoji: ?struct { - /// The id of a guild's custom emoji. Exactly one of `emojiId` and `emojiName` must be set. - emoji_id: ?Snowflake, - /// The unicode character of the emoji. Exactly one of `emojiId` and `emojiName` must be set. - emoji_name: ?[]const u8, - }, - /// Set of tags that can be used in a forum channel - available_tags: ?[]struct { - /// The id of the tag - id: Snowflake, - /// The name of the tag (0-20 characters) - name: []const u8, - /// whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission - moderated: bool, - /// The id of a guild's custom emoji - emoji_id: Snowflake, - /// The unicode character of the emoji - emoji_name: ?[]const u8, - }, - /// the default sort order type used to order posts in forum channels - default_sort_order: ?SortOrderTypes, -}; - -pub const CreateMessage = struct { - /// The message contents (up to 2000 characters) - content: ?[]const u8, - /// Can be used to verify a message was sent (up to 25 characters). Value will appear in the Message Create event. - nonce: ?union(enum) { - string: ?[]const u8, - integer: isize, - }, - /// true if this is a TTS message - tts: ?bool, - /// Embedded `rich` content (up to 6000 characters) - embeds: []?Embed, - /// Allowed mentions for the message - allowed_mentions: ?AllowedMentions, - /// Include to make your message a reply - message_reference: ?struct { - /// id of the originating message - message_id: ?Snowflake, - /// - /// id of the originating message's channel - /// Note: `channel_id` is optional when creating a reply, but will always be present when receiving an event/response that includes this data model., - /// - channel_id: ?Snowflake, - /// id of the originating message's guild - guild_id: ?Snowflake, - /// When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true - fail_if_not_exists: bool, - }, - /// The components you would like to have sent in this message - components: ?[]MessageComponent, - /// IDs of up to 3 stickers in the server to send in the message - stickerIds: ?union(enum) { one: struct { []const u8 }, two: struct { []const u8 }, three: struct { []const u8 } }, -}; - -/// https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen -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, - /// The server description to show in the welcome screen - description: ?[]const u8, -}; - -pub const FollowAnnouncementChannel = struct { - /// The id of the channel to send announcements to. - webhook_channel_id: Snowflake, -}; - -pub const EditChannelPermissionOverridesOptions = struct { - /// Permission bit set - allow: []const u8, - /// Permission bit set - deny: []const u8, - /// Either 0 (role) or 1 (member) - type: OverwriteTypes, -}; - -/// https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions -pub const ModifyGuildChannelPositions = struct { - /// Channel id - id: Snowflake, - /// Sorting position of the channel - position: ?isize, - /// Syncs the permission overwrites with the new parent, if moving to a new category - lock_positions: ?bool, - /// The new parent ID for the channel that is moved - parent_id: ?Snowflake, -}; - -pub const CreateWebhook = struct { - /// Name of the webhook (1-80 characters) - name: []const u8, - /// Image url for the default webhook avatar - avatar: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/channel#start-thread-in-forum-channel -pub const CreateForumPostWithMessage = struct { - /// 1-100 character channel name - name: []const u8, - /// duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080, - auto_archive_duration: ?isize, - /// amount of seconds a user has to wait before sending another message (0-21600) - rate_limit_per_user: ?isize, - /// contents of the first message in the forum thread - message: struct { - /// Message contents (up to 2000 characters) - content: ?[]const u8, - /// Embedded rich content (up to 6000 characters) - embeds: []?Embed, - /// Allowed mentions for the message - allowed_mentions: []?AllowedMentions, - /// Components to include with the message - components: []?[]MessageComponent, - /// IDs of up to 3 stickers in the server to send in the message - 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, - /// 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, -}; - -pub const ArchivedThreads = struct { - threads: []Channel, - members: []ThreadMember, - hasMore: bool, -}; - -pub const ActiveThreads = struct { - threads: []Channel, - members: []ThreadMember, -}; - -pub const VanityUrl = struct { - code: ?[]const u8, - uses: isize, -}; - -pub const PrunedCount = struct { - pruned: isize, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-guild-onboarding-structure -pub const GuildOnboarding = struct { - /// ID of the guild this onboarding is part of - guild_id: Snowflake, - /// Prompts shown during onboarding and in customize community - prompts: []GuildOnboardingPrompt, - /// Channel IDs that members get opted into automatically - default_channel_ids: [][]const u8, - /// Whether onboarding is enabled in the guild - enabled: bool, - /// Current mode of onboarding - mode: GuildOnboardingMode, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-prompt-structure -pub const GuildOnboardingPrompt = struct { - /// ID of the prompt - id: Snowflake, - /// Type of prompt - type: GuildOnboardingPromptType, - /// Options available within the prompt - options: []GuildOnboardingPromptOption, - /// Title of the prompt - title: []const u8, - /// Indicates whether users are limited to selecting one option for the prompt - single_select: bool, - /// Indicates whether the prompt is required before a user completes the onboarding flow - required: bool, - /// Indicates whether the prompt is present in the onboarding flow. If `false`, the prompt will only appear in the Channels & Roles tab - in_onboarding: bool, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-option-structure -pub const GuildOnboardingPromptOption = struct { - /// ID of the prompt option - id: Snowflake, - /// IDs for channels a member is added to when the option is selected - channel_ids: [][]const u8, - /// IDs for roles assigned to a member when the option is selected - role_ids: [][]const u8, - /// - /// Emoji of the option - /// - /// @remarks - /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. - /// - emoji: ?Emoji, - /// - /// Emoji ID of the option - /// - /// @remarks - /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. - /// - emoji_id: ?Snowflake, - /// - /// Emoji name of the option - /// - /// @remarks - /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. - /// - emoji_name: ?[]const u8, - /// - /// Whether the emoji is animated - /// - /// @remarks - /// When creating or updating a prompt option, the `emoji_id`, `emoji_name`, and `emoji_animated` fields must be used instead of the emoji object. - /// - emoji_animated: ?bool, - /// Title of the option - title: []const u8, - /// Description of the option - description: ?[]const u8, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-types -pub const GuildOnboardingPromptType = enum { - MultipleChoice, - DropDown, -}; - -/// https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-mode -pub const GuildOnboardingMode = enum { - /// Counts only Default Channels towards constraints - OnboardingDefault, - /// Counts Default Channels and Questions towards constraints - OnboardingAdvanced, -}; - -/// https://discord.com/developers/docs/topics/teams#team-member-roles-team-member-role-types -pub const TeamMemberRole = enum { - /// Owners are the most permissiable role, and can take destructive, irreversible actions like deleting the team itself. Teams are limited to 1 owner. - owner, - /// Admins have similar access as owners, except they cannot take destructive actions on the team or team-owned apps. - admin, - /// broken - /// - developer, - /// Read-only members can access information about a team and any team-owned apps. Some examples include getting the IDs of applications and exporting payout records. - read_only, -}; - -/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-structure -pub const Entitlement = struct { - /// ID of the entitlement - id: Snowflake, - /// ID of the SKU - sku_id: Snowflake, - /// ID of the user that is granted access to the entitlement's sku - user_id: ?Snowflake, - /// ID of the guild that is granted access to the entitlement's sku - guild_id: ?Snowflake, - /// ID of the parent application - application_id: Snowflake, - /// Type of entitlement - type: EntitlementType, - /// Entitlement was deleted - deleted: bool, - /// Start date at which the entitlement is valid. Not present when using test entitlements - starts_at: ?[]const u8, - /// Date at which the entitlement is no longer valid. Not present when using test entitlements - ends_at: ?[]const u8, - /// For consumable items, whether or not the entitlement has been consumed - consumed: ?bool, -}; - -/// https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-types -pub const EntitlementType = enum(u4) { - /// Entitlement was purchased by user - Purchase = 1, - ///Entitlement for Nitro subscription - PremiumSubscription = 2, - /// Entitlement was gifted by developer - DeveloperGift = 3, - /// Entitlement was purchased by a dev in application test mode - TestModePurchase = 4, - /// Entitlement was granted when the SKU was free - FreePurchase = 5, - /// Entitlement was gifted by another user - UserGift = 6, - /// Entitlement was claimed by user for free as a Nitro Subscriber - PremiumPurchase = 7, - /// Entitlement was purchased as an app subscription - ApplicationSubscription = 8, -}; - -/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-structure -pub const Sku = struct { - /// ID of SKU - id: Snowflake, - /// Type of SKU - type: SkuType, - /// ID of the parent application - application_id: Snowflake, - /// Customer-facing name of your premium offering - name: []const u8, - /// System-generated URL slug based on the SKU's name - slug: []const u8, - /// SKU flags combined as a bitfield - flags: SkuFlags, -}; - -/// https://discord.com/developers/docs/monetization/skus#sku-object-sku-types -pub const SkuType = enum(u4) { - /// Durable one-time purchase - Durable = 2, - /// Consumable one-time purchase - Consumable = 3, - /// Represents a recurring subscription - Subscription = 5, - /// System-generated group for each SUBSCRIPTION SKU created - SubscriptionGroup = 6, -}; - -/// https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types -pub const InteractionContextType = enum { - /// Interaction can be used within servers - Guild, - /// Interaction can be used within DMs with the app's bot user - BotDm, - /// Interaction can be used within Group DMs and DMs other than the app's bot user - PrivateChannel, -}; - -/// https://discord.com/developers/docs/resources/guild#bulk-guild-ban -pub const BulkBan = struct { - /// list of user ids, that were successfully banned - banned_users: [][]const u8, - /// list of user ids, that were not banned - failed_users: [][]const u8, -}; diff --git a/vendor/zjson/README.md b/vendor/zjson/README.md new file mode 100644 index 0000000..4054d04 --- /dev/null +++ b/vendor/zjson/README.md @@ -0,0 +1,58 @@ +## zjson + +basic usage +```zig +const json = @import("zjson"); + +const data: []const u8 = + \\ { + \\ "username": "yuzu", + \\ "id": 10000, + \\ "bot": true + \\ } +; + +const User = struct { + username: []const u8, + id: u64, + bot: bool, +}; + +const my_user = try json.parse(User, std.testing.allocator, data); +defer my_user.deinit(); + +try std.testing.expectEqual(10000, my_user.value.id); +try std.testing.expectEqualStrings("yuzu", my_user.value.username); +try std.testing.expect(my_user.bot); +``` + +definition of your custom parsing as follows +you can define your own to tell the parser how to behave +```zig +/// this is named after the TypeScript `Record` +pub fn Record(comptime T: type) type { + return struct { + /// the actual data + map: std.StringHashMapUnmanaged(T), + + /// any function `toJson` has this signature + pub fn toJson(allocator: mem.Allocator, value: JsonType) !@This() { + var map: std.StringHashMapUnmanaged(T) = .{}; + + var iterator = value.object.iterator(); + + while (iterator.next()) |pair| { + const k = pair.key_ptr.*; + const v = pair.value_ptr.*; + + // make sure to delete this as placing might fail + errdefer allocator.free(k); + errdefer v.deinit(allocator); + try map.put(allocator, k, try parseInto(T, allocator, v)); + } + + return .{ .map = map }; + } + }; +} +``` diff --git a/vendor/zjson/build.zig b/vendor/zjson/build.zig new file mode 100644 index 0000000..fba79f7 --- /dev/null +++ b/vendor/zjson/build.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("zjson", .{ + .root_source_file = b.path("json.zig"), + .optimize = optimize, + .target = target, + }); +} diff --git a/vendor/zjson/build.zig.zon b/vendor/zjson/build.zig.zon new file mode 100644 index 0000000..f98cc0c --- /dev/null +++ b/vendor/zjson/build.zig.zon @@ -0,0 +1,72 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = "zjson", + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "1.2.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + // + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "json.zig", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/vendor/zjson/json.zig b/vendor/zjson/json.zig new file mode 100644 index 0000000..3170137 --- /dev/null +++ b/vendor/zjson/json.zig @@ -0,0 +1,1117 @@ +//! JSON Parser +//! This is an implementation of a JSON parser written using the Zig standard library. +//! It uses monadic combinators to build an expressive PEG grammar that is extensible to other formats. +//! It leverages comptime for functional abstractions and type magic, enabling high-level APIs with zero runtime overhead. +//! +//! High-Level API Functions: +//! - `ultimateParser`: Parses any string or buffer, resolving data into a high-level `JsonType`. +//! - `parseIntoT`: Parses a `JsonType` and resolves it as a given struct type `T`. +//! - `parse`: Parses any string or buffer, directly resolving it as type `T` using an arena allocator. +//! +//! Example usage: +//! ```zig +//! const allocator = std.heap.GeneralPurposeAllocator(.{}){}; +//! defer allocator.deinit(); +//! const result = parseIntoT(MyStruct, "{ \"key\": \"value\" }", allocator); +//! ``` + +const std = @import("std"); +const mem = std.mem; + +/// Error Definitions +pub const ParserError = error{ + /// JSON input is malformed (e.g., missing a closing brace). + MalformedJson, + /// A character is mismatched. + UnexpectedCharacter, + /// Input ran out before parsing completed. + Empty, + /// Memory allocation failed. + OutOfMemory, + /// Infinite recursion detected. + InfiniteBehaviour, + /// Type mismatch during parsing. + TypeMismatch, + /// Failed to parse number (int or float). + NumberCastFail, + /// A string is mismatched. + MismatchedValue, + /// unclosed bracket + UnclosedBracket, + /// unclosed curly braces + UnclosedBraces, + /// for `ultimateParserAssert` + UnconsumedInput, +}; + +/// Parser a = String -> Either ParserError (String, a) +/// Functor and Applicative +pub fn Parser(comptime T: type) type { + return fn ([]const u8, allocator: mem.Allocator) ParseResult(T); +} + +/// error union, the result of a Functor a being called is a tuple (String, a) +/// which can further be piped onto another parser +pub fn ParseResult(comptime T: type) type { + return ParserError!struct { []const u8, T }; +} + +/// Either a b = Left a | Right b +pub fn Either(comptime T: type, comptime U: type) type { + return union(enum) { + left: T, + right: U, + + pub fn is(self: @This(), tag: std.meta.Tag(@This())) bool { + return self == tag; + } + }; +} + +/// parser of the string `null` +pub fn jsonNull(str: []const u8, allocator: mem.Allocator) ParseResult(void) { + const null_parser = stringP("null"); + + const rem, const out = try null_parser(str, allocator); + defer allocator.free(out); + + if (!mem.eql(u8, out, "null")) return error.MismatchedValue; // non null + + return .{ rem, {} }; +} + +/// parser of the string `true` or `false` +/// right means true +pub fn jsonBool(str: []const u8, allocator: mem.Allocator) ParseResult(bool) { + const bool_parser = either([]const u8, []const u8, stringP("false"), stringP("true")); + + const rem, const out = try bool_parser(str, allocator); + + switch (out) { + .left => |slice| { + defer allocator.free(slice); + if (!mem.eql(u8, slice, "false")) return error.MismatchedValue; // non false + return .{ rem, false }; + }, + .right => |slice| { + defer allocator.free(slice); + if (!mem.eql(u8, slice, "true")) return error.MismatchedValue; // non true + return .{ rem, true }; + }, + } +} + +pub fn nonQuote(c: u8) bool { + return c != '"'; +} + +pub fn isEscapeSeq(c: u8) bool { + return c == '\\'; +} + +/// might throw out of bounds error +pub fn escapedSequence(str: []const u8, _: mem.Allocator) ParseResult(u8) { + if (str.len == 0) return error.Empty; + const char = str[0]; + + return switch (char) { + 0x5C => if (str.len > 1 and str[1] == 0x22) .{ str[2..], str[1] } else .{ str[1..], char }, + 0x22 => error.Empty, + else => .{ str[1..], char }, + }; +} + +/// parser of any sequence of characters surrounded by " +/// handles escaping +pub fn jsonString(str: []const u8, allocator: mem.Allocator) ParseResult([]const u8) { + const quote = term('"'); + const str2, _ = try quote(str, allocator); + const str3, const string = try repeat(u8, escapedSequence)(str2, allocator); + defer allocator.free(string); + const str4, _ = try quote(str3, allocator); + + var characters: std.ArrayList(u8) = .init(allocator); + errdefer characters.deinit(); + + var i: usize = 0; + while (i < string.len) { + if (isEscapeSeq(string[i]) and i + 5 < string.len) switch (string[i + 1]) { + 0x22, 0x5C, 0x2F => |d| try characters.append(d), + 'b' => try characters.append(0x8), + 'f' => try characters.append(0xC), + 'n' => try characters.append(0xA), + 'r' => try characters.append(0xD), + 't' => try characters.append(0x9), + 'u' => { + const bytes = string[i + 2 .. i + 6]; + const code_point = std.fmt.parseInt(u21, bytes, 16) catch + return error.NumberCastFail; + //std.debug.print("cp: {x} and bytes: {u}\n", .{ code_point, bytes }); + if (code_point <= 0x7F) { + try characters.append(@as(u8, @intCast(code_point))); + } else if (code_point <= 0x7FF) { + try characters.append(0xC0 | (@as(u8, @intCast(code_point >> 6)))); + try characters.append(0x80 | (@as(u8, @intCast(code_point & 0x3F)))); + } else if (code_point <= 0xFFFF) { + try characters.append(0xE0 | (@as(u8, @intCast(code_point >> 12)))); + try characters.append(0x80 | (@as(u8, @intCast((code_point >> 6) & 0x3F)))); + try characters.append(0x80 | (@as(u8, @intCast(code_point & 0x3F)))); + } else if (code_point <= 0x10FFFF) { + try characters.append(0xF0 | (@as(u8, @intCast(code_point >> 18)))); + try characters.append(0x80 | (@as(u8, @intCast((code_point >> 12) & 0x3F)))); + try characters.append(0x80 | (@as(u8, @intCast((code_point >> 6) & 0x3F)))); + try characters.append(0x80 | (@as(u8, @intCast(code_point & 0x3F)))); + } + i += 6; + continue; + }, + else => return error.MalformedJson, + }; + + if (string[i] < 0x20) + return error.MalformedJson; + try characters.append(string[i]); + i += 1; + } + + return .{ str4, try characters.toOwnedSlice() }; +} + +pub const JsonNumber = union(enum) { + integer: i64, + float: f64, + + /// to match against another JsonNumber + pub fn is(self: JsonNumber, tag: std.meta.Tag(JsonNumber)) bool { + return self == tag; + } + + /// may only cast numeric types + pub fn cast(self: JsonNumber, comptime T: type) T { + return switch (self) { + .integer => |i| switch (@typeInfo(T)) { + .float => @as(T, @floatFromInt(i)), + .int => @as(T, @intCast(i)), + else => @compileError("not a number type"), + }, + .float => |f| switch (@typeInfo(T)) { + .float => @as(T, @floatCast(f)), + .int => @as(T, @intFromFloat(f)), + else => @compileError("not a number type"), + }, + }; + } +}; + +/// caller owns returned memory +fn formatInt(allocator: mem.Allocator, sign: ?u8, digits: []const u8) mem.Allocator.Error![]const u8 { + if (sign) |some_sign| return std.fmt.allocPrint(allocator, "{c}{s}", .{ some_sign, digits }); + return std.fmt.allocPrint(allocator, "{s}", .{digits}); +} + +/// caller owns returned memory +fn formatFloat(allocator: mem.Allocator, sign: ?u8, digits: []const u8, floating_point: ?[]const u8, exponent: ?[]const u8) mem.Allocator.Error![]const u8 { + if (exponent) |some| { + if (sign) |some_sign| return std.fmt.allocPrint(allocator, "{c}{s}.{s}{s}", .{ some_sign, digits, floating_point orelse "0", some }); + return std.fmt.allocPrint(allocator, "{s}.{s}{s}", .{ digits, floating_point orelse "0", some }); + } else { + if (sign) |some_sign| return std.fmt.allocPrint(allocator, "{c}{s}.{s}", .{ some_sign, digits, floating_point orelse "0" }); + return std.fmt.allocPrint(allocator, "{s}.{s}", .{ digits, floating_point orelse "0" }); + } +} + +fn parseExponent(str: []const u8, allocator: mem.Allocator) ParseResult([]const u8) { + const digitParser = repeat(u8, satisfy(std.ascii.isDigit)); + + const rem1, const e = try termIgnoreCase('e')(str, allocator); //ignore the e? + const rem2, const maybe_sign = try optional(Either(u8, u8), either(u8, u8, term('-'), term('+')))(rem1, allocator); + const rem3, const exponent = try digitParser(rem2, allocator); + defer allocator.free(exponent); + + // maybe do a function to optimize this + if (maybe_sign) |some| switch (some) { + .left => |neg| { + // eg: $(number)e-5 + const int = try std.fmt.allocPrint(allocator, "{c}{c}{s}", .{ e, neg, exponent }); + errdefer allocator.free(int); + + return .{ rem3, int }; + }, + .right => |pos| { + // eg: $(number)e+5 + const int = try std.fmt.allocPrint(allocator, "{c}{c}{s}", .{ e, pos, exponent }); + errdefer allocator.free(int); + + return .{ rem3, int }; + }, + } else { + // eg: $(number)e50 + const int = try std.fmt.allocPrint(allocator, "{c}{s}", .{ e, exponent }); + errdefer allocator.free(int); + + // no sign + return .{ rem3, int }; + } +} + +/// parser of either `integer` or `float` which can further be casted +/// onto the desired type +/// big integers bigger than `i64` are generally non sent throughout a JSON payload +/// as numbers but rather as strings, so it is unecessary to use a bigger integer size +pub fn jsonNumber(str: []const u8, allocator: mem.Allocator) ParseResult(JsonNumber) { + const dot = term('.'); + const digitParser = repeat(u8, satisfy(std.ascii.isDigit)); + + const str2, const maybe_sign = try optional(u8, term('-'))(str, allocator); + const str3, const digits = try digitParser(str2, allocator); + defer allocator.free(digits); + + const str4, _ = dot(str3, allocator) catch { + // no floating point + const rem, const exponent = try optional([]const u8, parseExponent)(str3, allocator); + defer if (exponent) |some| allocator.free(some); + + if (exponent == null) { + // it's an int eg: 150 + const printedi = try formatInt(allocator, maybe_sign, digits); + defer allocator.free(printedi); + const int = std.fmt.parseInt(i64, printedi, 10) catch + return error.NumberCastFail; + return .{ rem, .{ .integer = int } }; + } else { + // it is a float eg: $(digits)e5 or $(digits)e+5 + const printedf = try formatFloat(allocator, maybe_sign, digits, null, exponent); + defer allocator.free(printedf); + const double = std.fmt.parseFloat(f64, printedf) catch + return error.NumberCastFail; + + return .{ rem, .{ .float = double } }; + } + }; + // it has floating points + const str5, const floating_point = try digitParser(str4, allocator); + defer allocator.free(floating_point); + + // it might have exponent + const rem, const exponent = try optional([]const u8, parseExponent)(str5, allocator); + defer if (exponent) |some| allocator.free(some); + + const printedf = try formatFloat(allocator, maybe_sign, digits, floating_point, exponent); + defer allocator.free(printedf); + + const double = std.fmt.parseFloat(f64, printedf) catch + return error.NumberCastFail; + + return .{ rem, .{ .float = double } }; +} + +/// parser of whitespaces +const whitespaces = repeat(u8, satisfy(std.ascii.isWhitespace)); + +/// parser of any JsonType surrounded by any number of whitespaces +/// it automatically frees the whitespaces, making the code drier +pub fn token(str: []const u8, allocator: mem.Allocator) ParseResult(JsonType) { + const str2, const ws1 = try whitespaces(str, allocator); + defer allocator.free(ws1); + const str3, const val = try ultimateParser(str2, allocator); + errdefer val.deinit(allocator); + const str4, const ws2 = try whitespaces(str3, allocator); + defer allocator.free(ws2); + + return .{ str4, val }; +} + +/// parser of any parser a surrounded by whitespaces +pub fn surroundingWhiteSpaces(comptime A: type, parser: Parser(A)) Parser(A) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(A) { + const str2, const ws1 = try whitespaces(str, allocator); + defer allocator.free(ws1); + const str3, const val = try parser(str2, allocator); + const str4, const ws2 = try whitespaces(str3, allocator); + defer allocator.free(ws2); + + return .{ str4, val }; + } + }.f; +} + +/// parser of arrays +/// caller must free returned slice +pub fn jsonArray(str: []const u8, allocator: mem.Allocator) ParseResult([]JsonType) { + const openBracket = surroundingWhiteSpaces(u8, term('[')); + const closedBracket = surroundingWhiteSpaces(u8, term(']')); + const elements = sepBy(u8, JsonType, term(','), token); + + const str2, _ = try openBracket(str, allocator); + const str3, const values = try elements(str2, allocator); + errdefer allocator.free(values); + errdefer for (values) |v| v.deinit(allocator); + const str4, _ = try closedBracket(str3, allocator); + + return .{ str4, values }; +} + +test jsonArray { + const data: []const u8 = + \\ [""], + ; + const rem, const array = try jsonArray(data, std.testing.allocator); + defer std.testing.allocator.free(array); + _ = rem; +} + +pub const JsonRawHashMap = std.StringHashMapUnmanaged(JsonType); + +/// parser of `"key": value` +fn objPair(str: []const u8, allocator: mem.Allocator) ParseResult(struct { []const u8, JsonType }) { + const colon = term(':'); + + const str2, const key = surroundingWhiteSpaces([]const u8, jsonString)(str, allocator) catch |err| switch (err) { + error.UnexpectedCharacter => return err, // expected key + else => return err, + }; + errdefer allocator.free(key); + + const str3, _ = colon(str2, allocator) catch |err| switch (err) { + error.UnexpectedCharacter => return err, // expected colon + else => return err, + }; + + const str4, const value = token(str3, allocator) catch |err| switch (err) { + error.Empty => return err, // found key but no value + else => return err, + }; + errdefer value.deinit(allocator); + + return .{ str4, .{ key, value } }; +} + +/// parser for objects +/// caller is responsible of freeing the owned hashmap +/// `JsonRawHashMap` is an alias for an unmanaged hashmap as it helps reduce the memory footprint +/// this does not free the slices that the hashmap uses as keys, as it'd be undefined behaviour +pub fn jsonObject(str: []const u8, allocator: mem.Allocator) ParseResult(JsonRawHashMap) { + const openingCurlyBrace = surroundingWhiteSpaces(u8, term('{')); + const closingCurlyBrace = surroundingWhiteSpaces(u8, term('}')); + const elements = sepBy(u8, struct { []const u8, JsonType }, term(','), objPair); + const str2, _ = try openingCurlyBrace(str, allocator); + const str3, const pairs = elements(str2, allocator) catch { + const out, _ = try closingCurlyBrace(str2, allocator); + return .{ out, .{} }; + }; + defer allocator.free(pairs); + errdefer for (pairs) |p| { + allocator.free(p[0]); + p[1].deinit(allocator); + }; + const str4, _ = try closingCurlyBrace(str3, allocator); + + var obj = JsonRawHashMap{}; + errdefer obj.deinit(allocator); + + for (pairs) |entry| { + const name, const value = entry; + try obj.put(allocator, name, value); + } + + return .{ str4, obj }; +} + +test jsonObject { + const data: []const u8 = + \\{"a":"b"}# + ; + + const rem, var obj = try jsonObject(data, std.testing.allocator); + defer obj.deinit(std.testing.allocator); + var iterator = obj.iterator(); + while (iterator.next()) |kv| { + const k = kv.key_ptr.*; + const v = kv.value_ptr.*; + std.debug.print("key: {s}, value: {any} and rem: {s}\n", .{ k, v, rem }); + } + try std.testing.expect(rem.len == 1); +} + +pub const JsonType = union(enum) { + null, + bool: bool, + string: []const u8, + /// either a float or an int + /// may be casted + number: JsonNumber, + array: []JsonType, + object: JsonRawHashMap, + + pub fn is(self: JsonType, tag: std.meta.Tag(JsonType)) bool { + return self == tag; + } + + pub fn deinit(self: JsonType, allocator: mem.Allocator) void { + switch (self) { + .string => |slice| allocator.free(slice), + .array => |slice| { + for (slice) |val| val.deinit(allocator); + allocator.free(slice); + }, + .object => |obj| { + defer @constCast(&obj).deinit(allocator); + var it = obj.iterator(); + while (it.next()) |entry| { + //std.debug.print("freeing {*}\n", .{entry.key_ptr}); + allocator.free(entry.key_ptr.*); + entry.value_ptr.*.deinit(allocator); + } + //allocator.destroy(&self); + }, + else => {}, + } + } +}; + +/// entry point of the library +pub const ultimateParser: Parser(JsonType) = alternation(JsonType, .{ + jsonNull, + jsonBool, + jsonString, + jsonNumber, + jsonArray, + jsonObject, +}); + +/// same as ultimateParser but it errors out if it didn't consume the data +pub fn ultimateParserAssert(str: []const u8, allocator: mem.Allocator) ParserError!JsonType { + const rem, const json = try ultimateParser(str, allocator); + errdefer json.deinit(allocator); + if (rem.len != 0) return error.UnconsumedInput; + return json; +} + +/// empty f void +pub fn empty() Parser(void) { + return struct { + fn f(str: []const u8, _: mem.Allocator) ParseResult(void) { + return .{ str, {} }; + } + }.f; +} + +/// pure a -> f a +pub fn pure(comptime T: type, x: T) Parser(T) { + return struct { + fn f(str: []const u8, _: mem.Allocator) ParseResult(T) { + return .{ str, x }; + } + }.f; +} + +/// fmap (a -> b) -> f a -> f b +pub fn fmap( + comptime A: type, + comptime B: type, + comptime map: fn (A) B, + parser: Parser(A), +) Parser(B) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(B) { + const pofa = try parser(str, allocator); + return .{ pofa[0], map(pofa[1]) }; + } + }.f; +} + +/// u8 -> bool -> f u8 +pub fn satisfy(pred: fn (u8) bool) Parser(u8) { + return struct { + fn f(str: []const u8, _: mem.Allocator) ParseResult(u8) { + if (str.len == 0) return error.Empty; + return if (pred(str[0])) .{ str[1..], str[0] } else error.UnexpectedCharacter; + } + }.f; +} + +/// parser of char +/// also: term char = satisfy (==char) +/// u8 -> f u8 +pub fn term(char: u8) Parser(u8) { + return struct { + fn f(str: []const u8, _: mem.Allocator) ParseResult(u8) { + return if (str.len == 0) + error.Empty + else if (str[0] == char) + .{ str[1..], char } + else + error.UnexpectedCharacter; + } + }.f; +} + +/// same as term +pub fn termIgnoreCase(char: u8) Parser(u8) { + return struct { + fn f(str: []const u8, _: mem.Allocator) ParseResult(u8) { + return if (str.len == 0) + error.Empty + else if (std.ascii.toLower(str[0]) == std.ascii.toLower(char)) + .{ str[1..], char } + else + error.UnexpectedCharacter; + } + }.f; +} + +/// must handle error.Empty +pub fn sepBy( + comptime A: type, + comptime B: type, + parserA: Parser(A), // parser of separators + parserB: Parser(B), // parser of elements +) Parser([]B) { + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult([]B) { + var bailing_allocator: BailingAllocator = .init(allocator); + errdefer bailing_allocator.bail(); + + var res: std.ArrayListUnmanaged(B) = .empty; + errdefer res.deinit(allocator); + + const elemParser = repeat(struct { A, B }, join(A, B, parserA, parserB)); + // element + const str2, const first = parserB(str, allocator) catch |err| switch (err) { + error.Empty, error.MismatchedValue => { + res.deinit(allocator); + bailing_allocator.commit(); + return .{ str, &.{} }; + }, + else => return err, + }; + + try res.append(allocator, first); + + // , element... + const str3, const elems = elemParser(str2, allocator) catch { + const slice_res = try res.toOwnedSlice(allocator); + bailing_allocator.commit(); + return .{ str2, slice_res }; + }; + defer allocator.free(elems); + + for (elems) |pair| { + _, const elem = pair; + try res.append(allocator, elem); + } + + const slice_res = try res.toOwnedSlice(allocator); + bailing_allocator.commit(); + + return .{ str3, slice_res }; + } + }.f; +} + +pub fn traverse( + comptime A: type, + comptime B: type, + comptime map: fn (A) Parser(B), + comptime slice: []const A, +) Parser([]A) { + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult([]A) { + var bailing_allocator: BailingAllocator = .init(allocator); + errdefer bailing_allocator.bail(); + + var res: std.ArrayListUnmanaged(A) = .empty; + errdefer res.deinit(allocator); + + var str2 = str; + + inline for (slice) |item| { + // record at the beginning of the iteration + const len = str2.len; + const parser = map(item); + str2, const parsed = try parser(str2, allocator); + if (len == str2.len) return error.InfiniteBehaviour; + try res.append(allocator, parsed); + } + + const slice_res = try res.toOwnedSlice(allocator); + bailing_allocator.commit(); + + return .{ str2, slice_res }; + } + }.f; +} + +/// repeat :: Functor f => f a -> f [a] +/// [] is Traversable* +/// repeats a parser until it fails +/// caller must free []A in Parser of []A +/// e* +pub fn repeat(comptime A: type, parser: Parser(A)) Parser([]A) { + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult([]A) { + var bailing_allocator: BailingAllocator = .init(allocator); + errdefer bailing_allocator.bail(); + + var res: std.ArrayListUnmanaged(A) = .empty; + errdefer res.deinit(allocator); + + var str2 = str; + + while (true) { + // record at the beginning of the iteration + const len = str2.len; + str2, const parsed = parser(str2, allocator) catch |err| switch (err) { + error.Empty, error.UnexpectedCharacter => break, + else => return err, + }; + if (len == str2.len) return error.InfiniteBehaviour; + try res.append(allocator, parsed); + } + + const slice_res = try res.toOwnedSlice(allocator); + bailing_allocator.commit(); + + return .{ str2, slice_res }; + } + }.f; +} + +/// parser of any sequence of characters +/// caller must free []const u8 in Parser of []const u8 +/// String -> f String +pub fn stringP(comptime str1: []const u8) Parser([]const u8) { + return struct { + fn f(str2: []const u8, allocator: mem.Allocator) ParseResult([]const u8) { + const parser = traverse(u8, u8, term, str1); + const remaining, const parsed = try parser(str2, allocator); + + if (!std.mem.eql(u8, str1, parsed)) return error.MismatchedValue; + + return .{ remaining, parsed }; + } + }.f; +} + +/// >>= :: Monad m => m a -> (a -> m b) -> m b +/// generally useless when doing FP in Zig +pub inline fn bind(comptime A: type, comptime B: type, parserA: Parser(A), map: fn (A) Parser(B)) Parser(B) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(B) { + const pa = try parserA(str, allocator); + const pb = try map(pa[0]); + return .{ str, pb[1] }; + } + }.f; +} + +/// join :: f a -> f b -> f (a, b) +pub inline fn join(comptime A: type, comptime B: type, parserA: Parser(A), parserB: Parser(B)) Parser(struct { A, B }) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(struct { A, B }) { + const str2, const pa = try parserA(str, allocator); + const str3, const pb = try parserB(str2, allocator); + return .{ str3, .{ pa, pb } }; + } + }.f; +} + +/// either :: f a -> f b -> f (Either a b) +pub inline fn either(comptime A: type, comptime B: type, parserA: Parser(A), parserB: Parser(B)) Parser(Either(A, B)) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(Either(A, B)) { + const pa = parserA(str, allocator) catch { + const pb = try parserB(str, allocator); + return .{ pb[0], .{ .right = pb[1] } }; + }; + return .{ pa[0], .{ .left = pa[1] } }; + } + }.f; +} + +/// ap :: f (a -> b) -> f a -> f b +/// the S combinator +pub inline fn ap(comptime A: type, comptime B: type, parserA: Parser(fn (A) B), parserB: Parser(A)) Parser(B) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(B) { + const stra, const pa = try parserA(str, allocator); + const strb, const pb = try parserB(stra, allocator); + return .{ strb, pa(pb) }; + } + }.f; +} + +/// kestrel a -> b -> a +/// the K combinator +pub inline fn kestrel(comptime A: type, comptime B: type, a: A) fn (B) A { + return struct { + fn f(_: B) A { + return a; + } + }.f; +} + +/// phoenix (a -> b -> c) -> f a -> f b -> f c +/// the S' combinator +pub inline fn phoenix(comptime A: type, comptime B: type, comptime C: type, map: fn (A, B) C, parserA: Parser(A), parserB: Parser(B)) Parser(C) { + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(C) { + const stra, const pa = try parserA(str, allocator); + const strb, const pb = try parserB(stra, allocator); + return .{ strb, map(pa, pb) }; + } + }.f; +} + +/// general either +/// same as Either but with any other Union type +pub fn alternation(comptime Union: type, comptime field_parsers: FieldParsers(Union)) Parser(Union) { + if (@typeInfo(Union) != .@"union" or + @typeInfo(Union).@"union".tag_type == null) @compileError("expected a tagged `union` type"); + + return struct { + fn f(str: []const u8, allocator: mem.Allocator) ParseResult(Union) { + inline for (field_parsers, std.meta.fields(Union)) |field_parser, field| { + const rest, const parsed = field_parser(str, allocator) catch { + comptime continue; + }; + return .{ rest, @unionInit(Union, field.name, parsed) }; + } + return error.Empty; + } + }.f; +} + +/// …? +/// notice how this combinator doesn't have any issues with memory leaks +pub fn optional(comptime T: type, parser: Parser(T)) Parser(?T) { + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult(?T) { + const rest, const parsed = parser(str, allocator) catch |err| return switch (err) { + error.UnexpectedCharacter, error.Empty, error.NumberCastFail => .{ str, null }, + error.OutOfMemory => error.OutOfMemory, + else => return err, + }; + return .{ rest, parsed }; + } + }.f; +} + +fn FieldParsers(T: type) type { + var Types: []const type = &.{}; + for (std.meta.fields(T)) |field| Types = Types ++ .{Parser(field.type)}; + return std.meta.Tuple(Types); +} + +pub const BailingAllocator = struct { + child_allocator: std.mem.Allocator, + responsibilities: std.MultiArrayList(Mem), + + const Mem = struct { + ptr: [*]u8, + len: usize, + ptr_align: u8, + }; + + fn init(child_allocator: std.mem.Allocator) BailingAllocator { + return .{ + .child_allocator = child_allocator, + .responsibilities = .empty, + }; + } + + fn allocator(self: *BailingAllocator) std.mem.Allocator { + return .{ + .ptr = self, + .vtable = &.{ .alloc = alloc, .resize = resize, .free = free }, + }; + } + + /// disposes of this allocator, all allocated memory that were aquired via + /// this allocator are to be freed via this allocator's `child_allocator`. + fn commit(self: *BailingAllocator) void { + self.responsibilities.deinit(self.child_allocator); + } + + /// disposes of this allocator, frees all allocations made using this allocator. + fn bail(self: *BailingAllocator) void { + for (0..self.responsibilities.len) |i| { + const memory = self.responsibilities.get(i); + self.child_allocator.rawFree(memory.ptr[0..memory.len], memory.ptr_align, 0); + } + self.responsibilities.deinit(self.child_allocator); + } + + fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, _: usize) ?[*]u8 { + const self: *BailingAllocator = @ptrCast(@alignCast(ctx)); + self.responsibilities.ensureUnusedCapacity(self.child_allocator, 1) catch return null; + const ptr = self.child_allocator.rawAlloc(len, ptr_align, @returnAddress()) orelse return null; + self.responsibilities.appendAssumeCapacity(.{ .len = len, .ptr = ptr, .ptr_align = ptr_align }); + return ptr; + } + + fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, _: usize) bool { + const self: *BailingAllocator = @ptrCast(@alignCast(ctx)); + const res = self.child_allocator.rawResize(buf, buf_align, new_len, @returnAddress()); + if (res) { + const i = std.mem.indexOfScalar([*]u8, self.responsibilities.items(.ptr), buf.ptr) orelse + unreachable; // resized pointer must have been allocated beforehand. + self.responsibilities.items(.len)[i] = new_len; + } + return res; + } + + fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, _: usize) void { + const self: *BailingAllocator = @ptrCast(@alignCast(ctx)); + self.child_allocator.rawFree(buf, buf_align, @returnAddress()); + + const i = std.mem.indexOfScalar([*]u8, self.responsibilities.items(.ptr), buf.ptr) orelse + unreachable; // freed pointer must have been allocated beforehand. + _ = self.responsibilities.swapRemove(i); + } +}; + +/// general join +/// useful for joining more than 2 parsers +pub fn sequence(comptime Struct: type, comptime parsers: FieldParsers(Struct)) Parser(Struct) { + if (@typeInfo(Struct) != .@"struct") @compileError("expected a `struct` type"); + + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult(Struct) { + var bailing_allocator: BailingAllocator = .init(allocator); + errdefer bailing_allocator.bail(); + + var s: Struct = undefined; + var rest = str; + inline for (parsers, std.meta.fields(Struct)) |parser, field| { + rest, const parsed = try parser(rest, bailing_allocator.allocator()); + @field(s, field.name) = parsed; + } + bailing_allocator.commit(); + return .{ rest, s }; + } + }.f; +} + +/// general repeat, same as repeat +/// e* +pub fn repetition(comptime T: type, parser: Parser(T)) Parser([]T) { + return struct { + fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult([]T) { + var bailing_allocator: BailingAllocator = .init(allocator); + errdefer bailing_allocator.bail(); + + var res: std.ArrayListUnmanaged(T) = .empty; + errdefer res.deinit(allocator); + + var str2 = str; + while (true) { + str2, const parsed = parser(str2, bailing_allocator.allocator()) catch |err| switch (err) { + error.Empty, error.UnexpectedCharacter => break, + error.OutOfMemory => return err, + }; + try res.append(allocator, parsed); + } + const slice_res = try res.toOwnedSlice(allocator); + bailing_allocator.commit(); + return .{ str2, slice_res }; + } + }.f; +} + +pub const Error = std.mem.Allocator.Error || ParserError; + +/// doesn't work yet +/// but will someday +pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Error!T { + switch (@typeInfo(T)) { + .bool => { + return value.bool; + }, + .int, .comptime_int => { + // std.debug.assert(value.number.is(.integer)); + return value.number.cast(T); + }, + .float, .comptime_float => { + // std.debug.assert(value.number.is(.float)); + return value.number.cast(T); + }, + .null => { + // std.debug.assert(value.is(.null)); + return null; + }, + .optional => |optionalInfo| { + if (value.is(.null)) return null; + return try parseInto(optionalInfo.child, allocator, value); // optional + }, + .@"union" => { + if (std.meta.hasFn(T, "toJson")) { + return try T.toJson(allocator, value); + } + }, + .@"enum" => { + if (std.meta.hasFn(T, "toJson")) + return try T.toJson(allocator, value); + + switch (value) { + .string => return std.meta.stringToEnum(T, value.string).?, // useful for parsing a name into enum T + .number => return @enumFromInt(value.number.integer), // forcibly casted + else => return error.TypeMismatch, + } + }, + .@"struct" => |structInfo| { + var r: T = undefined; + if (std.meta.hasFn(T, "toJson")) + return try T.toJson(allocator, value); + + if (!value.is(.object)) + @panic("tried to cast a non-object into: " ++ @typeName(T)); + + if (structInfo.is_tuple) inline for (0..structInfo.fields.len) |i| { + r[i] = try parseInto(structInfo.fields[i].type, allocator, value.array[i]); + }; + + inline for (structInfo.fields) |field| { + if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name); + if (value.object.get(field.name)) |prop| + @field(r, field.name) = try parseInto(field.type, allocator, prop) + else switch (@typeInfo(field.type)) { + .optional => @field(r, field.name) = null, + else => @panic("unknown property: " ++ field.name), + } + } + + return r; + }, + .array => |arrayInfo| { + switch (value) { + .string => |string| { + if (arrayInfo.child != u8) + return error.TypeMismatch; + var r: T = undefined; + var i: usize = 0; + while (i < arrayInfo.len) : (i += 1) + r[i] = try parseInto(arrayInfo.child, allocator, string[i]); + return r; + }, + .array => |array| { + var r: T = undefined; + var i: usize = 0; + while (i < arrayInfo.len) : (i += 1) + r[i] = try parseInto(arrayInfo.child, allocator, array[i]); + return r; + }, + else => return error.TypeMismatch, + } + }, + .pointer => |ptrInfo| switch (ptrInfo.size) { + .One => { + // we simply allocate the type and return an address instead + // of just returning the type + const r: *ptrInfo.child = try allocator.create(ptrInfo.child); + r.* = try parseInto(ptrInfo.child, allocator, value); + return r; + }, + .Slice => switch (value) { + .array => |array| { + var arraylist: std.ArrayList(ptrInfo.child) = .init(allocator); + for (array) |jsonval| { + try arraylist.ensureUnusedCapacity(1); + const item = try parseInto(ptrInfo.child, allocator, jsonval); + arraylist.appendAssumeCapacity(item); + } + if (ptrInfo.sentinel) |some| { + const sentinel = @as(*align(1) const ptrInfo.child, @ptrCast(some)).*; + return try arraylist.toOwnedSliceSentinel(sentinel); + } + return try arraylist.toOwnedSlice(); + }, + .string => |string| { + if (ptrInfo.child == u8) { + var arraylist: std.ArrayList(u8) = .init(allocator); + for (string) |char| { + try arraylist.ensureUnusedCapacity(1); + arraylist.appendAssumeCapacity(char); + } + if (ptrInfo.sentinel) |some| { + const sentinel = @as(*align(1) const ptrInfo.child, @ptrCast(some)).*; + return try arraylist.toOwnedSliceSentinel(sentinel); + } + if (ptrInfo.is_const) { + arraylist.deinit(); + return string; + } else { + arraylist.deinit(); + const slice = try allocator.dupe(u8, string); + return @as(T, slice); + } + return try arraylist.toOwnedSlice(); + } + }, + else => return error.TypeMismatch, + }, + else => { + if (std.meta.hasFn(T, "toJson")) + return T.toJson(allocator, value); + }, + }, + else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), + } + unreachable; +} + +/// meant to handle a `JsonType` value and handling the deinitialization thereof +pub fn Owned(comptime Struct: type) type { + if (@typeInfo(Struct) != .@"struct") @compileError("expected a `struct` type"); + + return struct { + arena: *std.heap.ArenaAllocator, + value: Struct, + + pub fn deinit(self: @This()) void { + const allocator = self.arena.child_allocator; + self.arena.deinit(); + allocator.destroy(self.arena); + } + }; +} + +/// parse any string containing a JSON object root `{...}` +/// casts the value into `T` +pub fn parse(comptime T: type, child_allocator: mem.Allocator, data: []const u8) (std.meta.IntToEnumError || ParserError)!Owned(T) { + var owned: Owned(T) = .{ + .arena = try child_allocator.create(std.heap.ArenaAllocator), + .value = undefined, + }; + owned.arena.* = std.heap.ArenaAllocator.init(child_allocator); + const allocator = owned.arena.allocator(); + const value = try ultimateParserAssert(data, allocator); + owned.value = try parseInto(T, allocator, value); + errdefer owned.arena.deinit(); + + return owned; +} + +/// a hashmap for key value pairs +pub fn Record(comptime T: type) type { + return struct { + map: std.StringHashMapUnmanaged(T), + pub fn toJson(allocator: mem.Allocator, value: JsonType) !@This() { + var map: std.StringHashMapUnmanaged(T) = .{}; + + var iterator = value.object.iterator(); + + while (iterator.next()) |pair| { + const k = pair.key_ptr.*; + const v = pair.value_ptr.*; + + errdefer allocator.free(k); + errdefer v.deinit(allocator); + try map.put(allocator, k, try parseInto(T, allocator, v)); + } + + return .{ .map = map }; + } + }; +}