added a bunch of methods

This commit is contained in:
Yuzu 2024-12-07 03:37:50 -05:00
parent 5c3d9fe397
commit 556e2fc380
13 changed files with 1320 additions and 1636 deletions

1378
:w

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ pub const Sharder = @import("core.zig");
const SessionOptions = Sharder.SessionOptions;
pub const FetchReq = @import("http.zig").FetchReq;
pub const FileData = @import("http.zig").FileData;
const std = @import("std");
const mem = std.mem;

View File

@ -135,7 +135,61 @@ pub const FetchReq = struct {
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice());
}
pub fn makeRequest(self: *FetchReq, method: http.Method, path: []const u8, to_post: ?[]const u8) MakeRequestError!http.Client.FetchResult {
pub fn post2(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) {
const result = try self.makeRequest(.POST, path, null);
if (result.status != .ok)
return error.FailedRequest;
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice());
}
pub fn post3(
self: *FetchReq,
comptime T: type,
path: []const u8,
object: anytype,
files: []const FileData,
) !void {
var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator());
errdefer string.deinit();
try json.stringify(object, .{}, string.writer());
const result = try self.makeRequestWithFiles(.POST, path, try string.toOwnedSlice(), files);
_ = T;
if (result.status != .ok)
return error.FailedRequest;
}
pub fn post4(self: *FetchReq, path: []const u8, object: anytype) !void {
var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator());
errdefer string.deinit();
try json.stringify(object, .{}, string.writer());
const result = try self.makeRequest(.POST, path, try string.toOwnedSlice());
if (result.status != .no_content)
return error.FailedRequest;
}
pub fn post5(self: *FetchReq, path: []const u8) !void {
const result = try self.makeRequest(.POST, path, null);
if (result.status != .no_content)
return error.FailedRequest;
}
pub fn makeRequest(
self: *FetchReq,
method: http.Method,
path: []const u8,
to_post: ?[]const u8,
) MakeRequestError!http.Client.FetchResult {
var buf: [256]u8 = undefined;
const constructed = try std.fmt.bufPrint(&buf, "{s}{s}", .{ BASE_URL, path });
@ -157,4 +211,133 @@ pub const FetchReq = struct {
const res = try self.client.fetch(fetch_options);
return res;
}
pub fn makeRequestWithFiles(
self: *FetchReq,
method: http.Method,
path: []const u8,
to_post: []const u8,
files: []const FileData,
) !http.Client.FetchResult {
var form_fields = try std.ArrayList(FormField).initCapacity(self.allocator, files.len + 1);
errdefer form_fields.deinit();
for (files, 0..) |file, i|
form_fields.appendAssumeCapacity(.{
.name = try std.fmt.allocPrint(self.allocator, "files[{d}]", .{i}),
.filename = file.filename,
.value = file.value,
.content_type = .{ .override = try file.type.string() },
});
form_fields.appendAssumeCapacity(.{
.name = "payload_json",
.value = to_post,
.content_type = .{ .override = "application/json" },
});
var boundary: [64 + 3]u8 = undefined;
std.debug.assert((std.fmt.bufPrint(
&boundary,
"{x:0>16}-{x:0>16}-{x:0>16}-{x:0>16}",
.{ std.crypto.random.int(u64), std.crypto.random.int(u64), std.crypto.random.int(u64), std.crypto.random.int(u64) },
) catch unreachable).len == boundary.len);
const body = try createMultiPartFormDataBody(
self.allocator,
&boundary,
try form_fields.toOwnedSlice(),
);
const headers: std.http.Client.Request.Headers = .{
.content_type = .{ .override = try std.fmt.allocPrint(self.allocator, "multipart/form-data; boundary={s}", .{boundary}) },
.authorization = .{ .override = self.token },
};
var uri_buf: [256]u8 = undefined;
const uri = try std.Uri.parse(try std.fmt.bufPrint(&uri_buf, "{s}{s}", .{ BASE_URL, path }));
var server_header_buffer: [16 * 1024]u8 = undefined;
var request = try self.client.open(method, uri, .{
.keep_alive = false,
.server_header_buffer = &server_header_buffer,
.headers = headers,
});
defer request.deinit();
request.transfer_encoding = .{ .content_length = body.len };
try request.send();
try request.writeAll(body);
try request.finish();
try request.wait();
try request.reader().readAllArrayList(&self.body, 2 * 1024 * 1024);
if (request.response.status.class() == .success)
return .{ .status = request.response.status };
return error.FailedRequest; // TODO: make an Either type lol
}
};
pub const FileData = struct {
filename: []const u8,
value: []const u8,
type: union(enum) {
jpg,
jpeg,
png,
webp,
gif,
pub fn string(self: @This()) ![]const u8 {
var buf: [256]u8 = undefined;
return std.fmt.bufPrint(&buf, "image/{s}", .{@tagName(self)});
}
},
};
pub const FormField = struct {
name: []const u8,
filename: ?[]const u8 = null,
content_type: std.http.Client.Request.Headers.Value = .default,
value: []const u8,
};
fn createMultiPartFormDataBody(
allocator: std.mem.Allocator,
boundary: []const u8,
fields: []const FormField,
) error{OutOfMemory}![]const u8 {
var body: std.ArrayListUnmanaged(u8) = .{};
errdefer body.deinit(allocator);
const writer = body.writer(allocator);
for (fields) |field| {
try writer.print("--{s}\r\n", .{boundary});
if (field.filename) |filename| {
try writer.print("Content-Disposition: form-data; name=\"{s}\"; filename=\"{s}\"\r\n", .{ field.name, filename });
} else {
try writer.print("Content-Disposition: form-data; name=\"{s}\"\r\n", .{field.name});
}
switch (field.content_type) {
.default => {
if (field.filename != null) {
try writer.writeAll("Content-Type: application/octet-stream\r\n");
}
},
.omit => {},
.override => |content_type| {
try writer.print("Content-Type: {s}\r\n", .{content_type});
},
}
try writer.writeAll("\r\n");
try writer.writeAll(field.value);
try writer.writeAll("\r\n");
}
try writer.print("--{s}--\r\n", .{boundary});
return try body.toOwnedSlice(allocator);
}

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ 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 TargetTypes = @import("shared.zig").TargetTypes;
const VideoQualityModes = @import("shared.zig").VideoQualityModes;
const SortOrderTypes = @import("shared.zig").SortOrderTypes;
const User = @import("user.zig").User;
@ -198,3 +199,20 @@ pub const ModifyGuildChannelPositions = struct {
/// The new parent ID for the channel that is moved
parent_id: ?Snowflake,
};
pub const CreateChannelInvite = struct {
/// Duration of invite in seconds before expiry, or 0 for never. Between 0 and 604800 (7 days). Default: 86400 (24 hours)
max_age: ?isize,
/// Max number of users or 0 for unlimited. Between 0 and 100. Default: 0
max_uses: ?isize,
/// Whether this invite only grants temporary membership. Default: false
temporary: ?bool,
/// If true, don't try to reuse similar invite (useful for creating many unique one time use invites). Default: false
unique: ?bool,
/// The type of target for this voice channel invite
target_type: ?TargetTypes,
/// The id of the user whose stream to display for this invite, required if `target_type` is 1, the user must be streaming in the channel
target_user_id: ?Snowflake,
/// The id of the embedded application to open for this invite, required if `target_type` is 2, the application must have the `EMBEDDED` flag
target_application_id: ?Snowflake,
};

View File

@ -616,6 +616,7 @@ pub const CreateGuildChannel = struct {
};
pub const CreateMessage = struct {
attachments: []Partial(Attachment),
/// 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.

View File

@ -1,6 +1,7 @@
const User = @import("user.zig").User;
const Snowflake = @import("snowflake.zig").Snowflake;
const ActivityTypes = @import("shared.zig").ActivityTypes;
const Partial = @import("partial.zig").Partial;
/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot
pub const GetGatewayBot = struct {
@ -34,7 +35,7 @@ pub const PresenceUpdate = struct {
offline,
},
/// The user presence is being updated for
user: User,
user: Partial(User),
/// id of the guild
guild_id: Snowflake,
/// User's current activities
@ -64,7 +65,8 @@ pub const Activity = struct {
/// Unix timestamps for start and/or end of the game
timestamps: ?ActivityTimestamps,
/// Application id for the game
application_id: ?Snowflake,
/// a string
application_id: ?[]const u8,
/// The emoji used for a custom status
emoji: ?ActivityEmoji,
/// Information for the current party of the player
@ -80,21 +82,25 @@ pub const Activity = struct {
/// https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-instance-object
pub const ActivityInstance = struct {
/// Application ID
application_id: Snowflake,
/// a string
application_id: []const u8,
/// Activity Instance ID
instance_id: Snowflake,
/// a string
instance_id: []const u8,
/// Unique identifier for the launch
launch_id: Snowflake,
/// a string
launch_id: []const u8,
/// The Location the instance is runnning in
location: ActivityLocation,
/// The IDs of the Users currently connected to the instance
users: [][]const u8,
users: []Snowflake,
};
/// 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,
/// a string
id: []const u8,
/// Enum describing kind of location
kind: ActivityLocationKind,
/// The id of the Channel
@ -136,7 +142,8 @@ pub const ActivityEmoji = struct {
/// Whether this emoji is animated
animated: ?bool,
/// The id of the emoji
id: ?Snowflake,
/// a string
id: ?[]const u8,
};
/// https://discord.com/developers/docs/topics/gateway#activity-object-activity-party

View File

@ -4,6 +4,7 @@ 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 AllowedMentionTypes = @import("shared.zig").AllowedMentionsTypes;
const PremiumTypes = @import("shared.zig").PremiumTypes;
const InteractionTypes = @import("shared.zig").InteractionTypes;
const StickerTypes = @import("shared.zig").StickerTypes;
@ -342,3 +343,14 @@ pub const StickerPack = struct {
/// id of the sticker pack's [banner image](https://discord.com/developers/docs/reference#image-formatting)
banner_asset_id: ?Snowflake,
};
pub const AllowedMentions = struct {
/// An array of allowed mention types to parse from the content.
parse: []AllowedMentionTypes,
/// Array of role_ids to mention (Max size of 100)
roles: []Snowflake,
/// Array of user_ids to mention (Max size of 100)
users: []Snowflake,
/// For replies, whether to mention the author of the message being replied to (default false)
replied_user: ?bool,
};

View File

@ -19,38 +19,82 @@ pub const PremiumTypes = enum {
/// https://discord.com/developers/docs/resources/user#user-object-user-flags
pub const UserFlags = packed struct {
pub fn toRaw(self: UserFlags) u32 {
pub fn toRaw(self: UserFlags) u34 {
return @bitCast(self);
}
pub fn fromRaw(raw: u32) UserFlags {
pub fn fromRaw(raw: u34) UserFlags {
return @bitCast(raw);
}
pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u32));
return @bitCast(value.number.cast(u34));
}
DiscordEmployee: bool = false,
PartneredServerOwner: bool = false,
HypeSquadEventsMember: bool = false,
BugHunterLevel1: bool = false,
_pad: u3 = 0,
MfaSms: bool = false,
PremiumPromoDismissed: bool = false,
HouseBravery: bool = false,
HouseBrilliance: bool = false,
HouseBalance: bool = false,
EarlySupporter: bool = false,
TeamUser: bool = false,
_pad2: u4 = 0,
PartnerOrVerificationApplication: bool = false,
System: bool = false,
HasUnreadUrgentMessages: bool = false,
BugHunterLevel2: bool = false,
_pad3: u1 = 0,
UnderageDeleted: bool = false,
VerifiedBot: bool = false,
EarlyVerifiedBotDeveloper: bool = false,
DiscordCertifiedModerator: bool = false,
BotHttpInteractions: bool = false,
_pad4: u3 = 0,
Spammer: bool = false,
DisablePremium: bool = false,
ActiveDeveloper: bool = false,
_pad5: u6 = 0,
_pad: u10 = 0,
Quarantined: bool = false,
};
pub const PremiumUsageFlags = packed struct {
pub fn toRaw(self: PremiumUsageFlags) u8 {
return @bitCast(self);
}
pub fn fromRaw(raw: u8) PremiumUsageFlags {
return @bitCast(raw);
}
pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
PremiumDiscriminator: bool = false,
AnimatedAvatar: bool = false,
ProfileBanner: bool = false,
_pad: u5 = 0,
};
pub const PurchasedFlags = packed struct {
pub fn toRaw(self: PurchasedFlags) u8 {
return @bitCast(self);
}
pub fn fromRaw(raw: u8) PurchasedFlags {
return @bitCast(raw);
}
pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
NitroClassic: bool = false,
Nitro: bool = false,
GuildBoost: bool = false,
NitroBasic: bool = false,
_pad: u4 = 0,
};
pub const MemberFlags = packed struct {

View File

@ -8,6 +8,10 @@ pub const Snowflake = enum(u64) {
return @intFromEnum(self);
}
pub fn from(int: u64) Snowflake {
return @enumFromInt(int);
}
pub fn fromMaybe(raw: ?[]const u8) std.fmt.ParseIntError!?Snowflake {
if (raw) |id| return @enumFromInt(try std.fmt.parseInt(u64, id, 10));
return null;
@ -28,7 +32,12 @@ pub const Snowflake = enum(u64) {
pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
if (value.is(.string))
return Snowflake.fromRaw(value.string) catch unreachable;
return Snowflake.fromRaw(value.string) catch std.debug.panic("invalid snowflake: {s}\n", .{value.string});
unreachable;
}
pub fn format(self: Snowflake) ![]const u8 {
var buf: [256]u8 = undefined;
return std.fmt.bufPrint(&buf, "{d}\n", .{self.into()});
}
};

View File

@ -1,5 +1,12 @@
const Snowflake = @import("snowflake.zig").Snowflake;
const Channel = @import("channel.zig").Channel;
const ChannelTypes = @import("shared.zig").ChannelTypes;
const MessageFlags = @import("shared.zig").MessageFlags;
const Embed = @import("embed.zig").Embed;
const Partial = @import("partial.zig").Partial;
const Attachment = @import("attachment.zig").Attachment;
const AllowedMentions = @import("message.zig").AllowedMentions;
const MessageComponent = @import("message.zig").MessageComponent;
pub const ThreadMetadata = struct {
/// Whether the thread is archived
@ -53,3 +60,59 @@ pub const ThreadListSync = struct {
/// 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/channel#start-thread-from-message
pub const StartThreadFromMessage = struct {
/// 1-100 character thread name
name: []const u8,
/// Duration in minutes to automatically archive the thread after recent activity
auto_archive_duration: ?isize,
/// Amount of seconds a user has to wait before sending another message (0-21600)
rate_limit_per_user: ?isize,
};
/// https://discord.com/developers/docs/resources/channel#start-thread-without-message
pub const StartThreadWithoutMessage = struct {
/// 1-100 character thread name,
name: []const u8,
/// Duration in minutes to automatically archive the thread after recent activity,
auto_archive_duration: isize,
/// Amount of seconds a user has to wait before sending another message (0-21600),
rateLimitPerUser: ?isize,
/// the type of thread to create,
/// may only be AnnouncementThread, PublicThread, or PrivateThread
type: ChannelTypes,
/// whether non-moderators can add other non-moderators to a thread; only available when creating a private thread,
invitable: ?bool,
};
/// https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel-forum-and-media-thread-message-params-object
pub const CreateForumAndMediaThreadMessage = struct {
/// Message contents (up to 2000 characters)
content: ?[]const u8,
/// Up to 10 rich embeds (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: ?[]Snowflake,
/// Attachment objects with filename and description. See Uploading Files
attachments: ?[]Partial(Attachment),
/// Message flags combined as a bitfield (only SUPPRESS_EMBEDS and SUPPRESS_NOTIFICATIONS can be set)
flags: ?MessageFlags,
};
pub const StartThreadInForumOrMediaChannel = 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/media thread
message: CreateForumAndMediaThreadMessage,
/// The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM or a GUILD_MEDIA channel
applied_tags: ?[]Snowflake,
};

View File

@ -142,3 +142,12 @@ pub const ApplicationRoleConnection = struct {
/// 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),
};
pub const ModifyCurrentUser = struct {
/// user's username, if changed may cause the user's discriminator to be randomized.
username: ?[]const u8,
/// if passed, modifies the user's avatar
avatar: ?[]const u8,
/// if passed, modifies the user's banner
banner: ?[]const u8,
};

View File

@ -17,15 +17,15 @@ fn message_create(session: *Shard, message: Discord.Message) !void {
std.debug.print("captured: {?s} send by {s}\n", .{ message.content, message.author.username });
if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) {
const payload: Discord.Partial(Discord.CreateMessage) = .{
const msg = try session.sendMessage(message.channel_id, .{
.content = "discord.zig best library",
};
_ = try session.sendMessage(message.channel_id, payload);
});
defer msg.deinit();
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 9999 }){};
var handler = Discord.init(gpa.allocator());
try handler.start(.{
.token = std.posix.getenv("DISCORD_TOKEN").?,