add components :)

This commit is contained in:
Yuzu 2024-12-13 05:02:21 -05:00
parent 846b265e07
commit e8fbc72485
10 changed files with 273 additions and 36 deletions

View File

@ -9,39 +9,40 @@ A high-performance bleeding edge Discord library in Zig, featuring full API cove
```zig
const std = @import("std");
const Discord = @import("discord.zig");
const Discord = @import("discord");
const Shard = Discord.Shard;
const Intents = Discord.Intents;
fn ready(_: *Shard, payload: Discord.Ready) !void {
std.debug.print("logged in as {s}\n", .{payload.user.username});
}
fn message_create(session: *Shard, message: Discord.Message) !void {
if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) {
var result = try session.sendMessage(message.channel_id, .{ .content = "discord.zig best lib" });
if (std.ascii.eqlIgnoreCase(message.content.?, "!hi")) {
var result = try session.sendMessage(message.channel_id, .{
.content = "hello world from discord.zig",
});
defer result.deinit();
switch (result.value) {
.left => |e| std.debug.panic("Error: {d}\r{s}\n", .{ e.code, e.message }), // or you may tell the end user the error
.right => |m| std.debug.print("Sent: {?s} sent by {s}\n", .{ m.content, m.author.username }),
const m = result.value.unwrap();
std.debug.print("sent: {?s}\n", .{m.content});
}
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 9999 }){};
var handler = Discord.init(gpa.allocator());
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();
var handler = Discord.init(allocator);
defer handler.deinit();
try handler.start(.{
.token = std.posix.getenv("TOKEN").?, // or your token
.intents = Intents.fromRaw(53608447), // all intents
.intents = Discord.Intents.fromRaw(53608447);
.token = std.posix.getenv("DISCORD_TOKEN").?,
.run = .{ .message_create = &message_create, .ready = &ready },
.log = .yes,
.options = .{},
});
errdefer handler.deinit();
}
```
## Installation
```zig
// In your build.zig file

View File

@ -38,7 +38,7 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
marin.root_module.addImport("discord.zig", dzig);
marin.root_module.addImport("discord", dzig);
marin.root_module.addImport("ws", websocket.module("websocket"));
marin.root_module.addImport("zlib", zlib.module("zlib"));
marin.root_module.addImport("deque", deque.module("zig-deque"));

View File

@ -154,7 +154,7 @@ fn spawnBuckets(self: *Self) ![][]Shard {
fn create(self: *Self, shard_id: usize) !Shard {
if (self.shards.get(shard_id)) |s| return s;
const shard: Shard = try Shard.init(self.allocator, shard_id, .{
const shard: Shard = try .init(self.allocator, shard_id, self.options.total_shards, .{
.token = self.shard_details.token,
.intents = self.shard_details.intents,
.options = Shard.ShardOptions{

View File

@ -1011,7 +1011,7 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
const fieldname = switch (value) {
.string => |slice| slice,
else => @panic("can only cast strings for untagged union"),
else => @panic("can only cast strings for tagged union"),
};
inline for (unionInfo.fields) |u_field| {

View File

@ -74,6 +74,7 @@ pub const ShardOptions = struct {
ratelimit_options: RatelimitOptions = .{},
};
total_shards: usize,
id: usize,
client: ws.Client,
@ -128,6 +129,7 @@ pub fn identify(self: *Self, properties: ?IdentifyProperties) SendError!void {
.intents = self.details.intents.toRaw(),
.properties = properties orelse default_identify_properties,
.token = self.details.token,
.shard = &.{ self.id, self.total_shards },
},
};
try self.send(false, data);
@ -144,7 +146,7 @@ pub fn identify(self: *Self, properties: ?IdentifyProperties) SendError!void {
}
}
pub fn init(allocator: mem.Allocator, shard_id: usize, settings: struct {
pub fn init(allocator: mem.Allocator, shard_id: usize, total_shards: usize, settings: struct {
token: []const u8,
intents: Intents,
options: ShardOptions,
@ -161,6 +163,7 @@ pub fn init(allocator: mem.Allocator, shard_id: usize, settings: struct {
.ratelimit_options = settings.options.ratelimit_options,
},
.id = shard_id,
.total_shards = total_shards,
.allocator = allocator,
.details = ShardDetails{
.token = settings.token,
@ -2799,12 +2802,7 @@ pub fn createSticker(
defer req.deinit();
var files = .{file};
return req.post2(
Types.Sticker,
path,
sticker,
&files,
);
return req.post2(Types.Sticker, path, sticker, &files);
}
/// Modify the given sticker.

View File

@ -0,0 +1,236 @@
const Partial = @import("partial.zig").Partial;
const Snowflake = @import("snowflake.zig").Snowflake;
const Emoji = @import("emoji.zig").Emoji;
const ButtonStyles = @import("shared.zig").ButtonStyles;
const ChannelTypes = @import("shared.zig").ChannelTypes;
const MessageComponentTypes = @import("shared.zig").MessageComponentTypes;
const zjson = @import("../json.zig");
const std = @import("std");
/// https://discord.com/developers/docs/interactions/message-components#buttons
pub const Button = struct {
/// 2 for a button
type: MessageComponentTypes,
/// A button style
style: ButtonStyles,
/// Text that appears on the button; max 80 characters
label: ?[]const u8,
/// name, id, and animated
emoji: Partial(Emoji),
/// Developer-defined identifier for the button; max 100 characters
custom_id: ?[]const u8,
/// Identifier for a purchasable SKU, only available when using premium-style buttons
sku_id: ?Snowflake,
/// URL for link-style buttons
url: ?[]const u8,
/// Whether the button is disabled (defaults to false)
disabled: ?bool,
};
pub const SelectOption = struct {
/// User-facing name of the option; max 100 characters
label: []const u8,
/// Dev-defined value of the option; max 100 characters
value: []const u8,
/// Additional description of the option; max 100 characters
description: ?[]const u8,
/// id, name, and animated
emoji: ?Partial(Emoji),
/// Will show this option as selected by default
default: ?bool,
};
pub const DefaultValue = struct {
/// ID of a user, role, or channel
id: Snowflake,
/// Type of value that id represents. Either "user", "role", or "channel"
type: union(enum) { user, role, channel },
};
/// https://discord.com/developers/docs/interactions/message-components#select-menus
pub const SelectMenuString = struct {
/// Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8)
type: MessageComponentTypes,
/// ID for the select menu; max 100 characters
custom_id: []const u8,
/// Specified choices in a select menu (only required and available for string selects (type 3); max 25
/// * options is required for string select menus (component type 3), and unavailable for all other select menu components.
options: ?[]SelectOption,
/// Placeholder text if nothing is selected; max 150 characters
placeholder: ?[]const u8,
/// Minimum number of items that must be chosen (defaults to 1); min 0, max 25
min_values: ?usize,
/// Maximum number of items that can be chosen (defaults to 1); max 25
max_values: ?usize,
/// Whether select menu is disabled (defaults to false)
disabled: ?bool,
};
/// https://discord.com/developers/docs/interactions/message-components#select-menus
pub const SelectMenuUsers = struct {
/// Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8)
type: MessageComponentTypes,
/// ID for the select menu; max 100 characters
custom_id: []const u8,
/// Placeholder text if nothing is selected; max 150 characters
placeholder: ?[]const u8,
/// List of default values for auto-populated select menu components; number of default values must be in the range defined by min_values and max_values
/// *** default_values is only available for auto-populated select menu components, which include user (5), role (6), mentionable (7), and channel (8) components.
default_values: ?[]DefaultValue,
/// Minimum number of items that must be chosen (defaults to 1); min 0, max 25
min_values: ?usize,
/// Maximum number of items that can be chosen (defaults to 1); max 25
max_values: ?usize,
/// Whether select menu is disabled (defaults to false)
disabled: ?bool,
};
/// https://discord.com/developers/docs/interactions/message-components#select-menus
pub const SelectMenuRoles = struct {
/// Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8)
type: MessageComponentTypes,
/// ID for the select menu; max 100 characters
custom_id: []const u8,
/// Placeholder text if nothing is selected; max 150 characters
placeholder: ?[]const u8,
/// List of default values for auto-populated select menu components; number of default values must be in the range defined by min_values and max_values
/// *** default_values is only available for auto-populated select menu components, which include user (5), role (6), mentionable (7), and channel (8) components.
default_values: ?[]DefaultValue,
/// Minimum number of items that must be chosen (defaults to 1); min 0, max 25
min_values: ?usize,
/// Maximum number of items that can be chosen (defaults to 1); max 25
max_values: ?usize,
/// Whether select menu is disabled (defaults to false)
disabled: ?bool,
};
/// https://discord.com/developers/docs/interactions/message-components#select-menus
pub const SelectMenuUsersAndRoles = struct {
/// Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8)
type: MessageComponentTypes,
/// ID for the select menu; max 100 characters
custom_id: []const u8,
/// Placeholder text if nothing is selected; max 150 characters
placeholder: ?[]const u8,
/// List of default values for auto-populated select menu components; number of default values must be in the range defined by min_values and max_values
/// *** default_values is only available for auto-populated select menu components, which include user (5), role (6), mentionable (7), and channel (8) components.
default_values: ?[]DefaultValue,
/// Minimum number of items that must be chosen (defaults to 1); min 0, max 25
min_values: ?usize,
/// Maximum number of items that can be chosen (defaults to 1); max 25
max_values: ?usize,
/// Whether select menu is disabled (defaults to false)
disabled: ?bool,
};
/// https://discord.com/developers/docs/interactions/message-components#select-menus
pub const SelectMenuChannels = struct {
/// Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8)
type: MessageComponentTypes,
/// ID for the select menu; max 100 characters
custom_id: []const u8,
/// List of channel types to include in the channel select component (type 8)
/// ** channel_types can only be used for channel select menu components.
channel_types: ?[]ChannelTypes,
/// Placeholder text if nothing is selected; max 150 characters
placeholder: ?[]const u8,
/// List of default values for auto-populated select menu components; number of default values must be in the range defined by min_values and max_values
/// *** default_values is only available for auto-populated select menu components, which include user (5), role (6), mentionable (7), and channel (8) components.
default_values: ?[]DefaultValue,
/// Minimum number of items that must be chosen (defaults to 1); min 0, max 25
min_values: ?usize,
/// Maximum number of items that can be chosen (defaults to 1); max 25
max_values: ?usize,
/// Whether select menu is disabled (defaults to false)
disabled: ?bool,
};
pub const SelectMenu = union(MessageComponentTypes) {
SelectMenu: SelectMenuString,
SelectMenuUsers: SelectMenuUsers,
SelectMenuRoles: SelectMenuRoles,
SelectMenuUsersAndRoles: SelectMenuUsersAndRoles,
SelectMenuChannels: SelectMenuChannels,
pub fn toJson(allocator: std.mem.Allocator, value: zjson.JsonType) !@This() {
if (!value.is(.object))
@panic("coulnd't match against non-object type");
switch (value.object.get("type") orelse @panic("couldn't find property `type`")) {
.number => |num| switch (num) {
.integer => |int| return switch (@as(MessageComponentTypes, @enumFromInt(int))) {
.SelectMenu => .{ .SelectMenu = try zjson.parseInto(SelectMenuString, allocator, value) },
.SelectMenuUsers => .{ .SelectMenuUsers = try zjson.parseInto(SelectMenuUsers, allocator, value) },
.SelectMenuRoles => .{ .SelectMenuRoles = try zjson.parseInto(SelectMenuRoles, allocator, value) },
.SelectMenuUsersAndRoles => .{ .SelectMenuUsersAndRoles = try zjson.parseInto(SelectMenuUsersAndRoles, allocator, value) },
.SelectMenuChannels => .{ .SelectMenuChannels = try zjson.parseInto(SelectMenuChannels, allocator, value) },
else => unreachable,
},
else => unreachable,
},
else => @panic("got type but couldn't match against non enum member `type`"),
}
unreachable;
}
};
pub const InputTextStyles = enum(u4) {
Short = 1,
Paragraph,
};
pub const InputText = struct {
/// 4 for a text input
type: MessageComponentTypes,
/// Developer-defined identifier for the input; max 100 characters
custom_id: []const u8,
/// The Text Input Style
style: InputTextStyles,
/// Label for this component; max 45 characters
label: []const u8,
/// Minimum input length for a text input; min 0, max 4000
min_length: ?usize,
/// Maximum input length for a text input; min 1, max 4000
max_length: ?usize,
/// Whether this component is required to be filled (defaults to true)
required: ?bool,
/// Pre-filled value for this component; max 4000 characters
value: ?[]const u8,
/// Custom placeholder text if the input is empty; max 100 characters
placeholder: ?[]const u8,
};
pub const MessageComponent = union(MessageComponentTypes) {
ActionRow: []MessageComponent,
Button: Button,
SelectMenu: SelectMenuString,
InputText: InputText,
SelectMenuUsers: SelectMenuUsers,
SelectMenuRoles: SelectMenuRoles,
SelectMenuUsersAndRoles: SelectMenuUsersAndRoles,
SelectMenuChannels: SelectMenuChannels,
pub fn toJson(allocator: std.mem.Allocator, value: zjson.JsonType) !@This() {
if (!value.is(.object))
@panic("coulnd't match against non-object type");
switch (value.object.get("type") orelse @panic("couldn't find property `type`")) {
.number => |num| switch (num) {
.integer => |int| return switch (@as(MessageComponentTypes, @enumFromInt(int))) {
.ActionRow => .{ .ActionRow = try zjson.parseInto([]MessageComponent, allocator, value) },
.Button => .{ .Button = try zjson.parseInto(Button, allocator, value) },
.SelectMenu => .{ .SelectMenu = try zjson.parseInto(SelectMenuString, allocator, value) },
.InputText => .{ .InputText = try zjson.parseInto(InputText, allocator, value) },
.SelectMenuUsers => .{ .SelectMenuUsers = try zjson.parseInto(SelectMenuUsers, allocator, value) },
.SelectMenuRoles => .{ .SelectMenuRoles = try zjson.parseInto(SelectMenuRoles, allocator, value) },
.SelectMenuUsersAndRoles => .{ .SelectMenuUsersAndRoles = try zjson.parseInto(SelectMenuUsersAndRoles, allocator, value) },
.SelectMenuChannels => .{ .SelectMenuChannels = try zjson.parseInto(SelectMenuChannels, allocator, value) },
},
else => unreachable,
},
else => @panic("got type but couldn't match against non enum member `type`"),
}
unreachable;
}
};

View File

@ -35,7 +35,7 @@ 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 MessageComponent = @import("component.zig").MessageComponent;
const Sticker = @import("sticker.zig").Sticker;
const Partial = @import("partial.zig").Partial;
const ReactionType = @import("message.zig").ReactionType;

View File

@ -33,9 +33,7 @@ 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;
const MessageComponent = @import("component.zig").MessageComponent;
/// https://discord.com/developers/docs/resources/channel#message-object
pub const Message = struct {

View File

@ -27,6 +27,7 @@ pub usingnamespace @import("auditlog.zig");
pub usingnamespace @import("automod.zig");
pub usingnamespace @import("channel.zig");
pub usingnamespace @import("command.zig");
pub usingnamespace @import("component.zig");
pub usingnamespace @import("embed.zig");
pub usingnamespace @import("emoji.zig");
pub usingnamespace @import("gateway.zig");

View File

@ -15,7 +15,7 @@
//! PERFORMANCE OF THIS SOFTWARE.
const std = @import("std");
const Discord = @import("discord.zig");
const Discord = @import("discord");
const Shard = Discord.Shard;
const Intents = Discord.Intents;
@ -26,18 +26,22 @@ fn ready(_: *Shard, payload: Discord.Ready) !void {
}
fn message_create(session: *Shard, message: Discord.Message) !void {
if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) {
if (message.content != null and std.ascii.eqlIgnoreCase(message.content.?, "!hi")) {
var result = try session.sendMessage(message.channel_id, .{ .content = "hi :)" });
defer result.deinit();
const m = result.value.unwrap();
std.debug.print("sent: {?s}\n", .{m.content});
};
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 9999 }){};
var handler = Discord.init(gpa.allocator());
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();
var handler = Discord.init(allocator);
defer handler.deinit();
try handler.start(.{
.token = std.posix.getenv("DISCORD_TOKEN").?,
.intents = Intents.fromRaw(INTENTS),
@ -45,5 +49,4 @@ pub fn main() !void {
.log = .yes,
.options = .{},
});
errdefer handler.deinit();
}